File 0013-Global-Menu-Applet.patch of Package plasma5-workspace
From c9c5ee5a86e5cb49d652f70319cdccbedfa1949c Mon Sep 17 00:00:00 2001
From: David Edmundson <kde@davidedmundson.co.uk>
Date: Wed, 11 Jan 2017 14:02:13 +0000
Subject: [PATCH 13/44] Global Menu Applet
Summary:
This restores the global menu applet which you can place in panel.
The applet provides two views namely Compact and Full View.
Views can be switched from applet's settings.{F859590}
* Compact View provides a single button for accessing the application's
menu
{F859505}
* Full View shows the full application menu in panel
{F859566}
See https://phabricator.kde.org/D3156
Reviewers: #plasma, broulik, chinmoyr
Subscribers: andreaska, davidedmundson, plasma-devel
Tags: #plasma
Differential Revision: https://phabricator.kde.org/D3706
---
applets/CMakeLists.txt | 1 +
applets/appmenu/CMakeLists.txt | 4 +
applets/appmenu/Messages.sh | 4 +
applets/appmenu/lib/CMakeLists.txt | 15 ++
applets/appmenu/lib/appmenuapplet.cpp | 231 +++++++++++++++++++++
applets/appmenu/lib/appmenuapplet.h | 89 ++++++++
applets/appmenu/package/contents/config/config.qml | 32 +++
applets/appmenu/package/contents/config/main.xml | 15 ++
.../appmenu/package/contents/ui/configGeneral.qml | 52 +++++
applets/appmenu/package/contents/ui/main.qml | 113 ++++++++++
applets/appmenu/package/metadata.desktop | 17 ++
applets/appmenu/plugin/CMakeLists.txt | 21 ++
applets/appmenu/plugin/appmenumodel.cpp | 225 ++++++++++++++++++++
applets/appmenu/plugin/appmenumodel.h | 71 +++++++
applets/appmenu/plugin/appmenuplugin.cpp | 31 +++
applets/appmenu/plugin/appmenuplugin.h | 37 ++++
applets/appmenu/plugin/qmldir | 3 +
17 files changed, 961 insertions(+)
create mode 100644 applets/appmenu/CMakeLists.txt
create mode 100755 applets/appmenu/Messages.sh
create mode 100644 applets/appmenu/lib/CMakeLists.txt
create mode 100644 applets/appmenu/lib/appmenuapplet.cpp
create mode 100644 applets/appmenu/lib/appmenuapplet.h
create mode 100644 applets/appmenu/package/contents/config/config.qml
create mode 100644 applets/appmenu/package/contents/config/main.xml
create mode 100644 applets/appmenu/package/contents/ui/configGeneral.qml
create mode 100644 applets/appmenu/package/contents/ui/main.qml
create mode 100644 applets/appmenu/package/metadata.desktop
create mode 100644 applets/appmenu/plugin/CMakeLists.txt
create mode 100644 applets/appmenu/plugin/appmenumodel.cpp
create mode 100644 applets/appmenu/plugin/appmenumodel.h
create mode 100644 applets/appmenu/plugin/appmenuplugin.cpp
create mode 100644 applets/appmenu/plugin/appmenuplugin.h
create mode 100644 applets/appmenu/plugin/qmldir
diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt
index b404fa25..956105e5 100644
--- a/applets/CMakeLists.txt
+++ b/applets/CMakeLists.txt
@@ -6,6 +6,7 @@ plasma_install_package(mediacontroller org.kde.plasma.mediacontroller)
plasma_install_package(panelspacer org.kde.plasma.panelspacer)
plasma_install_package(lock_logout org.kde.plasma.lock_logout)
+add_subdirectory(appmenu)
add_subdirectory(systemmonitor)
add_subdirectory(batterymonitor)
add_subdirectory(calendar)
diff --git a/applets/appmenu/CMakeLists.txt b/applets/appmenu/CMakeLists.txt
new file mode 100644
index 00000000..731013ba
--- /dev/null
+++ b/applets/appmenu/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_subdirectory(lib)
+add_subdirectory(plugin)
+
+plasma_install_package(package org.kde.plasma.appmenu)
diff --git a/applets/appmenu/Messages.sh b/applets/appmenu/Messages.sh
new file mode 100755
index 00000000..89af6d0c
--- /dev/null
+++ b/applets/appmenu/Messages.sh
@@ -0,0 +1,4 @@
+#! /usr/bin/env bash
+$EXTRACTRC `find . -name \*.rc -o -name \*.ui -o -name \*.kcfg` >> rc.cpp
+$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.plasma.appmenu.pot
+rm -f rc.cpp
diff --git a/applets/appmenu/lib/CMakeLists.txt b/applets/appmenu/lib/CMakeLists.txt
new file mode 100644
index 00000000..2947b04b
--- /dev/null
+++ b/applets/appmenu/lib/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(appmenuapplet_SRCS
+ appmenuapplet.cpp
+)
+
+add_library(plasma_applet_appmenu MODULE ${appmenuapplet_SRCS})
+
+kcoreaddons_desktop_to_json(plasma_applet_appmenu ../package/metadata.desktop)
+
+target_link_libraries(plasma_applet_appmenu
+ Qt5::Widgets
+ Qt5::Quick
+ KF5::Plasma
+ KF5::WindowSystem)
+
+install(TARGETS plasma_applet_appmenu DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/applets)
diff --git a/applets/appmenu/lib/appmenuapplet.cpp b/applets/appmenu/lib/appmenuapplet.cpp
new file mode 100644
index 00000000..d70af0d7
--- /dev/null
+++ b/applets/appmenu/lib/appmenuapplet.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "appmenuapplet.h"
+#include "../plugin/appmenumodel.h"
+
+#include <QAction>
+#include <QKeyEvent>
+#include <QMenu>
+#include <QMouseEvent>
+#include <QQuickItem>
+#include <QQuickWindow>
+#include <QScreen>
+
+AppMenuApplet::AppMenuApplet(QObject *parent, const QVariantList &data)
+ : Plasma::Applet(parent, data)
+{
+}
+
+AppMenuApplet::~AppMenuApplet() = default;
+
+void AppMenuApplet::init()
+{
+ // TODO Wayland PlasmaShellSurface stuff
+}
+
+AppMenuModel *AppMenuApplet::model() const
+{
+ return m_model;
+}
+
+void AppMenuApplet::setModel(AppMenuModel *model)
+{
+ if (m_model != model) {
+ m_model = model;
+ emit modelChanged();
+ }
+}
+
+int AppMenuApplet::view() const
+{
+ return m_viewType;
+}
+
+void AppMenuApplet::setView(int type)
+{
+ if (m_viewType != type) {
+ m_viewType = type;
+ emit viewChanged();
+ }
+}
+
+int AppMenuApplet::currentIndex() const
+{
+ return m_currentIndex;
+}
+
+void AppMenuApplet::setCurrentIndex(int currentIndex)
+{
+ if (m_currentIndex != currentIndex) {
+ m_currentIndex = currentIndex;
+ emit currentIndexChanged();
+ }
+}
+
+QQuickItem *AppMenuApplet::buttonGrid() const
+{
+ return m_buttonGrid;
+}
+
+void AppMenuApplet::setButtonGrid(QQuickItem *buttonGrid)
+{
+ if (m_buttonGrid != buttonGrid) {
+ m_buttonGrid = buttonGrid;
+ emit buttonGridChanged();
+ }
+}
+
+QMenu *AppMenuApplet::createMenu(int idx) const
+{
+ QMenu *menu = nullptr;
+ QAction *action = nullptr;
+
+ if (view() == CompactView) {
+ menu = new QMenu();
+ for (int i=0; i<m_model->rowCount(); i++) {
+ const QModelIndex index = m_model->index(i, 0);
+ const QVariant data = m_model->data(index, AppMenuModel::ActionRole);
+ action = (QAction *)data.value<void *>();
+ menu->addAction(action);
+ }
+ } else if (view() == FullView) {
+ const QModelIndex index = m_model->index(idx, 0);
+ const QVariant data = m_model->data(index, AppMenuModel::ActionRole);
+ action = (QAction *)data.value<void *>();
+ if (action) {
+ menu = action->menu();
+ }
+ }
+
+ return menu;
+}
+
+void AppMenuApplet::onMenuAboutToHide()
+{
+ setCurrentIndex(-1);
+}
+
+void AppMenuApplet::trigger(QQuickItem *ctx, int idx)
+{
+ QMenu *actionMenu = createMenu(idx);
+
+ if (actionMenu) {
+ if (m_currentIndex == idx) {
+ return;
+ }
+
+ if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) {
+ // FIXME event forge thing enters press and hold move mode :/
+ ctx->window()->mouseGrabberItem()->ungrabMouse();
+ }
+
+ const auto &geo = ctx->window()->screen()->availableVirtualGeometry();
+
+ QPoint pos = ctx->mapToGlobal(QPointF(0, 0)).toPoint();
+ if (location() == Plasma::Types::TopEdge) {
+ pos.setY(pos.y() + ctx->height());
+ }
+
+ actionMenu->adjustSize();
+
+ pos = QPoint(qBound(geo.x(), pos.x(), geo.x() + geo.width() - actionMenu->width()),
+ qBound(geo.y(), pos.y(), geo.y() + geo.height() - actionMenu->height()));
+
+ if (view() == FullView) {
+ actionMenu->installEventFilter(this);
+ }
+
+ 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) {
+ oldMenu->hide();
+ }
+ }
+
+ setCurrentIndex(idx);
+
+ // FIXME TODO connect only once
+ connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection);
+ return;
+ }
+}
+
+// FIXME TODO doesn't work on submenu
+bool AppMenuApplet::eventFilter(QObject *watched, QEvent *event)
+{
+ auto *menu = qobject_cast<QMenu *>(watched);
+ if (!menu) {
+ return false;
+ }
+
+ if (event->type() == QEvent::KeyPress) {
+ auto *e = static_cast<QKeyEvent *>(event);
+
+ // TODO right to left languages
+ if (e->key() == Qt::Key_Left) {
+ int desiredIndex = m_currentIndex - 1;
+ emit requestActivateIndex(desiredIndex);
+ return true;
+ } else if (e->key() == Qt::Key_Right) {
+ if (menu->activeAction() && menu->activeAction()->menu()) {
+ return false;
+ }
+
+ int desiredIndex = m_currentIndex + 1;
+ emit requestActivateIndex(desiredIndex);
+ return true;
+ }
+
+ } else if (event->type() == QEvent::MouseMove) {
+ auto *e = static_cast<QMouseEvent *>(event);
+
+ if (!m_buttonGrid) {
+ return false;
+ }
+
+ // FIXME the panel margin breaks Fitt's law :(
+ const QPointF &localPos = m_buttonGrid->mapFromGlobal(e->globalPos());
+ auto *item = m_buttonGrid->childAt(localPos.x(), localPos.y());
+ if (!item) {
+ return false;
+ }
+
+ bool ok;
+ const int buttonIndex = item->property("buttonIndex").toInt(&ok);
+ if (!ok) {
+ return false;
+ }
+
+ emit requestActivateIndex(buttonIndex);
+ }
+
+ return false;
+}
+
+K_EXPORT_PLASMA_APPLET_WITH_JSON(appmenu, AppMenuApplet, "metadata.json")
+
+#include "appmenuapplet.moc"
diff --git a/applets/appmenu/lib/appmenuapplet.h b/applets/appmenu/lib/appmenuapplet.h
new file mode 100644
index 00000000..85296720
--- /dev/null
+++ b/applets/appmenu/lib/appmenuapplet.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <Plasma/Applet>
+#include <QPointer>
+
+class KDBusMenuImporter;
+class QQuickItem;
+class QMenu;
+class AppMenuModel;
+
+class AppMenuApplet : public Plasma::Applet
+{
+ Q_OBJECT
+
+ Q_PROPERTY(AppMenuModel* model READ model WRITE setModel NOTIFY modelChanged)
+
+ Q_PROPERTY(int view READ view WRITE setView NOTIFY viewChanged)
+
+ Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged)
+
+ Q_PROPERTY(QQuickItem *buttonGrid READ buttonGrid WRITE setButtonGrid NOTIFY buttonGridChanged)
+
+public:
+ enum ViewType {
+ FullView,
+ CompactView
+ };
+
+ explicit AppMenuApplet(QObject *parent, const QVariantList &data);
+ ~AppMenuApplet() override;
+
+ void init() override;
+
+ int currentIndex() const;
+
+ QQuickItem *buttonGrid() const;
+ void setButtonGrid(QQuickItem *buttonGrid);
+
+ AppMenuModel *model() const;
+ void setModel(AppMenuModel *model);
+
+ int view() const;
+ void setView(int type);
+
+ Q_INVOKABLE void trigger(QQuickItem *ctx, int idx);
+
+signals:
+ void modelChanged();
+ void viewChanged();
+ void currentIndexChanged();
+ void buttonGridChanged();
+ void requestActivateIndex(int index);
+
+protected:
+ bool eventFilter(QObject *watched, QEvent *event);
+
+private:
+ QMenu *createMenu(int idx) const;
+ void setCurrentIndex(int currentIndex);
+ void onMenuAboutToHide();
+
+
+ int m_currentIndex = -1;
+ int m_viewType = FullView;
+ QPointer<QMenu> m_currentMenu;
+ QPointer<QQuickItem> m_buttonGrid;
+ QPointer<AppMenuModel> m_model;
+};
diff --git a/applets/appmenu/package/contents/config/config.qml b/applets/appmenu/package/contents/config/config.qml
new file mode 100644
index 00000000..de944030
--- /dev/null
+++ b/applets/appmenu/package/contents/config/config.qml
@@ -0,0 +1,32 @@
+/******************************************************************
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************/
+
+import QtQuick 2.0
+
+import org.kde.plasma.configuration 2.0
+
+ConfigModel {
+ ConfigCategory {
+ name: i18n("Appearance")
+ icon: "plasma"
+ source: "configGeneral.qml"
+ }
+}
diff --git a/applets/appmenu/package/contents/config/main.xml b/applets/appmenu/package/contents/config/main.xml
new file mode 100644
index 00000000..4891308e
--- /dev/null
+++ b/applets/appmenu/package/contents/config/main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name=""/>
+
+ <group name="Appearance">
+ <entry name="compactView" type="Bool">
+ <label>If true it only shows a button for the application menu.</label>
+ <default>false</default>
+ </entry>
+ </group>
+
+</kcfg>
diff --git a/applets/appmenu/package/contents/ui/configGeneral.qml b/applets/appmenu/package/contents/ui/configGeneral.qml
new file mode 100644
index 00000000..93de158b
--- /dev/null
+++ b/applets/appmenu/package/contents/ui/configGeneral.qml
@@ -0,0 +1,52 @@
+/********************************************************************
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **********************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Controls 1.0 as Controls
+import QtQuick.Layouts 1.1 as Layouts
+
+import org.kde.plasma.core 2.0 as PlasmaCore
+
+Layouts.ColumnLayout {
+ id: configGeneral
+
+ property alias cfg_compactView: compactViewCheckBox.checked
+
+ Controls.ExclusiveGroup {
+ id: viewOptionGroup
+ }
+
+ Controls.RadioButton {
+ id: compactViewCheckBox
+ text: i18n("Use single button for application menu")
+ exclusiveGroup: viewOptionGroup
+ }
+ Controls.RadioButton {
+ //this checked binding is just for the initial load in case
+ //compactViewCheckBox is not checked. Then exclusive group manages it
+ checked: !compactViewCheckBox.checked
+ text: i18n("Show full application menu")
+ exclusiveGroup: viewOptionGroup
+ }
+
+ Item {
+ Layouts.Layout.fillHeight: true
+ }
+}
diff --git a/applets/appmenu/package/contents/ui/main.qml b/applets/appmenu/package/contents/ui/main.qml
new file mode 100644
index 00000000..e24682ac
--- /dev/null
+++ b/applets/appmenu/package/contents/ui/main.qml
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 Heena Mahour <heena393@gmail.com>
+ * Copyright 2013 Sebastian Kügler <sebas@kde.org>
+ * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+import QtQuick 2.0
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 1.4
+
+import org.kde.plasma.plasmoid 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.plasma.private.appmenu 1.0 as AppMenuPrivate
+
+Item {
+ id: root
+
+ readonly property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Vertical
+
+ readonly property bool compactView: plasmoid.configuration.compactView
+
+ onCompactViewChanged: {
+ plasmoid.nativeInterface.view = compactView
+ }
+
+ Layout.minimumWidth: units.gridUnit
+ Layout.minimumHeight: units.gridUnit
+
+ Plasmoid.preferredRepresentation: plasmoid.configuration.compactView ? Plasmoid.compactRepresentation : Plasmoid.fullRepresentation
+
+ Plasmoid.compactRepresentation: PlasmaComponents.ToolButton {
+ Layout.fillWidth: false
+ Layout.fillHeight: false
+ Layout.minimumWidth: implicitWidth
+ Layout.maximumWidth: implicitWidth
+ text: i18n("Menu")
+ onClicked: {
+ plasmoid.nativeInterface.trigger(this, 0);
+ }
+ }
+
+ Plasmoid.fullRepresentation: GridLayout {
+ id: buttonGrid
+ Layout.fillWidth: !root.vertical
+ Layout.fillHeight: root.vertical
+ flow: root.vertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
+ rowSpacing: units.smallSpacing
+ columnSpacing: units.smallSpacing
+
+ Component.onCompleted: {
+ plasmoid.nativeInterface.buttonGrid = buttonGrid
+ }
+
+ Connections {
+ target: plasmoid.nativeInterface
+ onRequestActivateIndex: {
+ var idx = Math.max(0, Math.min(buttonRepeater.count - 1, index))
+ var button = buttonRepeater.itemAt(index)
+ if (button) {
+ button.clicked()
+ }
+ }
+ }
+
+ Repeater {
+ id: buttonRepeater
+ model: appMenuModel
+
+ PlasmaComponents.ToolButton {
+ readonly property int buttonIndex: index
+
+ Layout.preferredWidth: minimumWidth
+ Layout.fillWidth: root.vertical
+ Layout.fillHeight: !root.vertical
+ text: activeMenu
+ // fake highlighted
+ checkable: plasmoid.nativeInterface.currentIndex === index
+ checked: checkable
+ onClicked: {
+ plasmoid.nativeInterface.trigger(this, index)
+ }
+ }
+ }
+ }
+
+ AppMenuPrivate.AppMenuModel {
+ id: appMenuModel
+ Component.onCompleted: {
+ plasmoid.nativeInterface.model = appMenuModel
+ }
+ }
+
+ Connections {
+ target: appMenuModel
+ onResetModel: {
+ plasmoid.nativeInterface.model = appMenuModel
+ }
+ }
+}
diff --git a/applets/appmenu/package/metadata.desktop b/applets/appmenu/package/metadata.desktop
new file mode 100644
index 00000000..01a71591
--- /dev/null
+++ b/applets/appmenu/package/metadata.desktop
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=Application Menu
+Icon=show-menu
+Type=Service
+X-KDE-PluginInfo-Author=Kai Uwe Broulik
+X-KDE-PluginInfo-Email=kde@privat.broulik.de
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-Name=org.kde.plasma.appmenu
+X-KDE-PluginInfo-Version=2.0
+X-KDE-PluginInfo-Website=plasma.kde.org
+X-KDE-ServiceTypes=Plasma/Applet
+X-Plasma-API=declarativeappletscript
+X-KDE-Library=plasma_applet_appmenu
+
+X-Plasma-MainScript=ui/main.qml
+X-KDE-PluginInfo-Category=Windows and Tasks
diff --git a/applets/appmenu/plugin/CMakeLists.txt b/applets/appmenu/plugin/CMakeLists.txt
new file mode 100644
index 00000000..a223acbd
--- /dev/null
+++ b/applets/appmenu/plugin/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(appmenuapplet_SRCS
+ appmenumodel.cpp
+ appmenuplugin.cpp
+)
+
+add_library(appmenuplugin SHARED ${appmenuapplet_SRCS})
+target_link_libraries(appmenuplugin
+ Qt5::Core
+ Qt5::Widgets
+ Qt5::Quick
+ KF5::Plasma
+ KF5::WindowSystem
+ dbusmenuqt)
+
+if(HAVE_X11)
+ target_link_libraries(appmenuplugin Qt5::X11Extras XCB::XCB)
+endif()
+
+install(TARGETS appmenuplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/appmenu)
+
+install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/appmenu)
diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp
new file mode 100644
index 00000000..cf0681c1
--- /dev/null
+++ b/applets/appmenu/plugin/appmenumodel.cpp
@@ -0,0 +1,225 @@
+/******************************************************************
+ * Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************/
+
+#include "appmenumodel.h"
+
+#include <config-X11.h>
+
+#if HAVE_X11
+#include <QX11Info>
+#include <xcb/xcb.h>
+#endif
+
+#include <QAction>
+#include <QMenu>
+
+#include <dbusmenuimporter.h>
+
+static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
+static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
+
+class KDBusMenuImporter : public DBusMenuImporter
+{
+
+public:
+ KDBusMenuImporter(const QString &service, const QString &path, QObject *parent)
+ : DBusMenuImporter(service, path, parent)
+ {
+
+ }
+
+protected:
+ QIcon iconForName(const QString &name) override
+ {
+ return QIcon::fromTheme(name);
+ }
+
+};
+
+AppMenuModel::AppMenuModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged);
+ connect(this, &AppMenuModel::modelNeedsUpdate, this, &AppMenuModel::update, Qt::UniqueConnection);
+ onActiveWindowChanged(KWindowSystem::activeWindow());
+}
+
+AppMenuModel::~AppMenuModel() = default;
+
+int AppMenuModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return m_activeMenu.count();
+}
+
+void AppMenuModel::update()
+{
+ beginResetModel();
+ if (!m_activeMenu.isEmpty() && !m_activeActions.isEmpty()) {
+ m_activeMenu.clear();
+ m_activeActions.clear();
+ }
+
+ if (m_menu && m_winHasMenu) {
+ const auto &actions = m_menu->actions();
+ for (QAction *action : actions) {
+ m_activeActions.append(action);
+ m_activeMenu.append(action->text());
+ }
+ }
+
+ endResetModel();
+}
+
+
+void AppMenuModel::onActiveWindowChanged(WId id)
+{
+#if HAVE_X11
+ if (KWindowSystem::isPlatformX11()) {
+ auto *c = QX11Info::connection();
+
+ static QHash<QByteArray, xcb_atom_t> s_atoms;
+
+ auto getWindowPropertyString = [c, this](WId id, const QByteArray &name) -> QByteArray {
+ QByteArray value;
+ if (!s_atoms.contains(name)) {
+ const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData());
+ QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, Q_NULLPTR));
+ if (atomReply.isNull()) {
+ return value;
+ }
+
+ s_atoms[name] = atomReply->atom;
+ if (s_atoms[name] == XCB_ATOM_NONE) {
+ return value;
+ }
+ }
+
+ static const long MAX_PROP_SIZE = 10000;
+ auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE);
+ QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, NULL));
+ if (propertyReply.isNull()) {
+ return value;
+ }
+
+ if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) {
+ const char *data = (const char *) xcb_get_property_value(propertyReply.data());
+ int len = propertyReply->value_len;
+ if (data) {
+ value = QByteArray(data, data[len - 1] ? len : len - 1);
+ }
+ }
+
+ return value;
+ };
+
+ auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) {
+ const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName));
+ const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName));
+
+ if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) {
+ updateApplicationMenu(serviceName, menuObjectPath);
+ return true;
+ }
+ m_winHasMenu = false;
+ emit modelNeedsUpdate();
+ return false;
+ };
+
+ KWindowInfo info(id, NET::WMState, NET::WM2TransientFor);
+ if (info.hasState(NET::SkipTaskbar) || info.windowType(NET::UtilityMask) == NET::Utility) {
+ return;
+ }
+
+ WId transientId = info.transientFor();
+ // lok at transient windows first
+ while (transientId) {
+ if (updateMenuFromWindowIfHasMenu(transientId)) {
+ return;
+ }
+ transientId = KWindowInfo(transientId, 0, NET::WM2TransientFor).transientFor();
+ }
+
+ if (updateMenuFromWindowIfHasMenu(id)) {
+ return;
+ }
+ }
+#endif
+
+}
+
+
+QHash<int, QByteArray> AppMenuModel::roleNames() const
+{
+ QHash<int, QByteArray> roleNames;
+ roleNames[MenuRole] = "activeMenu";
+ roleNames[ActionRole] = "activeActions";
+ return roleNames;
+}
+
+QVariant AppMenuModel::data(const QModelIndex &index, int role) const
+{
+ int row = index.row();
+ if (row < 0 ) {
+ return QVariant();
+ }
+
+ if (role == MenuRole) {
+ return m_activeMenu.at(row);
+ } else if(role == ActionRole) {
+ const QVariant data = qVariantFromValue((void *) m_activeActions.at(row));
+ return data;
+ }
+
+ return QVariant();
+}
+
+void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath)
+{
+ if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) {
+ if (m_importer) {
+ QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);
+ }
+ return;
+ }
+
+ m_serviceName = serviceName;
+ m_menuObjectPath = menuObjectPath;
+
+ if (m_importer) {
+ m_importer->deleteLater();
+ }
+
+ m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this);
+ QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);
+
+ connect(m_importer, &DBusMenuImporter::menuUpdated, this, [=] {
+ m_menu = m_importer->menu();
+ if (m_menu.isNull()) {
+ return;
+ }
+ m_winHasMenu = true;
+ emit modelNeedsUpdate();
+ });
+
+}
diff --git a/applets/appmenu/plugin/appmenumodel.h b/applets/appmenu/plugin/appmenumodel.h
new file mode 100644
index 00000000..581cf483
--- /dev/null
+++ b/applets/appmenu/plugin/appmenumodel.h
@@ -0,0 +1,71 @@
+/******************************************************************
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************/
+
+#include <QAbstractListModel>
+#include <QStringList>
+#include <KWindowSystem>
+#include <QPointer>
+
+class QMenu;
+class QAction;
+class QModelIndex;
+class KDBusMenuImporter;
+
+class AppMenuModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ explicit AppMenuModel(QObject *parent = 0);
+ ~AppMenuModel();
+
+ enum AppMenuRole {
+ MenuRole = Qt::UserRole+1,
+ ActionRole
+ };
+
+ QVariant data(const QModelIndex &index, int role) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QHash<int, QByteArray> roleNames() const;
+
+ void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath);
+
+private Q_SLOTS:
+ void onActiveWindowChanged(WId id);
+ void update();
+
+signals:
+ void modelNeedsUpdate();
+
+
+private:
+ bool m_winHasMenu;
+
+ QPointer<QMenu> m_menu;
+ QStringList m_activeMenu;
+ QList<QAction *> m_activeActions;
+
+ QString m_serviceName;
+ QString m_menuObjectPath;
+
+ QPointer<KDBusMenuImporter> m_importer;
+};
+
diff --git a/applets/appmenu/plugin/appmenuplugin.cpp b/applets/appmenu/plugin/appmenuplugin.cpp
new file mode 100644
index 00000000..f3fa25c0
--- /dev/null
+++ b/applets/appmenu/plugin/appmenuplugin.cpp
@@ -0,0 +1,31 @@
+/******************************************************************
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************/
+
+#include "appmenuplugin.h"
+#include "appmenumodel.h"
+
+#include <QQmlEngine>
+
+void AppmenuPlugin::registerTypes(const char *uri)
+{
+ Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.appmenu"));
+ qmlRegisterType<AppMenuModel>(uri, 1, 0, "AppMenuModel");
+}
diff --git a/applets/appmenu/plugin/appmenuplugin.h b/applets/appmenu/plugin/appmenuplugin.h
new file mode 100644
index 00000000..583cec8d
--- /dev/null
+++ b/applets/appmenu/plugin/appmenuplugin.h
@@ -0,0 +1,37 @@
+/******************************************************************
+ * Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************/
+
+#ifndef APPMENUPLUGIN_H
+#define APPMENUPLUGIN_H
+
+#include <QQmlExtensionPlugin>
+
+class AppmenuPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+ void registerTypes(const char *uri) override;
+};
+
+#endif
+
diff --git a/applets/appmenu/plugin/qmldir b/applets/appmenu/plugin/qmldir
new file mode 100644
index 00000000..b5094ba1
--- /dev/null
+++ b/applets/appmenu/plugin/qmldir
@@ -0,0 +1,3 @@
+module org.kde.plasma.private.appmenu
+
+plugin appmenuplugin
--
2.12.0