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

openSUSE Build Service is sponsored by