dbusmenu: Refactor the code to allow dynamic updating of menus

* Transfer propertiesUpdated and updated signals from submenus to parent
  menus. Without this, the adaptor only receives this signal from top-level
  menu items, and doesn't receive it from items of submenus. Connect to
  these signals when a menu item is added or synced, and disconnect when it
  is removed.
* Make QDBusPlatformMenus use IDs of items containing them, not their own
  IDs (own IDs do not make any sense since they are not exported over D-Bus).
* Store toplevel menus per-adaptor, to make it possible to export multiple
  menus (for example a menubar and a tray icon menu).
* Adjust the QDBusMenuLayoutItem::populate methods to always get the menu
  via its containing item and to populate the menus recursively.
* Map D-Bus menu AboutToShow method to platform menu aboutToShow method,
  and map hovered and closed events to hovered and aboutToHide signals.
  (QTBUG-46293)
* Always set the visible property on item. Otherwise, when an item becomes
  visible, the D-Bus menu still thinks it's invisible because that property
  was not changed back to true. (QTBUG-48647)
* Call emitUpdated from insertMenuItem and removeMenuItem methods, as they
  really update layout. Do not call it from syncMenuItem, it changes only
  properties but not the layout.
* Start revision numbering with 1, because libdbusmenu-based hosts ignore
  updated signal with revision=1.

Task-number: QTBUG-46293
Task-number: QTBUG-48647
Change-Id: Icf713405db0443e25462c1a19046df7689fe5e78
Reviewed-by: Shawn Rutledge <shawn.rutledge@theqtcompany.com>
Reviewed-by: Błażej Szczygieł <spaz16@wp.pl>
This commit is contained in:
Dmitry Shachnev 2016-01-21 20:43:50 +03:00
parent 01859cc121
commit 9c7f37e648
6 changed files with 102 additions and 54 deletions

View File

@ -51,8 +51,9 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QDBusMenuAdaptor::QDBusMenuAdaptor(QObject *parent) QDBusMenuAdaptor::QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu)
: QDBusAbstractAdaptor(parent) : QDBusAbstractAdaptor(topLevelMenu)
, m_topLevelMenu(topLevelMenu)
{ {
setAutoRelaySignals(true); setAutoRelaySignals(true);
} }
@ -80,7 +81,17 @@ uint QDBusMenuAdaptor::version() const
bool QDBusMenuAdaptor::AboutToShow(int id) bool QDBusMenuAdaptor::AboutToShow(int id)
{ {
qCDebug(qLcMenu) << id; qCDebug(qLcMenu) << id;
return false; if (id == 0) {
emit m_topLevelMenu->aboutToShow();
} else {
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
if (item) {
const QDBusPlatformMenu *menu = static_cast<const QDBusPlatformMenu *>(item->menu());
if (menu)
emit const_cast<QDBusPlatformMenu *>(menu)->aboutToShow();
}
}
return false; // updateNeeded (we don't know that, so false)
} }
QList<int> QDBusMenuAdaptor::AboutToShowGroup(const QList<int> &ids, QList<int> &idErrors) QList<int> QDBusMenuAdaptor::AboutToShowGroup(const QList<int> &ids, QList<int> &idErrors)
@ -88,6 +99,8 @@ QList<int> QDBusMenuAdaptor::AboutToShowGroup(const QList<int> &ids, QList<int>
qCDebug(qLcMenu) << ids; qCDebug(qLcMenu) << ids;
Q_UNUSED(idErrors) Q_UNUSED(idErrors)
idErrors.clear(); idErrors.clear();
Q_FOREACH (int id, ids)
AboutToShow(id);
return QList<int>(); // updatesNeeded return QList<int>(); // updatesNeeded
} }
@ -97,9 +110,20 @@ void QDBusMenuAdaptor::Event(int id, const QString &eventId, const QDBusVariant
Q_UNUSED(timestamp) Q_UNUSED(timestamp)
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id); QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
qCDebug(qLcMenu) << id << (item ? item->text() : QLatin1String("")) << eventId; qCDebug(qLcMenu) << id << (item ? item->text() : QLatin1String("")) << eventId;
// Events occur on both menus and menuitems, but we only care if it's an item being clicked.
if (item && eventId == QLatin1String("clicked")) if (item && eventId == QLatin1String("clicked"))
item->trigger(); item->trigger();
if (item && eventId == QLatin1String("hovered"))
emit item->hovered();
if (eventId == QLatin1String("closed")) {
// There is no explicit AboutToHide method, so map closed event to aboutToHide method
const QDBusPlatformMenu *menu = Q_NULLPTR;
if (item)
menu = static_cast<const QDBusPlatformMenu *>(item->menu());
else if (id == 0)
menu = m_topLevelMenu;
if (menu)
emit const_cast<QDBusPlatformMenu *>(menu)->aboutToHide();
}
} }
QList<int> QDBusMenuAdaptor::EventGroup(const QDBusMenuEventList &events) QList<int> QDBusMenuAdaptor::EventGroup(const QDBusMenuEventList &events)
@ -117,7 +141,7 @@ QDBusMenuItemList QDBusMenuAdaptor::GetGroupProperties(const QList<int> &ids, co
uint QDBusMenuAdaptor::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, QDBusMenuLayoutItem &layout) uint QDBusMenuAdaptor::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, QDBusMenuLayoutItem &layout)
{ {
uint ret = layout.populate(parentId, recursionDepth, propertyNames); uint ret = layout.populate(parentId, recursionDepth, propertyNames, m_topLevelMenu);
qCDebug(qLcMenu) << parentId << "depth" << recursionDepth << propertyNames << layout.m_id << layout.m_properties << "revision" << ret << layout; qCDebug(qLcMenu) << parentId << "depth" << recursionDepth << propertyNames << layout.m_id << layout.m_properties << "revision" << ret << layout;
return ret; return ret;
} }

View File

@ -140,7 +140,7 @@ class QDBusMenuAdaptor: public QDBusAbstractAdaptor
" </interface>\n" " </interface>\n"
"") "")
public: public:
QDBusMenuAdaptor(QObject *parent); QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu);
virtual ~QDBusMenuAdaptor(); virtual ~QDBusMenuAdaptor();
public: // PROPERTIES public: // PROPERTIES
@ -166,6 +166,9 @@ Q_SIGNALS: // SIGNALS
void ItemActivationRequested(int id, uint timestamp); void ItemActivationRequested(int id, uint timestamp);
void ItemsPropertiesUpdated(const QDBusMenuItemList &updatedProps, const QDBusMenuItemKeysList &removedProps); void ItemsPropertiesUpdated(const QDBusMenuItemList &updatedProps, const QDBusMenuItemKeysList &removedProps);
void LayoutUpdated(uint revision, int parent); void LayoutUpdated(uint revision, int parent);
private:
QDBusPlatformMenu *m_topLevelMenu;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -80,29 +80,27 @@ const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItemKeys &key
return arg; return arg;
} }
uint QDBusMenuLayoutItem::populate(int id, int depth, const QStringList &propertyNames) uint QDBusMenuLayoutItem::populate(int id, int depth, const QStringList &propertyNames, const QDBusPlatformMenu *topLevelMenu)
{ {
qCDebug(qLcMenu) << id << "depth" << depth << propertyNames; qCDebug(qLcMenu) << id << "depth" << depth << propertyNames;
m_id = id; m_id = id;
if (id == 0) { if (id == 0) {
m_properties.insert(QLatin1String("children-display"), QLatin1String("submenu")); m_properties.insert(QLatin1String("children-display"), QLatin1String("submenu"));
Q_FOREACH (const QDBusPlatformMenu *menu, QDBusPlatformMenu::topLevelMenus()) { if (topLevelMenu)
if (menu) populate(topLevelMenu, depth, propertyNames);
populate(menu, depth, propertyNames);
}
return 1; // revision return 1; // revision
} }
const QDBusPlatformMenu *menu = QDBusPlatformMenu::byId(id); QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
if (!menu) { if (item) {
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id); const QDBusPlatformMenu *menu = static_cast<const QDBusPlatformMenu *>(item->menu());
if (item)
menu = static_cast<const QDBusPlatformMenu *>(item->menu()); if (menu) {
if (depth != 0)
populate(menu, depth, propertyNames);
return menu->revision();
}
} }
if (depth != 0 && menu)
populate(menu, depth, propertyNames);
if (menu)
return menu->revision();
return 1; // revision return 1; // revision
} }
@ -118,11 +116,13 @@ void QDBusMenuLayoutItem::populate(const QDBusPlatformMenu *menu, int depth, con
void QDBusMenuLayoutItem::populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames) void QDBusMenuLayoutItem::populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames)
{ {
Q_UNUSED(depth)
Q_UNUSED(propertyNames)
m_id = item->dbusID(); m_id = item->dbusID();
QDBusMenuItem proxy(item); QDBusMenuItem proxy(item);
m_properties = proxy.m_properties; m_properties = proxy.m_properties;
const QDBusPlatformMenu *menu = static_cast<const QDBusPlatformMenu *>(item->menu());
if (depth != 0 && menu)
populate(menu, depth, propertyNames);
} }
const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuLayoutItem &item) const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuLayoutItem &item)
@ -199,8 +199,7 @@ QDBusMenuItem::QDBusMenuItem(const QDBusPlatformMenuItem *item)
m_properties.insert(QLatin1String("icon-data"), buf.data()); m_properties.insert(QLatin1String("icon-data"), buf.data());
} }
} }
if (!item->isVisible()) m_properties.insert(QLatin1String("visible"), item->isVisible());
m_properties.insert(QLatin1String("visible"), false);
} }
QDBusMenuItemList QDBusMenuItem::items(const QList<int> &ids, const QStringList &propertyNames) QDBusMenuItemList QDBusMenuItem::items(const QList<int> &ids, const QStringList &propertyNames)

View File

@ -96,7 +96,7 @@ typedef QVector<QDBusMenuItemKeys> QDBusMenuItemKeysList;
class QDBusMenuLayoutItem class QDBusMenuLayoutItem
{ {
public: public:
uint populate(int id, int depth, const QStringList &propertyNames); uint populate(int id, int depth, const QStringList &propertyNames, const QDBusPlatformMenu *topLevelMenu);
void populate(const QDBusPlatformMenu *menu, int depth, const QStringList &propertyNames); void populate(const QDBusPlatformMenu *menu, int depth, const QStringList &propertyNames);
void populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames); void populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames);

View File

@ -41,9 +41,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(qLcMenu, "qt.qpa.menu") Q_LOGGING_CATEGORY(qLcMenu, "qt.qpa.menu")
static int nextDBusID = 1; static int nextDBusID = 1;
QHash<int, QDBusPlatformMenu *> menusByID;
QHash<int, QDBusPlatformMenuItem *> menuItemsByID; QHash<int, QDBusPlatformMenuItem *> menuItemsByID;
QList<QDBusPlatformMenu *> QDBusPlatformMenu::m_topLevelMenus;
QDBusPlatformMenuItem::QDBusPlatformMenuItem(quintptr tag) QDBusPlatformMenuItem::QDBusPlatformMenuItem(quintptr tag)
: m_tag(tag ? tag : reinterpret_cast<quintptr>(this)) // QMenu will overwrite this later : m_tag(tag ? tag : reinterpret_cast<quintptr>(this)) // QMenu will overwrite this later
@ -85,7 +83,11 @@ void QDBusPlatformMenuItem::setIcon(const QIcon &icon)
*/ */
void QDBusPlatformMenuItem::setMenu(QPlatformMenu *menu) void QDBusPlatformMenuItem::setMenu(QPlatformMenu *menu)
{ {
m_subMenu = static_cast<QDBusPlatformMenu *>(menu); if (m_subMenu)
static_cast<QDBusPlatformMenu *>(m_subMenu)->setContainingMenuItem(Q_NULLPTR);
m_subMenu = menu;
if (menu)
static_cast<QDBusPlatformMenu *>(menu)->setContainingMenuItem(this);
} }
void QDBusPlatformMenuItem::setEnabled(bool enabled) void QDBusPlatformMenuItem::setEnabled(bool enabled)
@ -130,7 +132,11 @@ void QDBusPlatformMenuItem::trigger()
QDBusPlatformMenuItem *QDBusPlatformMenuItem::byId(int id) QDBusPlatformMenuItem *QDBusPlatformMenuItem::byId(int id)
{ {
return menuItemsByID[id]; // We need to check contains because otherwise QHash would insert
// a default-constructed nullptr value into menuItemsByID
if (menuItemsByID.contains(id))
return menuItemsByID[id];
return Q_NULLPTR;
} }
QList<const QDBusPlatformMenuItem *> QDBusPlatformMenuItem::byIds(const QList<int> &ids) QList<const QDBusPlatformMenuItem *> QDBusPlatformMenuItem::byIds(const QList<int> &ids)
@ -149,18 +155,13 @@ QDBusPlatformMenu::QDBusPlatformMenu(quintptr tag)
, m_isEnabled(true) , m_isEnabled(true)
, m_isVisible(true) , m_isVisible(true)
, m_isSeparator(false) , m_isSeparator(false)
, m_dbusID(nextDBusID++) , m_revision(1)
, m_revision(0) , m_containingMenuItem(Q_NULLPTR)
{ {
menusByID.insert(m_dbusID, this);
// Assume it's top-level until we find out otherwise
m_topLevelMenus << this;
} }
QDBusPlatformMenu::~QDBusPlatformMenu() QDBusPlatformMenu::~QDBusPlatformMenu()
{ {
menusByID.remove(m_dbusID);
m_topLevelMenus.removeOne(this);
} }
void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
@ -174,38 +175,59 @@ void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMen
else else
m_items.insert(idx, item); m_items.insert(idx, item);
m_itemsByTag.insert(item->tag(), item); m_itemsByTag.insert(item->tag(), item);
// If a menu is found as a submenu under an item, we know that it's not a top-level menu.
if (item->menu()) if (item->menu())
m_topLevelMenus.removeOne(const_cast<QDBusPlatformMenu *>(static_cast<const QDBusPlatformMenu *>(item->menu()))); syncSubMenu(static_cast<const QDBusPlatformMenu *>(item->menu()));
emitUpdated();
} }
void QDBusPlatformMenu::removeMenuItem(QPlatformMenuItem *menuItem) void QDBusPlatformMenu::removeMenuItem(QPlatformMenuItem *menuItem)
{ {
m_items.removeAll(static_cast<QDBusPlatformMenuItem *>(menuItem)); QDBusPlatformMenuItem *item = static_cast<QDBusPlatformMenuItem *>(menuItem);
m_items.removeAll(item);
m_itemsByTag.remove(menuItem->tag()); m_itemsByTag.remove(menuItem->tag());
if (item->menu()) {
// disconnect from the signals we connected to in syncSubMenu()
const QDBusPlatformMenu *menu = static_cast<const QDBusPlatformMenu *>(item->menu());
disconnect(menu, &QDBusPlatformMenu::propertiesUpdated,
this, &QDBusPlatformMenu::propertiesUpdated);
disconnect(menu, &QDBusPlatformMenu::updated,
this, &QDBusPlatformMenu::updated);
}
emitUpdated();
}
void QDBusPlatformMenu::syncSubMenu(const QDBusPlatformMenu *menu)
{
// The adaptor is only connected to the propertiesUpdated signal of the top-level
// menu, so the submenus should transfer their signals to their parents.
connect(menu, &QDBusPlatformMenu::propertiesUpdated,
this, &QDBusPlatformMenu::propertiesUpdated, Qt::UniqueConnection);
connect(menu, &QDBusPlatformMenu::updated,
this, &QDBusPlatformMenu::updated, Qt::UniqueConnection);
} }
void QDBusPlatformMenu::syncMenuItem(QPlatformMenuItem *menuItem) void QDBusPlatformMenu::syncMenuItem(QPlatformMenuItem *menuItem)
{ {
QDBusPlatformMenuItem *item = static_cast<QDBusPlatformMenuItem *>(menuItem);
// if a submenu was added to this item, we need to connect to its signals
if (item->menu())
syncSubMenu(static_cast<const QDBusPlatformMenu *>(item->menu()));
// TODO keep around copies of the QDBusMenuLayoutItems so they can be updated? // TODO keep around copies of the QDBusMenuLayoutItems so they can be updated?
// or eliminate them by putting dbus streaming operators in this class instead? // or eliminate them by putting dbus streaming operators in this class instead?
// or somehow tell the dbusmenu client that something has changed, so it will ask for properties again // or somehow tell the dbusmenu client that something has changed, so it will ask for properties again
emitUpdated();
QDBusMenuItemList updated; QDBusMenuItemList updated;
QDBusMenuItemKeysList removed; QDBusMenuItemKeysList removed;
updated << QDBusMenuItem(static_cast<QDBusPlatformMenuItem *>(menuItem)); updated << QDBusMenuItem(item);
qCDebug(qLcMenu) << updated; qCDebug(qLcMenu) << updated;
emit propertiesUpdated(updated, removed); emit propertiesUpdated(updated, removed);
} }
QDBusPlatformMenu *QDBusPlatformMenu::byId(int id)
{
return menusByID[id];
}
void QDBusPlatformMenu::emitUpdated() void QDBusPlatformMenu::emitUpdated()
{ {
emit updated(++m_revision, m_dbusID); if (m_containingMenuItem)
emit updated(++m_revision, m_containingMenuItem->dbusID());
else
emit updated(++m_revision, 0);
} }
void QDBusPlatformMenu::setTag(quintptr tag) void QDBusPlatformMenu::setTag(quintptr tag)
@ -233,6 +255,11 @@ void QDBusPlatformMenu::setVisible(bool isVisible)
m_isVisible = isVisible; m_isVisible = isVisible;
} }
void QDBusPlatformMenu::setContainingMenuItem(QDBusPlatformMenuItem *item)
{
m_containingMenuItem = item;
}
QPlatformMenuItem *QDBusPlatformMenu::menuItemAt(int position) const QPlatformMenuItem *QDBusPlatformMenu::menuItemAt(int position) const
{ {
return m_items.at(position); return m_items.at(position);

View File

@ -130,6 +130,7 @@ public:
~QDBusPlatformMenu(); ~QDBusPlatformMenu();
void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE; void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE;
void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
void syncSubMenu(const QDBusPlatformMenu *menu);
void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
void syncSeparatorsCollapsible(bool enable) Q_DECL_OVERRIDE { Q_UNUSED(enable); } void syncSeparatorsCollapsible(bool enable) Q_DECL_OVERRIDE { Q_UNUSED(enable); }
@ -147,8 +148,7 @@ public:
void setMinimumWidth(int width) Q_DECL_OVERRIDE { Q_UNUSED(width); } void setMinimumWidth(int width) Q_DECL_OVERRIDE { Q_UNUSED(width); }
void setFont(const QFont &font) Q_DECL_OVERRIDE { Q_UNUSED(font); } void setFont(const QFont &font) Q_DECL_OVERRIDE { Q_UNUSED(font); }
void setMenuType(MenuType type) Q_DECL_OVERRIDE { Q_UNUSED(type); } void setMenuType(MenuType type) Q_DECL_OVERRIDE { Q_UNUSED(type); }
void setContainingMenuItem(QDBusPlatformMenuItem *item);
int dbusID() const { return m_dbusID; }
void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) Q_DECL_OVERRIDE void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) Q_DECL_OVERRIDE
{ {
@ -169,9 +169,6 @@ public:
bool operator==(const QDBusPlatformMenu& other) { return m_tag == other.m_tag; } bool operator==(const QDBusPlatformMenu& other) { return m_tag == other.m_tag; }
static QDBusPlatformMenu* byId(int id);
static QList<QDBusPlatformMenu *> topLevelMenus() { return m_topLevelMenus; }
uint revision() const { return m_revision; } uint revision() const { return m_revision; }
void emitUpdated(); void emitUpdated();
@ -187,12 +184,10 @@ private:
bool m_isEnabled; bool m_isEnabled;
bool m_isVisible; bool m_isVisible;
bool m_isSeparator; bool m_isSeparator;
int m_dbusID;
uint m_revision; uint m_revision;
QHash<quintptr, QDBusPlatformMenuItem *> m_itemsByTag; QHash<quintptr, QDBusPlatformMenuItem *> m_itemsByTag;
QList<QDBusPlatformMenuItem *> m_items; QList<QDBusPlatformMenuItem *> m_items;
QDBusPlatformMenuItem *m_containingMenuItem; QDBusPlatformMenuItem *m_containingMenuItem;
static QList<QDBusPlatformMenu *> m_topLevelMenus;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE