From 3d0a9cd462a7ab256b65dec7ed2035e0d7658818 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Fri, 22 May 2015 16:17:46 +0100 Subject: [PATCH] Use upcoming version of libdbusmenu-qt This contains a temporary fork of the importer, with the main paths fixed. The old code spawned a new event loop in the signal QMenu::aboutToShow() This refetched the menu, even though we had already fetched it, before calling show. Spawning new event loops from a QML function, leads to all sorts of crashes. This fixes that, and saves some pointless DBus traffic BUG: 343971 BUG: 345838 BUG: 345933 REVIEW: 123992 --- dataengines/statusnotifieritem/CMakeLists.txt | 7 +- .../statusnotifieritem/libdbusmenuqt/README | 2 + .../libdbusmenuqt/dbusmenuimporter.cpp | 532 ++++++++++++++++++ .../libdbusmenuqt/dbusmenuimporter.h | 109 ++++ .../libdbusmenuqt/dbusmenushortcut_p.cpp | 82 +++ .../libdbusmenuqt/dbusmenushortcut_p.h | 39 ++ .../libdbusmenuqt/dbusmenutypes_p.cpp | 111 ++++ .../libdbusmenuqt/dbusmenutypes_p.h | 93 +++ .../libdbusmenuqt/utils.cpp | 64 +++ .../libdbusmenuqt/utils_p.h | 31 + 10 files changed, 1068 insertions(+), 2 deletions(-) create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/README create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.cpp create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.h create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.cpp create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.h create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.cpp create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.h create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/utils.cpp create mode 100644 dataengines/statusnotifieritem/libdbusmenuqt/utils_p.h diff --git a/dataengines/statusnotifieritem/CMakeLists.txt b/dataengines/statusnotifieritem/CMakeLists.txt index e639ee351..c28312ea4 100644 --- a/dataengines/statusnotifieritem/CMakeLists.txt +++ b/dataengines/statusnotifieritem/CMakeLists.txt @@ -1,7 +1,6 @@ include_directories(${plasma-workspace_SOURCE_DIR}/statusnotifierwatcher) include_directories(${dbusmenu-qt5_INCLUDE_DIRS}) - # We add our source code here set(statusnotifieritem_engine_SRCS statusnotifieritem_engine.cpp @@ -9,6 +8,11 @@ set(statusnotifieritem_engine_SRCS statusnotifieritemservice.cpp statusnotifieritemjob.cpp systemtraytypes.cpp + + libdbusmenuqt/dbusmenuimporter.cpp + libdbusmenuqt/dbusmenushortcut_p.cpp + libdbusmenuqt/dbusmenutypes_p.cpp + libdbusmenuqt/utils.cpp ) set(statusnotifierwatcher_xml ${KNOTIFICATIONS_DBUS_INTERFACES_DIR}/kf5_org.kde.StatusNotifierWatcher.xml) @@ -29,7 +33,6 @@ target_link_libraries(plasma_engine_statusnotifieritem KF5::Service KF5::Plasma KF5::IconThemes - dbusmenu-qt5 ) kcoreaddons_desktop_to_json(plasma_engine_statusnotifieritem plasma-dataengine-statusnotifieritem.desktop) diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/README b/dataengines/statusnotifieritem/libdbusmenuqt/README new file mode 100644 index 000000000..35db8a488 --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/README @@ -0,0 +1,2 @@ +Contains a patched version of the import path of libdbusmenu-qt +Remove when next version of libdbusmenu-qt is released. \ No newline at end of file diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.cpp b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.cpp new file mode 100644 index 000000000..0aa68b9c0 --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.cpp @@ -0,0 +1,532 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "dbusmenutypes_p.h" +#include "dbusmenushortcut_p.h" +#include "utils_p.h" + +//#define BENCHMARK +#ifdef BENCHMARK +#include +static QTime sChrono; +#endif + +#define DMRETURN_IF_FAIL(cond) if (!(cond)) { \ + qWarning() << "Condition failed: " #cond; \ + return; \ +} + +static const char *DBUSMENU_INTERFACE = "com.canonical.dbusmenu"; + +static const int ABOUT_TO_SHOW_TIMEOUT = 3000; +static const int REFRESH_TIMEOUT = 4000; + +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(0); + 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; + + QDBusAbstractInterface *m_interface; + QMenu *m_menu; + typedef QMap > ActionForId; + ActionForId m_actionForId; + QSignalMapper m_mapper; + QTimer *m_pendingLayoutUpdateTimer; + + QSet m_idsRefreshedByAboutToShow; + QSet m_pendingLayoutUpdates; + int m_nPendingRequests; + + QDBusPendingCallWatcher *refresh(int id) + { + m_nPendingRequests++; + #ifdef BENCHMARK + DMDEBUG << "Starting refresh chrono for id" << id; + sChrono.start(); + #endif + QDBusPendingCall call = m_interface->asyncCall("GetLayout", id, 1, QStringList()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + q, SLOT(slotGetLayoutFinished(QDBusPendingCallWatcher*))); + + 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("type").toString(); + if (type == "separator") { + action->setSeparator(true); + } + + if (map.take("children-display").toString() == "submenu") { + QMenu *menu = createMenu(parent); + action->setMenu(menu); + } + + QString toggleType = map.take("toggle-type").toString(); + if (!toggleType.isEmpty()) { + action->setCheckable(true); + if (toggleType == "radio") { + QActionGroup *group = new QActionGroup(action); + group->addAction(action); + } + } + + bool isKdeTitle = map.take("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 if (key == QLatin1String("children-display")) { + } else { + qWarning() << "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)) { + qWarning() << "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(); + 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 0; + } + return action->menu(); + } + + void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); + + void sendEvent(int id, const QString &eventId) + { + QVariant empty = QVariant::fromValue(QDBusVariant(QString())); + m_interface->asyncCall("Event", id, eventId, empty, 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 QDBusInterface(service, path, DBUSMENU_INTERFACE, QDBusConnection::sessionBus(), this); + d->m_menu = 0; + d->m_nPendingRequests = 0; + + connect(&d->m_mapper, SIGNAL(mapped(int)), SLOT(sendClickedEvent(int))); + + d->m_pendingLayoutUpdateTimer = new QTimer(this); + d->m_pendingLayoutUpdateTimer->setSingleShot(true); + connect(d->m_pendingLayoutUpdateTimer, SIGNAL(timeout()), SLOT(processPendingLayoutUpdates())); + + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "LayoutUpdated", "ui", + this, SLOT(slotLayoutUpdated(uint, int))); + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "ItemsPropertiesUpdated", "a(ia{sv})a(ias)", + this, SLOT(slotItemsPropertiesUpdated(DBusMenuItemList, DBusMenuItemKeysList))); + QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, "ItemActivationRequested", "iu", + this, SLOT(slotItemActivationRequested(int, uint))); + + 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) +{ + if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { + return; + } + d->m_pendingLayoutUpdates << parentId; + if (!d->m_pendingLayoutUpdateTimer->isActive()) { + d->m_pendingLayoutUpdateTimer->start(); + } +} + +void DBusMenuImporter::processPendingLayoutUpdates() +{ + QSet 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(0); + } + 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()); + } + } +} + +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(); + + d->m_nPendingRequests--; + + QDBusPendingReply reply = *watcher; + if (!reply.isValid()) { + qWarning() << reply.error().message(); + + if (d->m_nPendingRequests == 0) { + emit menuUpdated(); + } + + return; + } + + #ifdef BENCHMARK + DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; + #endif + DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); + + QMenu *menu = d->menuForId(parentId); + if (!menu) { + qWarning() << "No menu for id" << parentId; + return; + } + + menu->clear(); + + Q_FOREACH(const DBusMenuLayoutItem &dbusMenuItem, rootItem.children) { + QAction *action = d->createAction(dbusMenuItem.id, dbusMenuItem.properties, menu); + DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id); + if (it == d->m_actionForId.end()) { + d->m_actionForId.insert(dbusMenuItem.id, action); + } else { + delete *it; + *it = action; + } + menu->addAction(action); + + connect(action, SIGNAL(triggered()), + &d->m_mapper, SLOT(map())); + d->m_mapper.setMapping(action, dbusMenuItem.id); + + if( action->menu() ) + { + d->refresh( dbusMenuItem.id ); + } + } + + if (d->m_nPendingRequests == 0) { + emit menuUpdated(); + } + + #ifdef BENCHMARK + DMDEBUG << "- Menu filled:" << sChrono.elapsed() << "ms"; + #endif +} + +void DBusMenuImporter::sendClickedEvent(int id) +{ + d->sendEvent(id, QString("clicked")); +} + +void DBusMenuImporter::updateMenu() +{ + QMenu *menu = DBusMenuImporter::menu(); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + + QDBusPendingCall call = d->m_interface->asyncCall("AboutToShow", id); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + connect(watcher, &QDBusPendingCallWatcher::finished, this, + &DBusMenuImporter::slotAboutToShowDBusCallFinished); +} + + + +void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); + watcher->deleteLater(); + + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + menuUpdated(); + qWarning() << "Call to AboutToShow() failed:" << reply.error().message(); + return; + } + bool needRefresh = reply.argumentAt<0>(); + + QMenu *menu = d->menuForId(id); + DMRETURN_IF_FAIL(menu); + + if (needRefresh || menu->actions().isEmpty()) { + d->m_idsRefreshedByAboutToShow << id; + d->refresh(id); + } else { + menuUpdated(); + } +} + +void DBusMenuImporter::slotMenuAboutToHide() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + d->sendEvent(id, QString("closed")); +} + +void DBusMenuImporter::slotMenuAboutToShow() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + d->sendEvent(id, QString("opened")); +} + + + +QMenu *DBusMenuImporter::createMenu(QWidget *parent) +{ + return new QMenu(parent); +} + +QIcon DBusMenuImporter::iconForName(const QString &/*name*/) +{ + return QIcon(); +} + +#include "moc_dbusmenuimporter.cpp" diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.h b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.h new file mode 100644 index 000000000..939cabe36 --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenuimporter.h @@ -0,0 +1,109 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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 + +class QAction; +class QDBusAbstractInterface; +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 = 0); + + virtual ~DBusMenuImporter(); + + /** + * 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(); + +Q_SIGNALS: + /** + * Emitted after a call to updateMenu(). + * @see updateMenu() + */ + void menuUpdated(); + + /** + * 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 */ diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.cpp b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.cpp new file mode 100644 index 000000000..8cde56dff --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.cpp @@ -0,0 +1,82 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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 + +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"}, + {0, 0} + }; + + const Row* ptr = table; + for (; ptr->zero != 0; ++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(", "); + 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("++", "+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); +} diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.h b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.h new file mode 100644 index 000000000..480bacf5e --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenushortcut_p.h @@ -0,0 +1,39 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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 +#include + +class QKeySequence; + +class DBusMenuShortcut : public QList +{ +public: + QKeySequence toKeySequence() const; + static DBusMenuShortcut fromKeySequence(const QKeySequence&); +}; + +Q_DECLARE_METATYPE(DBusMenuShortcut) + +#endif /* DBUSMENUSHORTCUT_H */ diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.cpp b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.cpp new file mode 100644 index 000000000..e98c4b93b --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.cpp @@ -0,0 +1,111 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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 +#include + +//// 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()); + Q_FOREACH(const DBusMenuLayoutItem& child, obj.children) { + argument << QDBusVariant(QVariant::fromValue(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(); + + DBusMenuLayoutItem child; + childArgument >> child; + obj.children.append(child); + } + argument.endArray(); + argument.endStructure(); + return argument; +} + +void DBusMenuTypes_register() +{ + static bool registered = false; + if (registered) { + return; + } + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + registered = true; +} diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.h b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.h new file mode 100644 index 000000000..4950a2227 --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/dbusmenutypes_p.h @@ -0,0 +1,93 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + 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 +#include +#include + +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 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 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 children; +}; + +Q_DECLARE_METATYPE(DBusMenuLayoutItem) + +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &); +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &); + +typedef QList DBusMenuLayoutItemList; + +Q_DECLARE_METATYPE(DBusMenuLayoutItemList) + +void DBusMenuTypes_register(); +#endif /* DBUSMENUTYPES_P_H */ diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/utils.cpp b/dataengines/statusnotifieritem/libdbusmenuqt/utils.cpp new file mode 100644 index 000000000..e0fa00482 --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/utils.cpp @@ -0,0 +1,64 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + 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 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; +} diff --git a/dataengines/statusnotifieritem/libdbusmenuqt/utils_p.h b/dataengines/statusnotifieritem/libdbusmenuqt/utils_p.h new file mode 100644 index 000000000..3f6e8888e --- /dev/null +++ b/dataengines/statusnotifieritem/libdbusmenuqt/utils_p.h @@ -0,0 +1,31 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + 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 */