service/tray!: redesign menus / dbusmenu and add native menu support

Reworks dbusmenu menus to be displayable with a system context menu.

Breaks the entire DBusMenu api.
This commit is contained in:
outfoxxed 2024-07-01 20:50:30 -07:00
parent c31bbea837
commit ec362637b8
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
18 changed files with 898 additions and 191 deletions

View file

@ -14,32 +14,18 @@
#include <qtypes.h>
#include "../../core/imageprovider.hpp"
#include "../../core/qsmenu.hpp"
#include "../properties.hpp"
#include "dbus_menu_types.hpp"
Q_DECLARE_LOGGING_CATEGORY(logDbusMenu);
namespace ToggleButtonType { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum {
/// This menu item does not have a checkbox or a radiobutton associated with it.
None = 0,
/// This menu item should draw a checkbox.
CheckBox = 1,
/// This menu item should draw a radiobutton.
RadioButton = 2,
};
Q_ENUM_NS(Enum);
} // namespace ToggleButtonType
class DBusMenuInterface;
namespace qs::dbus::dbusmenu {
QDebug operator<<(QDebug debug, const ToggleButtonType::Enum& toggleType);
// hack because docgen can't take namespaces in superclasses
using menu::QsMenuEntry;
class DBusMenu;
class DBusMenuPngImage;
@ -47,113 +33,56 @@ class DBusMenuPngImage;
///! Menu item shared by an external program.
/// Menu item shared by an external program via the
/// [DBusMenu specification](https://github.com/AyatanaIndicators/libdbusmenu/blob/master/libdbusmenu-glib/dbus-menu.xml).
class DBusMenuItem: public QObject {
class DBusMenuItem: public QsMenuEntry {
Q_OBJECT;
// clang-format off
/// Handle to the root of this menu.
Q_PROPERTY(DBusMenu* menuHandle READ menuHandle CONSTANT);
/// Text of the menu item, including hotkey markup.
Q_PROPERTY(QString label READ label NOTIFY labelChanged);
/// Text of the menu item without hotkey markup.
Q_PROPERTY(QString cleanLabel READ cleanLabel NOTIFY labelChanged);
/// If the menu item should be shown as enabled.
///
/// > [!INFO] Disabled menu items are often used as headers in addition
/// > to actual disabled entries.
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
/// Url of the menu item's icon or `""` if it doesn't have one.
///
/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
/// as shown below.
///
/// ```qml
/// Image {
/// source: menuItem.icon
/// // To get the best image quality, set the image source size to the same size
/// // as the rendered image.
/// sourceSize.width: width
/// sourceSize.height: height
/// }
/// ```
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
/// If this menu item has an associated checkbox or radiobutton.
///
/// > [!INFO] It is the responsibility of the remote application to update the state of
/// > checkboxes and radiobuttons via [checkState](#prop.checkState).
Q_PROPERTY(ToggleButtonType::Enum toggleType READ toggleType NOTIFY toggleTypeChanged);
/// The check state of the checkbox or radiobutton if applicable, as a
/// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
/// If this menu item should be rendered as a separator between other items.
///
/// No other properties have a meaningful value when `isSeparator` is true.
Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY separatorChanged);
/// If this menu item reveals a submenu containing more items.
///
/// Any submenu items must be requested by setting [showChildren](#prop.showChildren).
Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
/// If submenu entries of this item should be shown.
///
/// When true, children of this menu item will be exposed via [children](#prop.children).
/// Setting this property will additionally send the `opened` and `closed` events to the
/// process that provided the menu.
Q_PROPERTY(bool showChildren READ isShowingChildren WRITE setShowChildren NOTIFY showingChildrenChanged);
/// Children of this menu item. Only populated when [showChildren](#prop.showChildren) is true.
///
/// > [!INFO] Use [hasChildren](#prop.hasChildren) to check if this item should reveal a submenu
/// > instead of checking if `children` is empty.
Q_PROPERTY(QQmlListProperty<DBusMenuItem> children READ children NOTIFY childrenChanged);
// clang-format on
QML_ELEMENT;
QML_UNCREATABLE("DBusMenus can only be acquired from a DBusMenuHandle");
public:
explicit DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu);
/// Send a `clicked` event to the remote application for this menu item.
Q_INVOKABLE void click();
/// Send a `hovered` event to the remote application for this menu item.
/// Refreshes the menu contents.
///
/// Note: we are not aware of any programs that use this in any meaningful way.
Q_INVOKABLE void hover() const;
/// Usually you shouldn't need to call this manually but some applications providing
/// menus do not update them correctly. Call this if menus don't update their state.
///
/// The `layoutUpdated` signal will be sent when a response is received.
Q_INVOKABLE void updateLayout() const;
[[nodiscard]] DBusMenu* menuHandle() const;
[[nodiscard]] QString label() const;
[[nodiscard]] QString cleanLabel() const;
[[nodiscard]] bool enabled() const;
[[nodiscard]] QString icon() const;
[[nodiscard]] ToggleButtonType::Enum toggleType() const;
[[nodiscard]] Qt::CheckState checkState() const;
[[nodiscard]] bool isSeparator() const;
[[nodiscard]] bool hasChildren() const;
[[nodiscard]] bool isSeparator() const override;
[[nodiscard]] bool enabled() const override;
[[nodiscard]] QString text() const override;
[[nodiscard]] QString icon() const override;
[[nodiscard]] menu::QsMenuButtonType::Enum buttonType() const override;
[[nodiscard]] Qt::CheckState checkState() const override;
[[nodiscard]] bool hasChildren() const override;
[[nodiscard]] bool isShowingChildren() const;
void setShowChildren(bool showChildren);
void setShowChildrenRecursive(bool showChildren);
[[nodiscard]] QQmlListProperty<DBusMenuItem> children();
[[nodiscard]] QQmlListProperty<menu::QsMenuEntry> children() override;
void updateProperties(const QVariantMap& properties, const QStringList& removed = {});
void onChildrenUpdated();
qint32 id = 0;
QString mLabel;
QString mText;
QVector<qint32> mChildren;
bool mShowChildren = false;
bool childrenLoaded = false;
DBusMenu* menu = nullptr;
signals:
void labelChanged();
//void mnemonicChanged();
void enabledChanged();
void iconChanged();
void separatorChanged();
void toggleTypeChanged();
void checkStateChanged();
void hasChildrenChanged();
void showingChildrenChanged();
void childrenChanged();
void layoutUpdated();
private slots:
void sendOpened() const;
void sendClosed() const;
void sendTriggered() const;
private:
QString mCleanLabel;
@ -163,14 +92,14 @@ private:
bool mSeparator = false;
QString iconName;
DBusMenuPngImage* image = nullptr;
ToggleButtonType::Enum mToggleType = ToggleButtonType::None;
Qt::CheckState mCheckState = Qt::Checked;
menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None;
Qt::CheckState mCheckState = Qt::Unchecked;
bool displayChildren = false;
QVector<qint32> enabledChildren;
DBusMenuItem* parentMenu = nullptr;
static qsizetype childrenCount(QQmlListProperty<DBusMenuItem>* property);
static DBusMenuItem* childAt(QQmlListProperty<DBusMenuItem>* property, qsizetype index);
static qsizetype childrenCount(QQmlListProperty<menu::QsMenuEntry>* property);
static menu::QsMenuEntry* childAt(QQmlListProperty<menu::QsMenuEntry>* property, qsizetype index);
};
QDebug operator<<(QDebug debug, DBusMenuItem* item);
@ -192,7 +121,7 @@ public:
dbus::DBusProperty<QString> status {this->properties, "Status"};
dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath", {}, false};
void prepareToShow(qint32 item, bool sendOpened);
void prepareToShow(qint32 item, qint32 depth);
void updateLayout(qint32 parent, qint32 depth);
void removeRecursive(qint32 id);
void sendEvent(qint32 item, const QString& event);