import plasma code and try to work independent

This commit is contained in:
Michail Vourlakos committed 2018-11-16 13:48:40 +02:00
1 parent 13ec9ffbe5
commit e6fcdebc3f
32 files changed
+2647

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
*~
.*
!.arcconfig
!.gitignore
# Ignore hidden files
.directory
# build directory
build/
# backup files
*.autosave
*.desktop~
*.gitignore~
*.js~
*.kate-swp
*.qml~
*.sh~
*.xml~
.*.cpp~
.*.h~
.*.js~
.*.pot~
.*.po~
.*.qml~
.*.txt~
.*.xml~
.*~
*.*.user
*.*.orig
# locale files
*.mo
# KDevelop Project files
*.kdev4
.kdev4/
build-kdev/
+30
View File
@@ -0,0 +1,30 @@
project(applet_window_appmenu)
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
set(CMAKE_CXX_STANDARD 14)
set(VERSION 0.1.0)
set(AUTHOR "Michail Vourlakos")
set(EMAIL "mvourlakos@gmail.com")
set(QT_MIN_VERSION "5.9.0")
set(KF5_MIN_VERSION "5.38.0")
set(KF5_LOCALE_PREFIX "")
find_package(ECM ${KF5_MIN_VER} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Quick Widgets DBus)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Plasma WindowSystem)
include(ECMQtDeclareLoggingCategory)
include(KDECMakeSettings)
include(KDECompilerSettings)
include(KDEInstallDirs)
add_subdirectory(lib)
add_subdirectory(plugin)
plasma_install_package(package org.kde.windowappmenu)
+7
View File
@@ -0,0 +1,7 @@
if ! [ -a build ] ; then
mkdir build
fi
cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr .. -Wnodev
make
sudo make install
+16
View File
@@ -0,0 +1,16 @@
set(appmenuapplet_SRCS
appmenuapplet.cpp
)
add_library(plasma_applet_windowappmenu MODULE ${appmenuapplet_SRCS})
kcoreaddons_desktop_to_json(plasma_applet_windowappmenu ../package/metadata.desktop)
target_link_libraries(plasma_applet_windowappmenu
Qt5::Widgets
Qt5::Quick
Qt5::DBus
KF5::Plasma
KF5::WindowSystem)
install(TARGETS plasma_applet_windowappmenu DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets)
+308
View File
@@ -0,0 +1,308 @@
/*
* 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 <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QTimer>
#include <QQuickWindow>
#include <QScreen>
int AppMenuApplet::s_refs = 0;
namespace {
QString viewService() { return QStringLiteral("org.kde.kappmenuview"); }
}
AppMenuApplet::AppMenuApplet(QObject *parent, const QVariantList &data)
: Plasma::Applet(parent, data)
{
++s_refs;
//if we're the first, regster the service
if (s_refs == 1) {
QDBusConnection::sessionBus().interface()->registerService(viewService(),
QDBusConnectionInterface::QueueService,
QDBusConnectionInterface::DontAllowReplacement);
}
/*it registers or unregisters the service when the destroyed value of the applet change,
and not in the dtor, because:
when we "delete" an applet, it just hides it for about a minute setting its status
to destroyed, in order to be able to do a clean undo: if we undo, there will be
another destroyedchanged and destroyed will be false.
When this happens, if we are the only appmenu applet existing, the dbus interface
will have to be registered again*/
connect(this, &Applet::destroyedChanged, this, [this](bool destroyed) {
if (destroyed) {
//if we were the last, unregister
if (--s_refs == 0) {
QDBusConnection::sessionBus().interface()->unregisterService(viewService());
}
} else {
//if we're the first, regster the service
if (++s_refs == 1) {
QDBusConnection::sessionBus().interface()->registerService(viewService(),
QDBusConnectionInterface::QueueService,
QDBusConnectionInterface::DontAllowReplacement);
}
}
});
}
AppMenuApplet::~AppMenuApplet() = default;
void AppMenuApplet::init()
{
}
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);
}
menu->setAttribute(Qt::WA_DeleteOnClose);
} 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)
{
if (m_currentIndex == idx) {
return;
}
if (!ctx || !ctx->window() || !ctx->window()->screen()) {
return;
}
QMenu *actionMenu = createMenu(idx);
if (actionMenu) {
//this is a workaround where Qt will fail to realize a mouse has been released
// this happens if a window which does not accept focus spawns a new window that takes focus and X grab
// whilst the mouse is depressed
// https://bugreports.qt.io/browse/QTBUG-59044
// this causes the next click to go missing
//by releasing manually we avoid that situation
auto ungrabMouseHack = [ctx]() {
if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) {
// FIXME event forge thing enters press and hold move mode :/
ctx->window()->mouseGrabberItem()->ungrabMouse();
}
};
QTimer::singleShot(0, ctx, ungrabMouseHack);
//end workaround
const auto &geo = ctx->window()->screen()->availableVirtualGeometry();
QPoint pos = ctx->window()->mapToGlobal(ctx->mapToScene(QPointF()).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->winId();//create window handle
actionMenu->windowHandle()->setTransientParent(ctx->window());
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) {
//don't initialize the currentIndex when another menu is already shown
disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide);
oldMenu->hide();
}
}
setCurrentIndex(idx);
// FIXME TODO connect only once
connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection);
} else { // is it just an action without a menu?
const QVariant data = m_model->index(idx, 0).data(AppMenuModel::ActionRole);
QAction *action = static_cast<QAction *>(data.value<void *>());
if (action) {
Q_ASSERT(!action->menu());
action->trigger();
}
}
}
// 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 || !m_buttonGrid->window()) {
return false;
}
// FIXME the panel margin breaks Fitt's law :(
const QPointF &windowLocalPos = m_buttonGrid->window()->mapFromGlobal(e->globalPos());
const QPointF &buttonGridLocalPos = m_buttonGrid->mapFromScene(windowLocalPos);
auto *item = m_buttonGrid->childAt(buttonGridLocalPos.x(), buttonGridLocalPos.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"
+91
View File
@@ -0,0 +1,91 @@
/*
* 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 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);
signals:
void modelChanged();
void viewChanged();
void currentIndexChanged();
void buttonGridChanged();
void requestActivateIndex(int index);
public slots:
void trigger(QQuickItem *ctx, int idx);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
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;
static int s_refs;
};
+32
View File
@@ -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"
}
}
+15
View File
@@ -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>
+58
View File
@@ -0,0 +1,58 @@
/********************************************************************
* 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.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
Layouts.ColumnLayout {
id: configGeneral
property alias cfg_compactView: compactViewRadioButton.checked
property bool disableSetting: plasmoid.formFactor === PlasmaCore.Types.Vertical
Controls.ExclusiveGroup {
id: viewOptionGroup
}
Controls.RadioButton {
id: compactViewRadioButton
enabled: !disableSetting
text: i18n("Use single button for application menu")
exclusiveGroup: viewOptionGroup
}
Controls.RadioButton {
id: fullViewRadioButton
//this checked binding is just for the initial load in case
//compactViewCheckBox is not checked. Then exclusive group manages it
enabled: !disableSetting
checked: !compactViewRadioButton.checked
text: i18n("Show full application menu")
exclusiveGroup: viewOptionGroup
}
Item {
Layouts.Layout.fillHeight: true
}
}
+157
View File
@@ -0,0 +1,157 @@
/*
* 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.kquickcontrolsaddons 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.private.windowAppMenu 0.1 as AppMenuPrivate
Item {
id: root
readonly property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Vertical
readonly property bool view: plasmoid.configuration.compactView
readonly property bool menuAvailable: appMenuModel.menuAvailable
readonly property bool kcmAuthorized: KCMShell.authorize(["style.desktop"]).length > 0
onViewChanged: {
plasmoid.nativeInterface.view = view
}
Plasmoid.preferredRepresentation: (plasmoid.configuration.compactView || vertical) ? Plasmoid.compactRepresentation : Plasmoid.fullRepresentation
Plasmoid.compactRepresentation: PlasmaComponents.ToolButton {
readonly property int fakeIndex: 0
Layout.fillWidth: false
Layout.fillHeight: false
Layout.minimumWidth: implicitWidth
Layout.maximumWidth: implicitWidth
enabled: menuAvailable
checkable: menuAvailable && plasmoid.nativeInterface.currentIndex === fakeIndex
checked: checkable
iconSource: i18n("application-menu")
onClicked: plasmoid.nativeInterface.trigger(this, 0);
}
Plasmoid.fullRepresentation: GridLayout {
id: buttonGrid
Plasmoid.status: {
if (menuAvailable && plasmoid.nativeInterface.currentIndex > -1 && buttonRepeater.count > 0) {
return PlasmaCore.Types.NeedsAttentionStatus;
} else if (menuAvailable){
//when we're not enabled set to active to show the configure button
return buttonRepeater.count > 0 ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.HiddenStatus;
} else {
return PlasmaCore.Types.PassiveStatus;
}
}
Layout.minimumWidth: implicitWidth
Layout.minimumHeight: implicitHeight
flow: root.vertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
rowSpacing: units.smallSpacing
columnSpacing: units.smallSpacing
Component.onCompleted: {
plasmoid.nativeInterface.buttonGrid = buttonGrid
// using a Connections {} doesn't work for some reason in Qt >= 5.8
plasmoid.nativeInterface.requestActivateIndex.connect(function (index) {
var idx = Math.max(0, Math.min(buttonRepeater.count - 1, index))
var button = buttonRepeater.itemAt(index)
if (button) {
button.clicked()
}
});
plasmoid.activated.connect(function () {
var button = buttonRepeater.itemAt(0);
if (button) {
button.clicked();
}
});
}
// So we can show mnemonic underlines only while Alt is pressed
PlasmaCore.DataSource {
id: keystateSource
engine: "keystate"
connectedSources: ["Alt"]
}
Repeater {
id: buttonRepeater
model: appMenuModel
PlasmaComponents.ToolButton {
readonly property int buttonIndex: index
Layout.preferredWidth: minimumWidth
Layout.fillWidth: root.vertical
Layout.fillHeight: !root.vertical
text: {
var text = activeMenu;
var alt = keystateSource.data.Alt;
if (!alt || !alt.Pressed) {
// StyleHelpers.removeMnemonics
text = text.replace(/([^&]*)&(.)([^&]*)/g, function (match, p1, p2, p3) {
return p1.concat(p2, p3);
});
}
return text;
}
// fake highlighted
checkable: plasmoid.nativeInterface.currentIndex === index
checked: checkable
visible: text !== ""
onClicked: {
plasmoid.nativeInterface.trigger(this, index)
checked = Qt.binding(function() {
return plasmoid.nativeInterface.currentIndex === index;
});
}
// QMenu opens on press, so we'll replicate that here
MouseArea {
anchors.fill: parent
onPressed: parent.clicked()
}
}
}
}
AppMenuPrivate.AppMenuModel {
id: appMenuModel
onRequestActivateIndex: plasmoid.nativeInterface.requestActivateIndex(index)
Component.onCompleted: {
plasmoid.nativeInterface.model = appMenuModel
}
}
}
+17
View File
@@ -0,0 +1,17 @@
[Desktop Entry]
Name=Window AppMenu
Comment=Shows the current window menu
Icon=show-menu
Type=Service
X-KDE-PluginInfo-Author=Michail Vourlakos
X-KDE-PluginInfo-Email=mvourlakos@gmail.com
X-KDE-PluginInfo-License=GPL-2.0+
X-KDE-PluginInfo-Name=org.kde.windowappmenu
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Website=https://github.com/psifidotos/applet-window-appmenu
X-KDE-ServiceTypes=Plasma/Applet
X-Plasma-API=declarativeappletscript
X-KDE-Library=plasma_applet_windowappmenu
X-Plasma-MainScript=ui/main.qml
X-KDE-PluginInfo-Category=Windows and Tasks
+52
View File
@@ -0,0 +1,52 @@
set(appmenuapplet_SRCS
appmenumodel.cpp
appmenuplugin.cpp
)
add_library(appmenuplugin SHARED ${appmenuapplet_SRCS})
# load dbusmenuqt
find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets DBus)
cmake_policy(SET CMP0063 NEW) # this is very important otherwise some signals are not recognized
add_subdirectory(libdbusmenuqt)
target_link_libraries(appmenuplugin
Qt5::Core
Qt5::Widgets
Qt5::Quick
KF5::Plasma
KF5::WindowSystem
dbusmenuqt)
# load X11 libraries
find_package(X11)
set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries"
URL "http://www.x.org"
TYPE OPTIONAL
PURPOSE "Required for building the X11 based workspace")
if(X11_FOUND)
find_package(XCB MODULE REQUIRED COMPONENTS XCB RANDR)
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
if(NOT X11_SM_FOUND)
message(FATAL_ERROR "\nThe X11 Session Management (SM) development package could not be found.\nPlease install libSM.\n")
endif(NOT X11_SM_FOUND)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras)
endif()
if(X11_FOUND AND XCB_XCB_FOUND)
set(HAVE_X11 1)
endif()
configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h)
if(HAVE_X11)
find_package(XCB MODULE REQUIRED COMPONENTS XCB)
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
target_link_libraries(appmenuplugin Qt5::X11Extras XCB::XCB)
endif()
install(TARGETS appmenuplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/windowAppMenu)
install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/windowAppMenu)
+343
View File
@@ -0,0 +1,343 @@
/******************************************************************
* 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 <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QGuiApplication>
#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");
#if HAVE_X11
static QHash<QByteArray, xcb_atom_t> s_atoms;
#endif
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),
m_serviceWatcher(new QDBusServiceWatcher(this))
{
connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged);
connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] {
if (!m_updatePending) {
m_updatePending = true;
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}
});
onActiveWindowChanged(KWindowSystem::activeWindow());
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
//if our current DBus connection gets lost, close the menu
//we'll select the new menu when the focus changes
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName)
{
if (serviceName == m_serviceName) {
setMenuAvailable(false);
emit modelNeedsUpdate();
}
});
}
AppMenuModel::~AppMenuModel() = default;
bool AppMenuModel::menuAvailable() const
{
return m_menuAvailable;
}
void AppMenuModel::setMenuAvailable(bool set)
{
if (m_menuAvailable != set) {
m_menuAvailable = set;
emit menuAvailableChanged();
}
}
int AppMenuModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (!m_menuAvailable || !m_menu) {
return 0;
}
return m_menu->actions().count();
}
void AppMenuModel::update()
{
beginResetModel();
endResetModel();
m_updatePending = false;
}
void AppMenuModel::onActiveWindowChanged(WId id)
{
qApp->removeNativeEventFilter(this);
if (!id) {
setMenuAvailable(false);
emit modelNeedsUpdate();
return;
}
#if HAVE_X11
if (KWindowSystem::isPlatformX11()) {
auto *c = QX11Info::connection();
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, 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, nullptr));
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;
}
return false;
};
KWindowInfo info(id, NET::WMState | NET::WMWindowType, NET::WM2TransientFor);
if (info.hasState(NET::SkipTaskbar) ||
info.windowType(NET::UtilityMask) == NET::Utility ||
info.windowType(NET::DesktopMask) == NET::Desktop) {
return;
}
WId transientId = info.transientFor();
// lok at transient windows first
while (transientId) {
if (updateMenuFromWindowIfHasMenu(transientId)) {
return;
}
transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor();
}
if (updateMenuFromWindowIfHasMenu(id)) {
return;
}
// monitor whether an app menu becomes available later
// this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox)
qApp->installNativeEventFilter(this);
m_currentWindowId = id;
//no menu found, set it to unavailable
setMenuAvailable(false);
emit modelNeedsUpdate();
}
#endif
}
QHash<int, QByteArray> AppMenuModel::roleNames() const
{
QHash<int, QByteArray> roleNames;
roleNames[MenuRole] = QByteArrayLiteral("activeMenu");
roleNames[ActionRole] = QByteArrayLiteral("activeActions");
return roleNames;
}
QVariant AppMenuModel::data(const QModelIndex &index, int role) const
{
const int row = index.row();
if (row < 0 || !m_menuAvailable || !m_menu) {
return QVariant();
}
const auto actions = m_menu->actions();
if (row >= actions.count()) {
return QVariant();
}
if (role == MenuRole) { // TODO this should be Qt::DisplayRole
return actions.at(row)->text();
} else if (role == ActionRole) {
return qVariantFromValue((void *) actions.at(row));
}
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_serviceWatcher->setWatchedServices(QStringList({m_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.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) {
m_menu = m_importer->menu();
if (m_menu.isNull() || menu != m_menu) {
return;
}
//cache first layer of sub menus, which we'll be popping up
for(QAction *a: m_menu->actions()) {
// signal dataChanged when the action changes
connect(a, &QAction::changed, this, [this, a] {
if (m_menuAvailable && m_menu) {
const int actionIdx = m_menu->actions().indexOf(a);
if (actionIdx > -1) {
const QModelIndex modelIdx = index(actionIdx, 0);
emit dataChanged(modelIdx, modelIdx);
}
}
});
connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate);
if (a->menu()) {
m_importer->updateMenu(a->menu());
}
}
setMenuAvailable(true);
emit modelNeedsUpdate();
});
connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) {
// TODO submenus
if (!m_menuAvailable || !m_menu) {
return;
}
const auto actions = m_menu->actions();
auto it = std::find(actions.begin(), actions.end(), action);
if (it != actions.end()) {
requestActivateIndex(it - actions.begin());
}
});
}
bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
{
Q_UNUSED(result);
if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") {
return false;
}
#if HAVE_X11
auto e = static_cast<xcb_generic_event_t *>(message);
const uint8_t type = e->response_type & ~0x80;
if (type == XCB_PROPERTY_NOTIFY) {
auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e);
if (event->window == m_currentWindowId) {
auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName);
auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName);
if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen
if (event->atom == serviceNameAtom || event->atom == objectPathAtom) {
// see if we now have a menu
onActiveWindowChanged(KWindowSystem::activeWindow());
}
}
}
}
#else
Q_UNUSED(message);
#endif
return false;
}
+90
View File
@@ -0,0 +1,90 @@
/******************************************************************
* 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 APPMENUMODEL_H
#define APPMENUMODEL_H
#include <QAbstractListModel>
#include <QAbstractNativeEventFilter>
#include <QStringList>
#include <KWindowSystem>
#include <QPointer>
class QMenu;
class QAction;
class QModelIndex;
class QDBusServiceWatcher;
class KDBusMenuImporter;
class AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter
{
Q_OBJECT
Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged)
public:
explicit AppMenuModel(QObject *parent = nullptr);
~AppMenuModel() override;
enum AppMenuRole {
MenuRole = Qt::UserRole+1, // TODO this should be Qt::DisplayRole
ActionRole
};
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath);
bool menuAvailable() const;
void setMenuAvailable(bool set);
signals:
void requestActivateIndex(int index);
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override;
private Q_SLOTS:
void onActiveWindowChanged(WId id);
void update();
signals:
void menuAvailableChanged();
void modelNeedsUpdate();
private:
bool m_menuAvailable;
bool m_updatePending = false;
WId m_currentWindowId = 0;
QPointer<QMenu> m_menu;
QDBusServiceWatcher *m_serviceWatcher;
QString m_serviceName;
QString m_menuObjectPath;
QPointer<KDBusMenuImporter> m_importer;
};
#endif
+31
View File
@@ -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.private.windowAppMenu"));
qmlRegisterType<AppMenuModel>(uri, 0, 1, "AppMenuModel");
}
+37
View File
@@ -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
+44
View File
@@ -0,0 +1,44 @@
/* Define if you have the XRandR extension */
#cmakedefine HAVE_XRANDR 1
/* Define if you have the XDamage extension */
#cmakedefine HAVE_XDAMAGE 1
/* Define if you have the XKB extension */
#cmakedefine HAVE_XKB 1
/* Define if you have the Xinerama extension */
#cmakedefine HAVE_XINERAMA 1
/* Define if you have the XSHM (MIT SHM) extension */
#cmakedefine HAVE_XSHM 1
/* Define if you have the XComposite extension */
#cmakedefine HAVE_XCOMPOSITE 1
/* Define to 1 if you have Xcursor */
#cmakedefine HAVE_XCURSOR 1
/* Define if you have the xf86misc extension */
#cmakedefine HAVE_XF86MISC 1
/* Define if you have the XFixes extension */
#cmakedefine HAVE_XFIXES 1
/* Define if you have the XTest extension */
#cmakedefine HAVE_XTEST 1
/* Define if your system has XRender support */
#cmakedefine HAVE_XRENDER 1
/* Define if you have OpenGL */
#cmakedefine HAVE_OPENGL 1
/* Define if you have the XSync extension */
#cmakedefine HAVE_XSYNC 1
/* Define if you have XRandR 1.3 */
#cmakedefine HAS_RANDR_1_3 1
/* Define if you have X11 at all */
#define HAVE_X11 ${X11_FOUND}
+27
View File
@@ -0,0 +1,27 @@
set(libdbusmenu_SRCS
dbusmenuimporter.cpp
dbusmenushortcut_p.cpp
dbusmenutypes_p.cpp
utils.cpp
)
ecm_qt_declare_logging_category(libdbusmenu_SRCS HEADER debug.h
IDENTIFIER DBUSMENUQT
CATEGORY_NAME org.kde.libdbusmenuqt
DEFAULT_SEVERITY Info)
set_source_files_properties(com.canonical.dbusmenu.xml PROPERTIES
NO_NAMESPACE true
INCLUDE "dbusmenutypes_p.h"
CLASSNAME DBusMenuInterface
)
qt5_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface)
add_library(dbusmenuqt STATIC ${libdbusmenu_SRCS})
target_link_libraries(dbusmenuqt
Qt5::DBus
Qt5::Widgets
)
add_subdirectory(test)
+2
View File
@@ -0,0 +1,2 @@
Contains a patched version of the import path of libdbusmenu-qt
Remove when next version of libdbusmenu-qt is released.
@@ -0,0 +1,49 @@
<interface name="com.canonical.dbusmenu">
<property name="Version" type="u" access="read"/>
<property name="Status" type="s" access="read"/>
<signal name="ItemsPropertiesUpdated">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuItemKeysList"/>
<arg type="a(ia{sv})" direction="out"/>
<arg type="a(ias)" direction="out"/>
</signal>
<signal name="LayoutUpdated">
<arg name="revision" type="u" direction="out"/>
<arg name="parentId" type="i" direction="out"/>
</signal>
<signal name="ItemActivationRequested">
<arg name="id" type="i" direction="out"/>
<arg name="timeStamp" type="u" direction="out"/>
</signal>
<method name="Event">
<arg name="id" type="i" direction="in"/>
<arg name="eventId" type="s" direction="in"/>
<arg name="data" type="v" direction="in"/>
<arg name="timestamp" type="u" direction="in"/>
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<method name="GetProperty">
<arg type="v" direction="out"/>
<arg name="id" type="i" direction="in"/>
<arg name="property" type="s" direction="in"/>
</method>
<method name="GetLayout">
<arg type="u" direction="out"/>
<arg name="parentId" type="i" direction="in"/>
<arg name="recursionDepth" type="i" direction="in"/>
<arg name="propertyNames" type="as" direction="in"/>
<arg name="item" type="(ia{sv}av)" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuLayoutItem"/>
</method>
<method name="GetGroupProperties">
<arg type="a(ia{sv})" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
<arg name="ids" type="ai" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList&lt;int&gt;"/>
<arg name="propertyNames" type="as" direction="in"/>
</method>
<method name="AboutToShow">
<arg type="b" direction="out"/>
<arg name="id" type="i" direction="in"/>
</method>
</interface>
+545
View File
@@ -0,0 +1,545 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "dbusmenuimporter.h"
#include "debug.h"
// Qt
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusVariant>
#include <QFont>
#include <QMenu>
#include <QPointer>
#include <QTime>
#include <QTimer>
#include <QToolButton>
#include <QWidgetAction>
#include <QSet>
#include <QDebug>
// Local
#include "dbusmenutypes_p.h"
#include "dbusmenushortcut_p.h"
#include "utils_p.h"
// Generated
#include "dbusmenu_interface.h"
//#define BENCHMARK
#ifdef BENCHMARK
static QTime sChrono;
#endif
#define DMRETURN_IF_FAIL(cond) if (!(cond)) { \
qCWarning(DBUSMENUQT) << "Condition failed: " #cond; \
return; \
}
static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id";
static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name";
static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash";
static QAction *createKdeTitle(QAction *action, QWidget *parent)
{
QToolButton *titleWidget = new QToolButton(nullptr);
QFont font = titleWidget->font();
font.setBold(true);
titleWidget->setFont(font);
titleWidget->setIcon(action->icon());
titleWidget->setText(action->text());
titleWidget->setDown(true);
titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
QWidgetAction *titleAction = new QWidgetAction(parent);
titleAction->setDefaultWidget(titleWidget);
return titleAction;
}
class DBusMenuImporterPrivate
{
public:
DBusMenuImporter *q;
DBusMenuInterface *m_interface;
QMenu *m_menu;
using ActionForId = QMap<int, QAction* >;
ActionForId m_actionForId;
QTimer *m_pendingLayoutUpdateTimer;
QSet<int> m_idsRefreshedByAboutToShow;
QSet<int> m_pendingLayoutUpdates;
QDBusPendingCallWatcher *refresh(int id)
{
auto call = m_interface->GetLayout(id, 1, QStringList());
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q);
watcher->setProperty(DBUSMENU_PROPERTY_ID, id);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
q, &DBusMenuImporter::slotGetLayoutFinished);
return watcher;
}
QMenu *createMenu(QWidget *parent)
{
QMenu *menu = q->createMenu(parent);
return menu;
}
/**
* Init all the immutable action properties here
* TODO: Document immutable properties?
*
* Note: we remove properties we handle from the map (using QMap::take()
* instead of QMap::value()) to avoid warnings about these properties in
* updateAction()
*/
QAction *createAction(int id, const QVariantMap &_map, QWidget *parent)
{
QVariantMap map = _map;
QAction *action = new QAction(parent);
action->setProperty(DBUSMENU_PROPERTY_ID, id);
QString type = map.take(QStringLiteral("type")).toString();
if (type == QLatin1String("separator")) {
action->setSeparator(true);
}
if (map.take(QStringLiteral("children-display")).toString() == QLatin1String("submenu")) {
QMenu *menu = createMenu(parent);
action->setMenu(menu);
}
QString toggleType = map.take(QStringLiteral("toggle-type")).toString();
if (!toggleType.isEmpty()) {
action->setCheckable(true);
if (toggleType == QLatin1String("radio")) {
QActionGroup *group = new QActionGroup(action);
group->addAction(action);
}
}
bool isKdeTitle = map.take(QStringLiteral("x-kde-title")).toBool();
updateAction(action, map, map.keys());
if (isKdeTitle) {
action = createKdeTitle(action, parent);
}
return action;
}
/**
* Update mutable properties of an action. A property may be listed in
* requestedProperties but not in map, this means we should use the default value
* for this property.
*
* @param action the action to update
* @param map holds the property values
* @param requestedProperties which properties has been requested
*/
void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties)
{
Q_FOREACH(const QString &key, requestedProperties) {
updateActionProperty(action, key, map.value(key));
}
}
void updateActionProperty(QAction *action, const QString &key, const QVariant &value)
{
if (key == QLatin1String("label")) {
updateActionLabel(action, value);
} else if (key == QLatin1String("enabled")) {
updateActionEnabled(action, value);
} else if (key == QLatin1String("toggle-state")) {
updateActionChecked(action, value);
} else if (key == QLatin1String("icon-name")) {
updateActionIconByName(action, value);
} else if (key == QLatin1String("icon-data")) {
updateActionIconByData(action, value);
} else if (key == QLatin1String("visible")) {
updateActionVisible(action, value);
} else if (key == QLatin1String("shortcut")) {
updateActionShortcut(action, value);
} else {
qDebug(DBUSMENUQT) << "Unhandled property update" << key;
}
}
void updateActionLabel(QAction *action, const QVariant &value)
{
QString text = swapMnemonicChar(value.toString(), '_', '&');
action->setText(text);
}
void updateActionEnabled(QAction *action, const QVariant &value)
{
action->setEnabled(value.isValid() ? value.toBool(): true);
}
void updateActionChecked(QAction *action, const QVariant &value)
{
if (action->isCheckable() && value.isValid()) {
action->setChecked(value.toInt() == 1);
}
}
void updateActionIconByName(QAction *action, const QVariant &value)
{
const QString iconName = value.toString();
const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString();
if (previous == iconName) {
return;
}
action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName);
if (iconName.isEmpty()) {
action->setIcon(QIcon());
return;
}
action->setIcon(q->iconForName(iconName));
}
void updateActionIconByData(QAction *action, const QVariant &value)
{
const QByteArray data = value.toByteArray();
uint dataHash = qHash(data);
uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt();
if (previousDataHash == dataHash) {
return;
}
action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash);
QPixmap pix;
if (!pix.loadFromData(data)) {
qDebug(DBUSMENUQT) << "Failed to decode icon-data property for action" << action->text();
action->setIcon(QIcon());
return;
}
action->setIcon(QIcon(pix));
}
void updateActionVisible(QAction *action, const QVariant &value)
{
action->setVisible(value.isValid() ? value.toBool() : true);
}
void updateActionShortcut(QAction *action, const QVariant &value)
{
QDBusArgument arg = value.value<QDBusArgument>();
DBusMenuShortcut dmShortcut;
arg >> dmShortcut;
QKeySequence keySequence = dmShortcut.toKeySequence();
action->setShortcut(keySequence);
}
QMenu *menuForId(int id) const
{
if (id == 0) {
return q->menu();
}
QAction *action = m_actionForId.value(id);
if (!action) {
return nullptr;
}
return action->menu();
}
void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList);
void sendEvent(int id, const QString &eventId)
{
m_interface->Event(id, eventId, QDBusVariant(QString()), 0u);
}
};
DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent)
: QObject(parent)
, d(new DBusMenuImporterPrivate)
{
DBusMenuTypes_register();
d->q = this;
d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this);
d->m_menu = nullptr;
d->m_pendingLayoutUpdateTimer = new QTimer(this);
d->m_pendingLayoutUpdateTimer->setSingleShot(true);
connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates);
connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated);
connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested);
connect(d->m_interface, &DBusMenuInterface::ItemsPropertiesUpdated, this, [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) {
d->slotItemsPropertiesUpdated(updatedList, removedList);
});
d->refresh(0);
}
DBusMenuImporter::~DBusMenuImporter()
{
// Do not use "delete d->m_menu": even if we are being deleted we should
// leave enough time for the menu to finish what it was doing, for example
// if it was being displayed.
d->m_menu->deleteLater();
delete d;
}
void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId)
{
Q_UNUSED(revision)
if (d->m_idsRefreshedByAboutToShow.remove(parentId)) {
return;
}
d->m_pendingLayoutUpdates << parentId;
if (!d->m_pendingLayoutUpdateTimer->isActive()) {
d->m_pendingLayoutUpdateTimer->start();
}
}
void DBusMenuImporter::processPendingLayoutUpdates()
{
QSet<int> ids = d->m_pendingLayoutUpdates;
d->m_pendingLayoutUpdates.clear();
Q_FOREACH(int id, ids) {
d->refresh(id);
}
}
QMenu *DBusMenuImporter::menu() const
{
if (!d->m_menu) {
d->m_menu = d->createMenu(nullptr);
}
return d->m_menu;
}
void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)
{
Q_FOREACH(const DBusMenuItem &item, updatedList) {
QAction *action = m_actionForId.value(item.id);
if (!action) {
// We don't know this action. It probably is in a menu we haven't fetched yet.
continue;
}
QVariantMap::ConstIterator
it = item.properties.constBegin(),
end = item.properties.constEnd();
for(; it != end; ++it) {
updateActionProperty(action, it.key(), it.value());
}
}
Q_FOREACH(const DBusMenuItemKeys &item, removedList) {
QAction *action = m_actionForId.value(item.id);
if (!action) {
// We don't know this action. It probably is in a menu we haven't fetched yet.
continue;
}
Q_FOREACH(const QString &key, item.properties) {
updateActionProperty(action, key, QVariant());
}
}
}
QAction *DBusMenuImporter::actionForId(int id) const
{
return d->m_actionForId.value(id);
}
void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/)
{
QAction *action = d->m_actionForId.value(id);
DMRETURN_IF_FAIL(action);
actionActivationRequested(action);
}
void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher)
{
int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt();
watcher->deleteLater();
QMenu *menu = d->menuForId(parentId);
QDBusPendingReply<uint, DBusMenuLayoutItem> reply = *watcher;
if (!reply.isValid()) {
qDebug(DBUSMENUQT) << reply.error().message();
if (menu) {
emit menuUpdated(menu);
}
return;
}
#ifdef BENCHMARK
DMDEBUG << "- items received:" << sChrono.elapsed() << "ms";
#endif
DBusMenuLayoutItem rootItem = reply.argumentAt<1>();
if (!menu) {
qDebug(DBUSMENUQT) << "No menu for id" << parentId;
return;
}
//remove outdated actions
QSet<int> newDBusMenuItemIds;
newDBusMenuItemIds.reserve(rootItem.children.count());
for (const DBusMenuLayoutItem &item: rootItem.children) {
newDBusMenuItemIds << item.id;
}
for (QAction *action: menu->actions()) {
int id = action->property(DBUSMENU_PROPERTY_ID).toInt();
if (! newDBusMenuItemIds.contains(id)) {
menu->removeAction(action);
action->deleteLater();
d->m_actionForId.remove(id);
}
}
//insert or update new actions into our menu
for (const DBusMenuLayoutItem &dbusMenuItem: rootItem.children) {
DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id);
QAction *action = nullptr;
if (it == d->m_actionForId.end()) {
int id = dbusMenuItem.id;
action = d->createAction(id, dbusMenuItem.properties, menu);
d->m_actionForId.insert(id, action);
connect(action, &QObject::destroyed, this, [this, id]() {
d->m_actionForId.remove(id);
});
connect(action, &QAction::triggered, this, [action, id, this]() {
sendClickedEvent(id);
});
if (QMenu *menuAction = action->menu()) {
connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection);
}
connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection);
menu->addAction(action);
} else {
action = *it;
QStringList filteredKeys = dbusMenuItem.properties.keys();
filteredKeys.removeOne("type");
filteredKeys.removeOne("toggle-type");
filteredKeys.removeOne("children-display");
d->updateAction(*it, dbusMenuItem.properties, filteredKeys);
// Move the action to the tail so we can keep the order same as the dbus request.
menu->removeAction(action);
menu->addAction(action);
}
}
emit menuUpdated(menu);
}
void DBusMenuImporter::sendClickedEvent(int id)
{
d->sendEvent(id, QStringLiteral("clicked"));
}
void DBusMenuImporter::updateMenu()
{
updateMenu(DBusMenuImporter::menu());
}
void DBusMenuImporter::updateMenu(QMenu * menu)
{
Q_ASSERT(menu);
QAction *action = menu->menuAction();
Q_ASSERT(action);
int id = action->property(DBUSMENU_PROPERTY_ID).toInt();
auto call = d->m_interface->AboutToShow(id);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
watcher->setProperty(DBUSMENU_PROPERTY_ID, id);
connect(watcher, &QDBusPendingCallWatcher::finished, this,
&DBusMenuImporter::slotAboutToShowDBusCallFinished);
// Firefox deliberately ignores "aboutToShow" whereas Qt ignores" opened", so we'll just send both all the time...
d->sendEvent(id, QStringLiteral("opened"));
}
void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher)
{
int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt();
watcher->deleteLater();
QMenu *menu = d->menuForId(id);
if (!menu) {
return;
}
QDBusPendingReply<bool> reply = *watcher;
if (reply.isError()) {
qDebug(DBUSMENUQT) << "Call to AboutToShow() failed:" << reply.error().message();
menuUpdated(menu);
return;
}
//Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before
//this returns, which equates to the same thing
bool needRefresh = reply.argumentAt<0>();
if (needRefresh || menu->actions().isEmpty()) {
d->m_idsRefreshedByAboutToShow << id;
d->refresh(id);
} else if (menu) {
menuUpdated(menu);
}
}
void DBusMenuImporter::slotMenuAboutToHide()
{
QMenu *menu = qobject_cast<QMenu*>(sender());
Q_ASSERT(menu);
QAction *action = menu->menuAction();
Q_ASSERT(action);
int id = action->property(DBUSMENU_PROPERTY_ID).toInt();
d->sendEvent(id, QStringLiteral("closed"));
}
void DBusMenuImporter::slotMenuAboutToShow()
{
QMenu *menu = qobject_cast<QMenu*>(sender());
Q_ASSERT(menu);
updateMenu(menu);
}
QMenu *DBusMenuImporter::createMenu(QWidget *parent)
{
return new QMenu(parent);
}
QIcon DBusMenuImporter::iconForName(const QString &/*name*/)
{
return QIcon();
}
#include "moc_dbusmenuimporter.cpp"
+113
View File
@@ -0,0 +1,113 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DBUSMENUIMPORTER_H
#define DBUSMENUIMPORTER_H
// Qt
#include <QObject>
class QAction;
class QDBusPendingCallWatcher;
class QDBusVariant;
class QIcon;
class QMenu;
class DBusMenuImporterPrivate;
/**
* A DBusMenuImporter instance can recreate a menu serialized over DBus by
* DBusMenuExporter
*/
class DBusMenuImporter : public QObject
{
Q_OBJECT
public:
/**
* Creates a DBusMenuImporter listening over DBus on service, path
*/
DBusMenuImporter(const QString &service, const QString &path, QObject *parent = nullptr);
~DBusMenuImporter() override;
QAction *actionForId(int id) const;
/**
* The menu created from listening to the DBusMenuExporter over DBus
*/
QMenu *menu() const;
public Q_SLOTS:
/**
* Load the menu
*
* Will emit menuUpdated() when complete.
* This should be done before showing a menu
*/
void updateMenu();
void updateMenu(QMenu *menu);
Q_SIGNALS:
/**
* Emitted after a call to updateMenu().
* @see updateMenu()
*/
void menuUpdated(QMenu *);
/**
* Emitted when the exporter was asked to activate an action
*/
void actionActivationRequested(QAction *);
protected:
/**
* Must create a menu, may be customized to fit host appearance.
* Default implementation creates a simple QMenu.
*/
virtual QMenu *createMenu(QWidget *parent);
/**
* Must convert a name into an icon.
* Default implementation returns a null icon.
*/
virtual QIcon iconForName(const QString &);
private Q_SLOTS:
void sendClickedEvent(int);
void slotMenuAboutToShow();
void slotMenuAboutToHide();
void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *);
void slotItemActivationRequested(int id, uint timestamp);
void processPendingLayoutUpdates();
void slotLayoutUpdated(uint revision, int parentId);
void slotGetLayoutFinished(QDBusPendingCallWatcher *);
private:
Q_DISABLE_COPY(DBusMenuImporter)
DBusMenuImporterPrivate *const d;
friend class DBusMenuImporterPrivate;
// Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList
Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList))
};
#endif /* DBUSMENUIMPORTER_H */
@@ -0,0 +1,82 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "dbusmenushortcut_p.h"
// Qt
#include <QKeySequence>
static const int QT_COLUMN = 0;
static const int DM_COLUMN = 1;
static void processKeyTokens(QStringList* tokens, int srcCol, int dstCol)
{
struct Row {
const char* zero;
const char* one;
const char* operator[](int col) const { return col == 0 ? zero : one; }
};
static const Row table[] =
{ {"Meta", "Super"},
{"Ctrl", "Control"},
// Special cases for compatibility with libdbusmenu-glib which uses
// "plus" for "+" and "minus" for "-".
// cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565
{"+", "plus"},
{"-", "minus"},
{nullptr, nullptr}
};
const Row* ptr = table;
for (; ptr->zero != nullptr; ++ptr) {
const char* from = (*ptr)[srcCol];
const char* to = (*ptr)[dstCol];
tokens->replaceInStrings(from, to);
}
}
DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence& sequence)
{
QString string = sequence.toString();
DBusMenuShortcut shortcut;
QStringList tokens = string.split(QStringLiteral(", "));
Q_FOREACH(QString token, tokens) {
// Hack: Qt::CTRL | Qt::Key_Plus is turned into the string "Ctrl++",
// but we don't want the call to token.split() to consider the
// second '+' as a separator so we replace it with its final value.
token.replace(QLatin1String("++"), QLatin1String("+plus"));
QStringList keyTokens = token.split('+');
processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN);
shortcut << keyTokens;
}
return shortcut;
}
QKeySequence DBusMenuShortcut::toKeySequence() const
{
QStringList tmp;
Q_FOREACH(const QStringList& keyTokens_, *this) {
QStringList keyTokens = keyTokens_;
processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN);
tmp << keyTokens.join(QLatin1String("+"));
}
QString string = tmp.join(QLatin1String(", "));
return QKeySequence::fromString(string);
}
+39
View File
@@ -0,0 +1,39 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DBUSMENUSHORTCUT_H
#define DBUSMENUSHORTCUT_H
// Qt
#include <QMetaType>
#include <QStringList>
class QKeySequence;
class DBusMenuShortcut : public QList<QStringList>
{
public:
QKeySequence toKeySequence() const;
static DBusMenuShortcut fromKeySequence(const QKeySequence&);
};
Q_DECLARE_METATYPE(DBusMenuShortcut)
#endif /* DBUSMENUSHORTCUT_H */
+136
View File
@@ -0,0 +1,136 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "dbusmenutypes_p.h"
// Local
#include "dbusmenushortcut_p.h"
// Qt
#include <QDBusArgument>
#include <QDBusMetaType>
//// DBusMenuItem
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj)
{
argument.beginStructure();
argument << obj.id << obj.properties;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &obj)
{
argument.beginStructure();
argument >> obj.id >> obj.properties;
argument.endStructure();
return argument;
}
//// DBusMenuItemKeys
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &obj)
{
argument.beginStructure();
argument << obj.id << obj.properties;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &obj)
{
argument.beginStructure();
argument >> obj.id >> obj.properties;
argument.endStructure();
return argument;
}
//// DBusMenuLayoutItem
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj)
{
argument.beginStructure();
argument << obj.id << obj.properties;
argument.beginArray(qMetaTypeId<QDBusVariant>());
Q_FOREACH(const DBusMenuLayoutItem& child, obj.children) {
argument << QDBusVariant(QVariant::fromValue<DBusMenuLayoutItem>(child));
}
argument.endArray();
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj)
{
argument.beginStructure();
argument >> obj.id >> obj.properties;
argument.beginArray();
while (!argument.atEnd()) {
QDBusVariant dbusVariant;
argument >> dbusVariant;
QDBusArgument childArgument = dbusVariant.variant().value<QDBusArgument>();
DBusMenuLayoutItem child;
childArgument >> child;
obj.children.append(child);
}
argument.endArray();
argument.endStructure();
return argument;
}
//// DBusMenuShortcut
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &obj)
{
argument.beginArray(qMetaTypeId<QStringList>());
typename QList<QStringList>::ConstIterator it = obj.constBegin();
typename QList<QStringList>::ConstIterator end = obj.constEnd();
for ( ; it != end; ++it)
argument << *it;
argument.endArray();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &obj)
{
argument.beginArray();
obj.clear();
while (!argument.atEnd()) {
QStringList item;
argument >> item;
obj.push_back(item);
}
argument.endArray();
return argument;
}
void DBusMenuTypes_register()
{
static bool registered = false;
if (registered) {
return;
}
qDBusRegisterMetaType<DBusMenuItem>();
qDBusRegisterMetaType<DBusMenuItemList>();
qDBusRegisterMetaType<DBusMenuItemKeys>();
qDBusRegisterMetaType<DBusMenuItemKeysList>();
qDBusRegisterMetaType<DBusMenuLayoutItem>();
qDBusRegisterMetaType<DBusMenuLayoutItemList>();
qDBusRegisterMetaType<DBusMenuShortcut>();
registered = true;
}
+100
View File
@@ -0,0 +1,100 @@
/* This file is part of the dbusmenu-qt library
Copyright 2009 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DBUSMENUTYPES_P_H
#define DBUSMENUTYPES_P_H
// Qt
#include <QList>
#include <QStringList>
#include <QVariant>
class QDBusArgument;
//// DBusMenuItem
/**
* Internal struct used to communicate on DBus
*/
struct DBusMenuItem
{
int id;
QVariantMap properties;
};
Q_DECLARE_METATYPE(DBusMenuItem)
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item);
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item);
typedef QList<DBusMenuItem> DBusMenuItemList;
Q_DECLARE_METATYPE(DBusMenuItemList)
//// DBusMenuItemKeys
/**
* Represents a list of keys for a menu item
*/
struct DBusMenuItemKeys
{
int id;
QStringList properties;
};
Q_DECLARE_METATYPE(DBusMenuItemKeys)
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &);
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &);
typedef QList<DBusMenuItemKeys> DBusMenuItemKeysList;
Q_DECLARE_METATYPE(DBusMenuItemKeysList)
//// DBusMenuLayoutItem
/**
* Represents an item with its children. GetLayout() returns a
* DBusMenuLayoutItemList.
*/
struct DBusMenuLayoutItem;
struct DBusMenuLayoutItem
{
int id;
QVariantMap properties;
QList<DBusMenuLayoutItem> children;
};
Q_DECLARE_METATYPE(DBusMenuLayoutItem)
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &);
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &);
typedef QList<DBusMenuLayoutItem> DBusMenuLayoutItemList;
Q_DECLARE_METATYPE(DBusMenuLayoutItemList)
//// DBusMenuShortcut
class DBusMenuShortcut;
QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &);
const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &);
void DBusMenuTypes_register();
#endif /* DBUSMENUTYPES_P_H */
+3
View File
@@ -0,0 +1,3 @@
add_executable(appmenutest main.cpp)
target_link_libraries(appmenutest
Qt5::Widgets)
+2
View File
@@ -0,0 +1,2 @@
App with a menu, designed for use testing appmenu QPTs/applets/kded modules
small enough that we can attach debuggers and breakpoints without drowning in data
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright 2017 David Edmundson <davidedmundson@kde.org>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QDateTime>
#include <QIcon>
#include <QDebug>
class MainWindow : public QMainWindow
{
public:
MainWindow();
};
MainWindow::MainWindow() :
QMainWindow()
{
/*set an initial menu with the following
Menu A
- Item
- Checkable Item
- Item With Icon
- A separator
- Menu B
- Item B1
Menu C
- DynamicItem ${timestamp}
TopLevelItem
*/
QAction *t;
auto menuA = new QMenu("Menu A", this);
menuA->addAction("Item");
t = menuA->addAction("Checkable Item");
t->setCheckable(true);
t = menuA->addAction(QIcon::fromTheme("document-edit"), "Item with icon");
menuA->addSeparator();
auto menuB = new QMenu("Menu B", this);
menuB->addAction("Item B1");
menuA->addMenu(menuB);
menuBar()->addMenu(menuA);
auto menuC = new QMenu("Menu C", this);
connect(menuC, &QMenu::aboutToShow, this, [menuC]() {
menuC->clear();
menuC->addAction("Dynamic Item " + QDateTime::currentDateTime().toString());
});
menuBar()->addMenu(menuC);
menuBar()->addAction("Top Level Item");
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MainWindow mw;
mw.show();
return app.exec();
}
+64
View File
@@ -0,0 +1,64 @@
/* This file is part of the dbusmenu-qt library
Copyright 2010 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "utils_p.h"
// Qt
#include <QString>
QString swapMnemonicChar(const QString &in, const char src, const char dst)
{
QString out;
bool mnemonicFound = false;
for (int pos = 0; pos < in.length(); ) {
QChar ch = in[pos];
if (ch == src) {
if (pos == in.length() - 1) {
// 'src' at the end of string, skip it
++pos;
} else {
if (in[pos + 1] == src) {
// A real 'src'
out += src;
pos += 2;
} else if (!mnemonicFound) {
// We found the mnemonic
mnemonicFound = true;
out += dst;
++pos;
} else {
// We already have a mnemonic, just skip the char
++pos;
}
}
} else if (ch == dst) {
// Escape 'dst'
out += dst;
out += dst;
++pos;
} else {
out += ch;
++pos;
}
}
return out;
}
+31
View File
@@ -0,0 +1,31 @@
/* This file is part of the dbusmenu-qt library
Copyright 2010 Canonical
Author: Aurelien Gateau <aurelien.gateau@canonical.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License (LGPL) as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later
version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef UTILS_P_H
#define UTILS_P_H
class QString;
/**
* Swap mnemonic char: Qt uses '&', while dbusmenu uses '_'
*/
QString swapMnemonicChar(const QString &in, const char src, const char dst);
#endif /* UTILS_P_H */
+3
View File
@@ -0,0 +1,3 @@
module org.kde.private.windowAppMenu
plugin appmenuplugin