diff --git a/applets/appmenu/lib/appmenuapplet.cpp b/applets/appmenu/lib/appmenuapplet.cpp --- a/applets/appmenu/lib/appmenuapplet.cpp +++ b/applets/appmenu/lib/appmenuapplet.cpp @@ -158,6 +158,7 @@ void AppMenuApplet::onMenuAboutToHide() { + m_model->setProperty("menuOpen", false); setCurrentIndex(-1); } @@ -210,11 +211,7 @@ actionMenu->winId();//create window handle actionMenu->windowHandle()->setTransientParent(ctx->window()); - actionMenu->popup(pos); - if (view() == FullView) { - // hide the old menu only after showing the new one to avoid brief flickering - // in other windows as they briefly re-gain focus QMenu *oldMenu = m_currentMenu; m_currentMenu = actionMenu; if (oldMenu && oldMenu != actionMenu) { @@ -224,6 +221,9 @@ } } + actionMenu->popup(pos); + m_model->setProperty("menuOpen", true); + setCurrentIndex(idx); // FIXME TODO connect only once diff --git a/applets/appmenu/package/contents/ui/main.qml b/applets/appmenu/package/contents/ui/main.qml --- a/applets/appmenu/package/contents/ui/main.qml +++ b/applets/appmenu/package/contents/ui/main.qml @@ -147,7 +147,9 @@ // QMenu opens on press, so we'll replicate that here MouseArea { anchors.fill: parent + hoverEnabled: plasmoid.nativeInterface.currentIndex !== -1 onPressed: parent.clicked() + onEntered: parent.clicked() } } } diff --git a/applets/appmenu/plugin/CMakeLists.txt b/applets/appmenu/plugin/CMakeLists.txt --- a/applets/appmenu/plugin/CMakeLists.txt +++ b/applets/appmenu/plugin/CMakeLists.txt @@ -10,6 +10,7 @@ Qt5::Quick KF5::Plasma KF5::WindowSystem + KF5::I18n PW::LibTaskManager dbusmenuqt) diff --git a/applets/appmenu/plugin/appmenumodel.h b/applets/appmenu/plugin/appmenumodel.h --- a/applets/appmenu/plugin/appmenumodel.h +++ b/applets/appmenu/plugin/appmenumodel.h @@ -22,6 +22,7 @@ #ifndef APPMENUMODEL_H #define APPMENUMODEL_H +#include #include #include #include @@ -42,6 +43,7 @@ Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) + Q_PROPERTY(bool menuOpen READ menuOpen WRITE setMenuOpen NOTIFY menuOpenChanged) public: explicit AppMenuModel(QObject *parent = nullptr); @@ -65,6 +67,11 @@ QRect screenGeometry() const; void setScreenGeometry(QRect geometry); + QList flatActionList(); + + void setMenuOpen(bool open); + bool menuOpen() const; + Q_SIGNAL void menuOpenChanged(); signals: void requestActivateIndex(int index); @@ -93,6 +100,12 @@ WId m_delayedMenuWindowId = 0; QPointer m_menu; + QPointer m_searchAction; + QList m_currentSearchActions; + bool m_menuOpen; + + void removeSearchActionsFromMenu(); + void insertSearchActionsIntoMenu(const QString &filter = QString()); QDBusServiceWatcher *m_serviceWatcher; QString m_serviceName; diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp --- a/applets/appmenu/plugin/appmenumodel.cpp +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -29,6 +29,12 @@ #include #include #include +#include +#include +#include + +#include +#include #include #include @@ -57,6 +63,8 @@ m_tasksModel(new TaskManager::TasksModel(this)) { m_tasksModel->setFilterByScreen(true); + m_menuOpen = false; + connect(this, &AppMenuModel::menuOpenChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, this, &AppMenuModel::onActiveWindowChanged); connect(m_tasksModel, &TaskManager::TasksModel::screenGeometryChanged, this, &AppMenuModel::screenGeometryChanged); @@ -79,6 +87,34 @@ emit modelNeedsUpdate(); } }); + + // X11 has funky menu behaviour that prevents this from working properly. + if (KWindowSystem::isPlatformWayland()) { + m_searchAction = new QAction(this); + m_searchAction->setText(i18n("Search")); + m_searchAction->setObjectName(QStringLiteral("appmenu")); + + auto searchMenu = new QMenu; + auto searchAction = new QWidgetAction(this); + auto searchBar = new QLineEdit; + searchBar->setPlaceholderText(i18n("Search...")); + searchBar->setMinimumWidth(200); + connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, [=]() { + if (!m_menuOpen) { + searchBar->setText(""); + } + }); + connect(searchBar, &QLineEdit::textChanged, [=]() mutable { + insertSearchActionsIntoMenu(searchBar->text()); + connect(this, &AppMenuModel::modelNeedsUpdate, this, [this, searchBar]() mutable { + }); + insertSearchActionsIntoMenu(searchBar->text()); + }); + searchAction->setDefaultWidget(searchBar); + searchMenu->addAction(searchAction); + searchMenu->addSeparator(); + m_searchAction->setMenu(searchMenu); + } } AppMenuModel::~AppMenuModel() = default; @@ -97,6 +133,19 @@ } } +void AppMenuModel::setMenuOpen(bool open) +{ + if (m_menuOpen != open) { + m_menuOpen = open; + emit menuOpenChanged(); + } +} + +bool AppMenuModel::menuOpen() const +{ + return m_menuOpen; +} + bool AppMenuModel::visible() const { return m_visible; @@ -127,7 +176,30 @@ return 0; } - return m_menu->actions().count(); + return m_menu->actions().count()+(KWindowSystem::isPlatformWayland() ? 1 : 0); +} + +void AppMenuModel::removeSearchActionsFromMenu() +{ + for (const auto &action: m_currentSearchActions) { + m_searchAction->menu()->removeAction(action); + } + m_currentSearchActions = QList(); +} + +void AppMenuModel::insertSearchActionsIntoMenu(const QString &filter) +{ + removeSearchActionsFromMenu(); + if (filter.isEmpty()) { + return; + } + const auto actions = flatActionList(); + for (const auto &action: actions) { + if (action->text().contains(filter, Qt::CaseInsensitive)) { + m_searchAction->menu()->addAction(action); + m_currentSearchActions << action; + } + } } void AppMenuModel::update() @@ -143,6 +215,9 @@ const QString objectPath = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuObjectPath).toString(); const QString serviceName = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuServiceName).toString(); + if (m_menuOpen) { + return; + } if (!objectPath.isEmpty() && !serviceName.isEmpty()) { setMenuAvailable(true); updateApplicationMenu(serviceName, objectPath); @@ -162,14 +237,36 @@ return roleNames; } +QList AppMenuModel::flatActionList() +{ + QList ret; + if (!m_menuAvailable || !m_menu) { + return ret; + } + const auto actions = m_menu->findChildren(); + for (auto &action: actions) { + if (action->menu() == nullptr) { + ret << action; + } + } + return ret; +} + QVariant AppMenuModel::data(const QModelIndex &index, int role) const { const int row = index.row(); if (row < 0 || !m_menuAvailable || !m_menu) { return QVariant(); } const auto actions = m_menu->actions(); + if (row == actions.count() && KWindowSystem::isPlatformWayland()) { + if (role == MenuRole) { + return m_searchAction->text(); + } else if (role == ActionRole) { + return QVariant::fromValue((void*)m_searchAction); + } + } if (row >= actions.count()) { return QVariant(); }