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:
parent
01859cc121
commit
9c7f37e648
@ -51,8 +51,9 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QDBusMenuAdaptor::QDBusMenuAdaptor(QObject *parent)
|
||||
: QDBusAbstractAdaptor(parent)
|
||||
QDBusMenuAdaptor::QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu)
|
||||
: QDBusAbstractAdaptor(topLevelMenu)
|
||||
, m_topLevelMenu(topLevelMenu)
|
||||
{
|
||||
setAutoRelaySignals(true);
|
||||
}
|
||||
@ -80,7 +81,17 @@ uint QDBusMenuAdaptor::version() const
|
||||
bool QDBusMenuAdaptor::AboutToShow(int 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)
|
||||
@ -88,6 +99,8 @@ QList<int> QDBusMenuAdaptor::AboutToShowGroup(const QList<int> &ids, QList<int>
|
||||
qCDebug(qLcMenu) << ids;
|
||||
Q_UNUSED(idErrors)
|
||||
idErrors.clear();
|
||||
Q_FOREACH (int id, ids)
|
||||
AboutToShow(id);
|
||||
return QList<int>(); // updatesNeeded
|
||||
}
|
||||
|
||||
@ -97,9 +110,20 @@ void QDBusMenuAdaptor::Event(int id, const QString &eventId, const QDBusVariant
|
||||
Q_UNUSED(timestamp)
|
||||
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
|
||||
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"))
|
||||
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)
|
||||
@ -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 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;
|
||||
return ret;
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ class QDBusMenuAdaptor: public QDBusAbstractAdaptor
|
||||
" </interface>\n"
|
||||
"")
|
||||
public:
|
||||
QDBusMenuAdaptor(QObject *parent);
|
||||
QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu);
|
||||
virtual ~QDBusMenuAdaptor();
|
||||
|
||||
public: // PROPERTIES
|
||||
@ -166,6 +166,9 @@ Q_SIGNALS: // SIGNALS
|
||||
void ItemActivationRequested(int id, uint timestamp);
|
||||
void ItemsPropertiesUpdated(const QDBusMenuItemList &updatedProps, const QDBusMenuItemKeysList &removedProps);
|
||||
void LayoutUpdated(uint revision, int parent);
|
||||
|
||||
private:
|
||||
QDBusPlatformMenu *m_topLevelMenu;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -80,29 +80,27 @@ const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItemKeys &key
|
||||
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;
|
||||
m_id = id;
|
||||
if (id == 0) {
|
||||
m_properties.insert(QLatin1String("children-display"), QLatin1String("submenu"));
|
||||
Q_FOREACH (const QDBusPlatformMenu *menu, QDBusPlatformMenu::topLevelMenus()) {
|
||||
if (menu)
|
||||
populate(menu, depth, propertyNames);
|
||||
}
|
||||
if (topLevelMenu)
|
||||
populate(topLevelMenu, depth, propertyNames);
|
||||
return 1; // revision
|
||||
}
|
||||
|
||||
const QDBusPlatformMenu *menu = QDBusPlatformMenu::byId(id);
|
||||
if (!menu) {
|
||||
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
|
||||
if (item)
|
||||
menu = static_cast<const QDBusPlatformMenu *>(item->menu());
|
||||
QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id);
|
||||
if (item) {
|
||||
const QDBusPlatformMenu *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
|
||||
}
|
||||
@ -118,11 +116,13 @@ void QDBusMenuLayoutItem::populate(const QDBusPlatformMenu *menu, int depth, con
|
||||
|
||||
void QDBusMenuLayoutItem::populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames)
|
||||
{
|
||||
Q_UNUSED(depth)
|
||||
Q_UNUSED(propertyNames)
|
||||
m_id = item->dbusID();
|
||||
QDBusMenuItem proxy(item);
|
||||
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)
|
||||
@ -199,8 +199,7 @@ QDBusMenuItem::QDBusMenuItem(const QDBusPlatformMenuItem *item)
|
||||
m_properties.insert(QLatin1String("icon-data"), buf.data());
|
||||
}
|
||||
}
|
||||
if (!item->isVisible())
|
||||
m_properties.insert(QLatin1String("visible"), false);
|
||||
m_properties.insert(QLatin1String("visible"), item->isVisible());
|
||||
}
|
||||
|
||||
QDBusMenuItemList QDBusMenuItem::items(const QList<int> &ids, const QStringList &propertyNames)
|
||||
|
@ -96,7 +96,7 @@ typedef QVector<QDBusMenuItemKeys> QDBusMenuItemKeysList;
|
||||
class QDBusMenuLayoutItem
|
||||
{
|
||||
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 QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames);
|
||||
|
||||
|
@ -41,9 +41,7 @@ QT_BEGIN_NAMESPACE
|
||||
Q_LOGGING_CATEGORY(qLcMenu, "qt.qpa.menu")
|
||||
|
||||
static int nextDBusID = 1;
|
||||
QHash<int, QDBusPlatformMenu *> menusByID;
|
||||
QHash<int, QDBusPlatformMenuItem *> menuItemsByID;
|
||||
QList<QDBusPlatformMenu *> QDBusPlatformMenu::m_topLevelMenus;
|
||||
|
||||
QDBusPlatformMenuItem::QDBusPlatformMenuItem(quintptr tag)
|
||||
: 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)
|
||||
{
|
||||
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)
|
||||
@ -130,7 +132,11 @@ void QDBusPlatformMenuItem::trigger()
|
||||
|
||||
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)
|
||||
@ -149,18 +155,13 @@ QDBusPlatformMenu::QDBusPlatformMenu(quintptr tag)
|
||||
, m_isEnabled(true)
|
||||
, m_isVisible(true)
|
||||
, m_isSeparator(false)
|
||||
, m_dbusID(nextDBusID++)
|
||||
, m_revision(0)
|
||||
, m_revision(1)
|
||||
, m_containingMenuItem(Q_NULLPTR)
|
||||
{
|
||||
menusByID.insert(m_dbusID, this);
|
||||
// Assume it's top-level until we find out otherwise
|
||||
m_topLevelMenus << this;
|
||||
}
|
||||
|
||||
QDBusPlatformMenu::~QDBusPlatformMenu()
|
||||
{
|
||||
menusByID.remove(m_dbusID);
|
||||
m_topLevelMenus.removeOne(this);
|
||||
}
|
||||
|
||||
void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
|
||||
@ -174,38 +175,59 @@ void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMen
|
||||
else
|
||||
m_items.insert(idx, 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())
|
||||
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)
|
||||
{
|
||||
m_items.removeAll(static_cast<QDBusPlatformMenuItem *>(menuItem));
|
||||
QDBusPlatformMenuItem *item = static_cast<QDBusPlatformMenuItem *>(menuItem);
|
||||
m_items.removeAll(item);
|
||||
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)
|
||||
{
|
||||
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?
|
||||
// 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
|
||||
emitUpdated();
|
||||
QDBusMenuItemList updated;
|
||||
QDBusMenuItemKeysList removed;
|
||||
updated << QDBusMenuItem(static_cast<QDBusPlatformMenuItem *>(menuItem));
|
||||
updated << QDBusMenuItem(item);
|
||||
qCDebug(qLcMenu) << updated;
|
||||
emit propertiesUpdated(updated, removed);
|
||||
}
|
||||
|
||||
QDBusPlatformMenu *QDBusPlatformMenu::byId(int id)
|
||||
{
|
||||
return menusByID[id];
|
||||
}
|
||||
|
||||
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)
|
||||
@ -233,6 +255,11 @@ void QDBusPlatformMenu::setVisible(bool isVisible)
|
||||
m_isVisible = isVisible;
|
||||
}
|
||||
|
||||
void QDBusPlatformMenu::setContainingMenuItem(QDBusPlatformMenuItem *item)
|
||||
{
|
||||
m_containingMenuItem = item;
|
||||
}
|
||||
|
||||
QPlatformMenuItem *QDBusPlatformMenu::menuItemAt(int position) const
|
||||
{
|
||||
return m_items.at(position);
|
||||
|
@ -130,6 +130,7 @@ public:
|
||||
~QDBusPlatformMenu();
|
||||
void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE;
|
||||
void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
|
||||
void syncSubMenu(const QDBusPlatformMenu *menu);
|
||||
void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
|
||||
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 setFont(const QFont &font) Q_DECL_OVERRIDE { Q_UNUSED(font); }
|
||||
void setMenuType(MenuType type) Q_DECL_OVERRIDE { Q_UNUSED(type); }
|
||||
|
||||
int dbusID() const { return m_dbusID; }
|
||||
void setContainingMenuItem(QDBusPlatformMenuItem *item);
|
||||
|
||||
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; }
|
||||
|
||||
static QDBusPlatformMenu* byId(int id);
|
||||
static QList<QDBusPlatformMenu *> topLevelMenus() { return m_topLevelMenus; }
|
||||
|
||||
uint revision() const { return m_revision; }
|
||||
|
||||
void emitUpdated();
|
||||
@ -187,12 +184,10 @@ private:
|
||||
bool m_isEnabled;
|
||||
bool m_isVisible;
|
||||
bool m_isSeparator;
|
||||
int m_dbusID;
|
||||
uint m_revision;
|
||||
QHash<quintptr, QDBusPlatformMenuItem *> m_itemsByTag;
|
||||
QList<QDBusPlatformMenuItem *> m_items;
|
||||
QDBusPlatformMenuItem *m_containingMenuItem;
|
||||
static QList<QDBusPlatformMenu *> m_topLevelMenus;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
Loading…
Reference in New Issue
Block a user