import plasma code and try to work independent
This commit is contained in:
1 parent
13ec9ffbe5
commit
e6fcdebc3f
32 files changed
+2647
No files matched your search
+39
@@ -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/
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
if ! [ -a build ] ; then
|
||||
mkdir build
|
||||
fi
|
||||
cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr .. -Wnodev
|
||||
make
|
||||
sudo make install
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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,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}
|
||||
@@ -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)
|
||||
@@ -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<int>"/>
|
||||
<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>
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,3 @@
|
||||
add_executable(appmenutest main.cpp)
|
||||
target_link_libraries(appmenutest
|
||||
Qt5::Widgets)
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -0,0 +1,3 @@
|
||||
module org.kde.private.windowAppMenu
|
||||
|
||||
plugin appmenuplugin
|
||||
Reference in new issue
Block a user