diff --git a/applets/CMakeLists.txt b/applets/CMakeLists.txt index b404fa256..956105e50 100644 --- a/applets/CMakeLists.txt +++ b/applets/CMakeLists.txt @@ -1,21 +1,22 @@ plasma_install_package(activitybar org.kde.plasma.activitybar) add_subdirectory(icon) plasma_install_package(analog-clock org.kde.plasma.analogclock) 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) add_subdirectory(devicenotifier) add_subdirectory(digital-clock) plasma_install_package(clipboard org.kde.plasma.clipboard) if(NOT WIN32) # #notifications # #should compile also on windows? (even if doesn't really make sense) add_subdirectory(notifications) add_subdirectory(systemtray) endif() diff --git a/applets/appmenu/CMakeLists.txt b/applets/appmenu/CMakeLists.txt new file mode 100644 index 000000000..731013ba7 --- /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 000000000..89af6d0c2 --- /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 000000000..2947b04b7 --- /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 000000000..d70af0d76 --- /dev/null +++ b/applets/appmenu/lib/appmenuapplet.cpp @@ -0,0 +1,231 @@ +/* + * Copyright 2016 Kai Uwe Broulik + * + * 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 . + * + */ + +#include "appmenuapplet.h" +#include "../plugin/appmenumodel.h" + +#include +#include +#include +#include +#include +#include +#include + +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; irowCount(); i++) { + const QModelIndex index = m_model->index(i, 0); + const QVariant data = m_model->data(index, AppMenuModel::ActionRole); + action = (QAction *)data.value(); + 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(); + 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(watched); + if (!menu) { + return false; + } + + if (event->type() == QEvent::KeyPress) { + auto *e = static_cast(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(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 000000000..852967200 --- /dev/null +++ b/applets/appmenu/lib/appmenuapplet.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Kai Uwe Broulik + * + * 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 . + * + */ + +#pragma once + +#include +#include + +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 m_currentMenu; + QPointer m_buttonGrid; + QPointer 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 000000000..de9440301 --- /dev/null +++ b/applets/appmenu/package/contents/config/config.qml @@ -0,0 +1,32 @@ +/****************************************************************** + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * 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 . + * + ******************************************************************/ + +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 000000000..4891308ea --- /dev/null +++ b/applets/appmenu/package/contents/config/main.xml @@ -0,0 +1,15 @@ + + + + + + + + false + + + + diff --git a/applets/appmenu/package/contents/ui/configGeneral.qml b/applets/appmenu/package/contents/ui/configGeneral.qml new file mode 100644 index 000000000..93de158b9 --- /dev/null +++ b/applets/appmenu/package/contents/ui/configGeneral.qml @@ -0,0 +1,52 @@ +/******************************************************************** + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * 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 . + **********************************************************************/ + +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 000000000..e24682ac8 --- /dev/null +++ b/applets/appmenu/package/contents/ui/main.qml @@ -0,0 +1,113 @@ +/* + * Copyright 2013 Heena Mahour + * Copyright 2013 Sebastian Kügler + * Copyright 2016 Kai Uwe Broulik + * + * 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 . + */ +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 000000000..01a71591b --- /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 000000000..a223acbdd --- /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 000000000..cf0681c14 --- /dev/null +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -0,0 +1,225 @@ +/****************************************************************** + * Copyright 2016 Kai Uwe Broulik + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * + * 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 . + * + ******************************************************************/ + +#include "appmenumodel.h" + +#include + +#if HAVE_X11 +#include +#include +#endif + +#include +#include + +#include + +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 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 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 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 AppMenuModel::roleNames() const +{ + QHash 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 000000000..581cf483c --- /dev/null +++ b/applets/appmenu/plugin/appmenumodel.h @@ -0,0 +1,71 @@ +/****************************************************************** + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * 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 . + * + ******************************************************************/ + +#include +#include +#include +#include + +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 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 m_menu; + QStringList m_activeMenu; + QList m_activeActions; + + QString m_serviceName; + QString m_menuObjectPath; + + QPointer m_importer; +}; + diff --git a/applets/appmenu/plugin/appmenuplugin.cpp b/applets/appmenu/plugin/appmenuplugin.cpp new file mode 100644 index 000000000..f3fa25c00 --- /dev/null +++ b/applets/appmenu/plugin/appmenuplugin.cpp @@ -0,0 +1,31 @@ +/****************************************************************** + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * 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 . + * + ******************************************************************/ + +#include "appmenuplugin.h" +#include "appmenumodel.h" + +#include + +void AppmenuPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.appmenu")); + qmlRegisterType(uri, 1, 0, "AppMenuModel"); +} diff --git a/applets/appmenu/plugin/appmenuplugin.h b/applets/appmenu/plugin/appmenuplugin.h new file mode 100644 index 000000000..583cec8d3 --- /dev/null +++ b/applets/appmenu/plugin/appmenuplugin.h @@ -0,0 +1,37 @@ +/****************************************************************** + * Copyright 2016 Chinmoy Ranjan Pradhan + * + * 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 . + * + ******************************************************************/ + +#ifndef APPMENUPLUGIN_H +#define APPMENUPLUGIN_H + +#include + +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 000000000..b5094ba13 --- /dev/null +++ b/applets/appmenu/plugin/qmldir @@ -0,0 +1,3 @@ +module org.kde.plasma.private.appmenu + +plugin appmenuplugin