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
wilder-5.14
David Edmundson 9 years ago
parent 109b0882da
commit 5b8380b604
  1. 1
      applets/CMakeLists.txt
  2. 4
      applets/appmenu/CMakeLists.txt
  3. 4
      applets/appmenu/Messages.sh
  4. 15
      applets/appmenu/lib/CMakeLists.txt
  5. 231
      applets/appmenu/lib/appmenuapplet.cpp
  6. 89
      applets/appmenu/lib/appmenuapplet.h
  7. 32
      applets/appmenu/package/contents/config/config.qml
  8. 15
      applets/appmenu/package/contents/config/main.xml
  9. 52
      applets/appmenu/package/contents/ui/configGeneral.qml
  10. 113
      applets/appmenu/package/contents/ui/main.qml
  11. 17
      applets/appmenu/package/metadata.desktop
  12. 21
      applets/appmenu/plugin/CMakeLists.txt
  13. 225
      applets/appmenu/plugin/appmenumodel.cpp
  14. 71
      applets/appmenu/plugin/appmenumodel.h
  15. 31
      applets/appmenu/plugin/appmenuplugin.cpp
  16. 37
      applets/appmenu/plugin/appmenuplugin.h
  17. 3
      applets/appmenu/plugin/qmldir

@ -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)

@ -0,0 +1,4 @@
add_subdirectory(lib)
add_subdirectory(plugin)
plasma_install_package(package org.kde.plasma.appmenu)

@ -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

@ -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)

@ -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"

@ -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;
};

@ -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"
}
}

@ -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>

@ -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
}
}

@ -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
}
}
}

@ -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

@ -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)

@ -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();
});
}

@ -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;
};

@ -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");
}

@ -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

@ -0,0 +1,3 @@
module org.kde.plasma.private.appmenu
plugin appmenuplugin
Loading…
Cancel
Save