diff --git a/src/core/qsmenu.cpp b/src/core/qsmenu.cpp index e7eed3c3..1587912d 100644 --- a/src/core/qsmenu.cpp +++ b/src/core/qsmenu.cpp @@ -21,6 +21,8 @@ QString QsMenuButtonType::toString(QsMenuButtonType::Enum value) { } } +QsMenuEntry* QsMenuEntry::menu() { return this; } + void QsMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) { auto* platform = new PlatformMenuEntry(this); @@ -53,22 +55,52 @@ void QsMenuEntry::unref() { QQmlListProperty QsMenuEntry::children() { return QsMenuEntry::emptyChildren(this); } -QsMenuEntry* QsMenuOpener::menu() const { return this->mMenu; } +QsMenuOpener::~QsMenuOpener() { + if (this->mMenu) { + if (this->mMenu->menu()) this->mMenu->menu()->unref(); + this->mMenu->unrefHandle(); + } +} -void QsMenuOpener::setMenu(QsMenuEntry* menu) { +QsMenuHandle* QsMenuOpener::menu() const { return this->mMenu; } + +void QsMenuOpener::setMenu(QsMenuHandle* menu) { if (menu == this->mMenu) return; if (this->mMenu != nullptr) { - this->mMenu->unref(); QObject::disconnect(this->mMenu, nullptr, this, nullptr); + + if (this->mMenu->menu()) { + this->mMenu->menu()->unref(); + QObject::disconnect(this->mMenu->menu(), nullptr, this, nullptr); + } + + this->mMenu->unrefHandle(); } this->mMenu = menu; if (menu != nullptr) { + auto onMenuChanged = [this, menu]() { + if (menu->menu()) { + QObject::connect( + menu->menu(), + &QsMenuEntry::childrenChanged, + this, + &QsMenuOpener::childrenChanged + ); + + menu->menu()->ref(); + } + + emit this->childrenChanged(); + }; + QObject::connect(menu, &QObject::destroyed, this, &QsMenuOpener::onMenuDestroyed); - QObject::connect(menu, &QsMenuEntry::childrenChanged, this, &QsMenuOpener::childrenChanged); - menu->ref(); + QObject::connect(menu, &QsMenuHandle::menuChanged, this, onMenuChanged); + + if (menu->menu()) onMenuChanged(); + menu->refHandle(); } emit this->menuChanged(); @@ -82,7 +114,11 @@ void QsMenuOpener::onMenuDestroyed() { } QQmlListProperty QsMenuOpener::children() { - return this->mMenu ? this->mMenu->children() : QsMenuEntry::emptyChildren(this); + if (this->mMenu && this->mMenu->menu()) { + return this->mMenu->menu()->children(); + } else { + return QsMenuEntry::emptyChildren(this); + } } qsizetype QsMenuEntry::childCount(QQmlListProperty* /*property*/) { return 0; } diff --git a/src/core/qsmenu.hpp b/src/core/qsmenu.hpp index 9c2f168d..f0e81edd 100644 --- a/src/core/qsmenu.hpp +++ b/src/core/qsmenu.hpp @@ -33,7 +33,28 @@ public: Q_INVOKABLE static QString toString(QsMenuButtonType::Enum value); }; -class QsMenuEntry: public QObject { +class QsMenuEntry; + +///! Menu handle for QsMenuOpener +/// See @@QsMenuOpener. +class QsMenuHandle: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE(""); + +public: + explicit QsMenuHandle(QObject* parent): QObject(parent) {} + + virtual void refHandle() {}; + virtual void unrefHandle() {}; + + [[nodiscard]] virtual QsMenuEntry* menu() = 0; + +signals: + void menuChanged(); +}; + +class QsMenuEntry: public QsMenuHandle { Q_OBJECT; /// If this menu item should be rendered as a separator between other items. /// @@ -68,7 +89,9 @@ class QsMenuEntry: public QObject { QML_UNCREATABLE("QsMenuEntry cannot be directly created"); public: - explicit QsMenuEntry(QObject* parent = nullptr): QObject(parent) {} + explicit QsMenuEntry(QObject* parent): QsMenuHandle(parent) {} + + [[nodiscard]] QsMenuEntry* menu() override; /// Display a platform menu at the given location relative to the parent window. Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY); @@ -111,37 +134,22 @@ private: qsizetype refcount = 0; }; -class QsMenuHandle: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - -public: - explicit QsMenuHandle(QObject* parent): QObject(parent) {} - - virtual void ref() {}; - virtual void unref() {}; - - [[nodiscard]] virtual QsMenuEntry* menu() const = 0; - -signals: - void menuChanged(); -}; - ///! Provides access to children of a QsMenuEntry class QsMenuOpener: public QObject { Q_OBJECT; /// The menu to retrieve children from. - Q_PROPERTY(QsMenuEntry* menu READ menu WRITE setMenu NOTIFY menuChanged); + Q_PROPERTY(QsMenuHandle* menu READ menu WRITE setMenu NOTIFY menuChanged); /// The children of the given menu. Q_PROPERTY(QQmlListProperty children READ children NOTIFY childrenChanged); QML_ELEMENT; public: explicit QsMenuOpener(QObject* parent = nullptr): QObject(parent) {} + ~QsMenuOpener() override; + Q_DISABLE_COPY_MOVE(QsMenuOpener); - [[nodiscard]] QsMenuEntry* menu() const; - void setMenu(QsMenuEntry* menu); + [[nodiscard]] QsMenuHandle* menu() const; + void setMenu(QsMenuHandle* menu); [[nodiscard]] QQmlListProperty children(); @@ -153,7 +161,7 @@ private slots: void onMenuDestroyed(); private: - QsMenuEntry* mMenu = nullptr; + QsMenuHandle* mMenu = nullptr; }; } // namespace qs::menu diff --git a/src/dbus/dbusmenu/dbusmenu.cpp b/src/dbus/dbusmenu/dbusmenu.cpp index 1539500d..0d966610 100644 --- a/src/dbus/dbusmenu/dbusmenu.cpp +++ b/src/dbus/dbusmenu/dbusmenu.cpp @@ -519,7 +519,7 @@ void DBusMenuHandle::setAddress(const QString& service, const QString& path) { this->onMenuPathChanged(); } -void DBusMenuHandle::ref() { +void DBusMenuHandle::refHandle() { this->refcount++; qCDebug(logDbusMenu) << this << "gained a reference. Refcount is now" << this->refcount; @@ -532,7 +532,7 @@ void DBusMenuHandle::ref() { } } -void DBusMenuHandle::unref() { +void DBusMenuHandle::unrefHandle() { this->refcount--; qCDebug(logDbusMenu) << this << "lost a reference. Refcount is now" << this->refcount; @@ -564,9 +564,7 @@ void DBusMenuHandle::onMenuPathChanged() { } } -QsMenuEntry* DBusMenuHandle::menu() const { - return this->loaded ? &this->mMenu->rootItem : nullptr; -} +QsMenuEntry* DBusMenuHandle::menu() { return this->loaded ? &this->mMenu->rootItem : nullptr; } QDebug operator<<(QDebug debug, const DBusMenuHandle* handle) { if (handle) { diff --git a/src/dbus/dbusmenu/dbusmenu.hpp b/src/dbus/dbusmenu/dbusmenu.hpp index cbfa61f4..0687761f 100644 --- a/src/dbus/dbusmenu/dbusmenu.hpp +++ b/src/dbus/dbusmenu/dbusmenu.hpp @@ -162,19 +162,15 @@ class DBusMenuHandle; QDebug operator<<(QDebug debug, const DBusMenuHandle* handle); class DBusMenuHandle: public menu::QsMenuHandle { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - public: explicit DBusMenuHandle(QObject* parent): menu::QsMenuHandle(parent) {} void setAddress(const QString& service, const QString& path); - void ref() override; - void unref() override; + void refHandle() override; + void unrefHandle() override; - [[nodiscard]] QsMenuEntry* menu() const override; + [[nodiscard]] QsMenuEntry* menu() override; private: void onMenuPathChanged(); diff --git a/src/services/status_notifier/qml.cpp b/src/services/status_notifier/qml.cpp index 854f4d27..1530e5f9 100644 --- a/src/services/status_notifier/qml.cpp +++ b/src/services/status_notifier/qml.cpp @@ -20,7 +20,6 @@ using namespace qs::dbus; using namespace qs::dbus::dbusmenu; using namespace qs::service::sni; using namespace qs::menu::platform; -using qs::menu::QsMenuHandle; SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObject* parent) : QObject(parent) @@ -96,6 +95,11 @@ bool SystemTrayItem::hasMenu() const { return !this->item->menuPath.get().path().isEmpty(); } +DBusMenuHandle* SystemTrayItem::menu() const { + if (this->item == nullptr) return nullptr; + return this->item->menuHandle(); +} + bool SystemTrayItem::onlyMenu() const { if (this->item == nullptr) return false; return this->item->isMenu.get(); @@ -120,7 +124,7 @@ void SystemTrayItem::display(QObject* parentWindow, qint32 relativeX, qint32 rel QObject::disconnect(handle, nullptr, this, nullptr); if (!handle->menu()) { - handle->unref(); + handle->unrefHandle(); return; } @@ -128,7 +132,7 @@ void SystemTrayItem::display(QObject* parentWindow, qint32 relativeX, qint32 rel // clang-format off QObject::connect(platform, &PlatformMenuEntry::closed, this, [=]() { platform->deleteLater(); }); - QObject::connect(platform, &QObject::destroyed, this, [=]() { handle->unref(); }); + QObject::connect(platform, &QObject::destroyed, this, [=]() { handle->unrefHandle(); }); // clang-format on auto success = platform->display(parentWindow, relativeX, relativeY); @@ -140,10 +144,10 @@ void SystemTrayItem::display(QObject* parentWindow, qint32 relativeX, qint32 rel if (handle->menu()) { onMenuChanged(); } else { - QObject::connect(handle, &QsMenuHandle::menuChanged, this, onMenuChanged); + QObject::connect(handle, &DBusMenuHandle::menuChanged, this, onMenuChanged); } - handle->ref(); + handle->refHandle(); } SystemTray::SystemTray(QObject* parent): QObject(parent) { @@ -179,7 +183,7 @@ SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; } SystemTrayMenuWatcher::~SystemTrayMenuWatcher() { if (this->item != nullptr) { - this->item->item->menuHandle()->unref(); + this->item->item->menuHandle()->unrefHandle(); } } @@ -187,14 +191,14 @@ void SystemTrayMenuWatcher::setTrayItem(SystemTrayItem* item) { if (item == this->item) return; if (this->item != nullptr) { - this->item->item->menuHandle()->unref(); + this->item->item->menuHandle()->unrefHandle(); QObject::disconnect(this->item, nullptr, this, nullptr); } this->item = item; if (item != nullptr) { - this->item->item->menuHandle()->ref(); + this->item->item->menuHandle()->refHandle(); QObject::connect(item, &QObject::destroyed, this, &SystemTrayMenuWatcher::onItemDestroyed); diff --git a/src/services/status_notifier/qml.hpp b/src/services/status_notifier/qml.hpp index 0d61e2ad..b6aa8366 100644 --- a/src/services/status_notifier/qml.hpp +++ b/src/services/status_notifier/qml.hpp @@ -53,6 +53,9 @@ Q_ENUM_NS(Enum); class SystemTrayItem: public QObject { using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem; + // intentionally wrongly aliased to temporarily hack around a docgen issue + using QsMenuHandle = qs::dbus::dbusmenu::DBusMenuHandle; + Q_OBJECT; /// A name unique to the application, such as its name. Q_PROPERTY(QString id READ id NOTIFY idChanged); @@ -64,9 +67,10 @@ class SystemTrayItem: public QObject { Q_PROPERTY(QString icon READ icon NOTIFY iconChanged); Q_PROPERTY(QString tooltipTitle READ tooltipTitle NOTIFY tooltipTitleChanged); Q_PROPERTY(QString tooltipDescription READ tooltipDescription NOTIFY tooltipDescriptionChanged); - /// If this tray item has an associated menu accessible via @@display() - /// or a @@SystemTrayMenuWatcher. + /// If this tray item has an associated menu accessible via @@display() or @@menu. Q_PROPERTY(bool hasMenu READ hasMenu NOTIFY hasMenuChanged); + /// A handle to the menu associated with this tray item, if any. + Q_PROPERTY(QsMenuHandle* menu READ menu NOTIFY hasMenuChanged); /// If this tray item only offers a menu and activation will do nothing. Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged); QML_ELEMENT; @@ -95,6 +99,7 @@ public: [[nodiscard]] QString tooltipTitle() const; [[nodiscard]] QString tooltipDescription() const; [[nodiscard]] bool hasMenu() const; + [[nodiscard]] QsMenuHandle* menu() const; [[nodiscard]] bool onlyMenu() const; qs::service::sni::StatusNotifierItem* item = nullptr; @@ -136,6 +141,9 @@ private: }; ///! Accessor for SystemTrayItem menus. +/// > [!ERROR] Deprecated in favor of @@Quickshell.QsMenuOpener.menu, +/// > which now supports directly accessing a tray menu via @@SystemTrayItem.menu. +/// /// SystemTrayMenuWatcher provides access to the associated /// @@Quickshell.DBusMenu.DBusMenuItem for a tray item. class SystemTrayMenuWatcher: public QObject {