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
parent
109b0882da
commit
5b8380b604
17 changed files with 961 additions and 0 deletions
@ -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…
Reference in new issue