Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:42.3
plasma5-workspace
0013-Global-Menu-Applet.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor