diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp index 4130d97f..02259a30 100644 --- a/applets/appmenu/plugin/appmenumodel.cpp +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -1,240 +1,240 @@ /****************************************************************** * Copyright 2016 Kai Uwe Broulik * Copyright 2016 Chinmoy Ranjan Pradhan * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ******************************************************************/ #include "appmenumodel.h" #include #if HAVE_X11 #include #include #endif #include #include #include static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); class KDBusMenuImporter : public DBusMenuImporter { public: KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) : DBusMenuImporter(service, path, parent) { } protected: QIcon iconForName(const QString &name) override { return QIcon::fromTheme(name); } }; AppMenuModel::AppMenuModel(QObject *parent) : QAbstractListModel(parent) { connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); connect(this, &AppMenuModel::modelNeedsUpdate, this, &AppMenuModel::update, Qt::UniqueConnection); onActiveWindowChanged(KWindowSystem::activeWindow()); } AppMenuModel::~AppMenuModel() = default; 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); return m_activeMenu.count(); } void AppMenuModel::update() { beginResetModel(); if (!m_activeMenu.isEmpty() && !m_activeActions.isEmpty()) { m_activeMenu.clear(); m_activeActions.clear(); } if (m_menu && m_menuAvailable) { const auto &actions = m_menu->actions(); for (QAction *action : actions) { m_activeActions.append(action); m_activeMenu.append(action->text()); } } endResetModel(); } void AppMenuModel::onActiveWindowChanged(WId id) { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { auto *c = QX11Info::connection(); static QHash s_atoms; auto getWindowPropertyString = [c, this](WId id, const QByteArray &name) -> QByteArray { QByteArray value; if (!s_atoms.contains(name)) { const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); QScopedPointer atomReply(xcb_intern_atom_reply(c, atomCookie, Q_NULLPTR)); if (atomReply.isNull()) { return value; } s_atoms[name] = atomReply->atom; if (s_atoms[name] == XCB_ATOM_NONE) { return value; } } static const long MAX_PROP_SIZE = 10000; auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); QScopedPointer propertyReply(xcb_get_property_reply(c, propertyCookie, NULL)); if (propertyReply.isNull()) { return value; } if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) { const char *data = (const char *) xcb_get_property_value(propertyReply.data()); int len = propertyReply->value_len; if (data) { value = QByteArray(data, data[len - 1] ? len : len - 1); } } return value; }; auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { updateApplicationMenu(serviceName, menuObjectPath); return true; } setMenuAvailable(false); emit modelNeedsUpdate(); 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, 0, NET::WM2TransientFor).transientFor(); } if (updateMenuFromWindowIfHasMenu(id)) { return; } } #endif } QHash AppMenuModel::roleNames() const { QHash roleNames; roleNames[MenuRole] = "activeMenu"; roleNames[ActionRole] = "activeActions"; return roleNames; } QVariant AppMenuModel::data(const QModelIndex &index, int role) const { int row = index.row(); if (row < 0 ) { return QVariant(); } if (role == MenuRole) { return m_activeMenu.at(row); } else if(role == ActionRole) { const QVariant data = qVariantFromValue((void *) m_activeActions.at(row)); return data; } return QVariant(); } void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) { if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { if (m_importer) { QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); } return; } m_serviceName = serviceName; m_menuObjectPath = menuObjectPath; if (m_importer) { m_importer->deleteLater(); } - m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); + m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); - connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=] { + connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { m_menu = m_importer->menu(); - if (m_menu.isNull()) { + if (m_menu.isNull() || menu != m_menu) { return; } setMenuAvailable(true); emit modelNeedsUpdate(); }); } diff --git a/appmenu/appmenu.cpp b/appmenu/appmenu.cpp index ce3ef01d..366f8c7a 100644 --- a/appmenu/appmenu.cpp +++ b/appmenu/appmenu.cpp @@ -1,204 +1,204 @@ /* This file is part of the KDE project. Copyright (c) 2011 Lionel Chauvin Copyright (c) 2011,2012 Cédric Bellegarde Copyright (c) 2016 Kai Uwe Broulik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "appmenu.h" #include "kdbusimporter.h" #include "menuimporteradaptor.h" #include "appmenuadaptor.h" #include "appmenu_dbus.h" #include "verticalmenu.h" #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #include #endif static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); K_PLUGIN_FACTORY_WITH_JSON(AppMenuFactory, "appmenu.json", registerPlugin();) AppMenuModule::AppMenuModule(QObject* parent, const QList&) : KDEDModule(parent), m_appmenuDBus(new AppmenuDBus(this)) { reconfigure(); m_appmenuDBus->connectToBus(); connect(m_appmenuDBus, &AppmenuDBus::appShowMenu, this, &AppMenuModule::slotShowMenu); connect(m_appmenuDBus, &AppmenuDBus::reconfigured, this, &AppMenuModule::reconfigure); // transfer our signals to dbus connect(this, &AppMenuModule::showRequest, m_appmenuDBus, &AppmenuDBus::showRequest); connect(this, &AppMenuModule::menuHidden, m_appmenuDBus, &AppmenuDBus::menuHidden); connect(this, &AppMenuModule::menuShown, m_appmenuDBus, &AppmenuDBus::menuShown); QDBusConnection::sessionBus().connect({}, {}, QStringLiteral("com.canonical.dbusmenu"), QStringLiteral("ItemActivationRequested"), this, SLOT(itemActivationRequested(int,uint))); } AppMenuModule::~AppMenuModule() = default; void AppMenuModule::slotWindowRegistered(WId id, const QString &serviceName, const QDBusObjectPath &menuObjectPath) { #ifdef HAVE_X11 if (KWindowSystem::isPlatformX11()) { auto *c = QX11Info::connection(); static xcb_atom_t s_serviceNameAtom = XCB_ATOM_NONE; static xcb_atom_t s_objectPathAtom = XCB_ATOM_NONE; auto setWindowProperty = [c](WId id, xcb_atom_t &atom, const QByteArray &name, const QByteArray &value) { if (atom == XCB_ATOM_NONE) { const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, false, name.length(), name.constData()); QScopedPointer reply(xcb_intern_atom_reply(c, cookie, Q_NULLPTR)); if (reply.isNull()) { return; } atom = reply->atom; if (atom == XCB_ATOM_NONE) { return; } } xcb_change_property(c, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING, 8, value.length(), value.constData()); }; // TODO only set the property if it doesn't already exist setWindowProperty(id, s_serviceNameAtom, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8()); setWindowProperty(id, s_objectPathAtom, s_x11AppMenuObjectPathPropertyName, menuObjectPath.path().toUtf8()); } #endif } void AppMenuModule::slotShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId) { if (!m_menuImporter) { return; } // If menu visible, hide it if (m_menu && m_menu.data()->isVisible()) { m_menu.data()->hide(); return; } //dbus call by user (for khotkey shortcut) if (x == -1 || y == -1) { // We do not know kwin button position, so tell kwin to show menu emit showRequest(serviceName, menuObjectPath, actionId); return; } auto *importer = new KDBusMenuImporter(serviceName, menuObjectPath.path(), this); QMetaObject::invokeMethod(importer, "updateMenu", Qt::QueuedConnection); disconnect(importer, 0, this, 0); // ensure we don't popup multiple times in case the menu updates again later - connect(importer, &KDBusMenuImporter::menuUpdated, this, [=] { + connect(importer, &KDBusMenuImporter::menuUpdated, this, [=](QMenu *m) { QMenu *menu = importer->menu(); - if (!menu) { + if (!menu || menu != m) { return; } m_menu = qobject_cast(menu); m_menu.data()->setServiceName(serviceName); m_menu.data()->setMenuObjectPath(menuObjectPath); connect(m_menu.data(), &QMenu::aboutToHide, this, [this, importer] { hideMenu(); importer->deleteLater(); }); //m_menuImporter->fakeUnityAboutToShow(serviceName, menuObjectPath); m_menu.data()->popup(QPoint(x, y) / qApp->devicePixelRatio()); emit menuShown(serviceName, menuObjectPath); if (m_waitingAction) { m_menu.data()->setActiveAction(m_waitingAction); m_waitingAction = nullptr; } }); } void AppMenuModule::hideMenu() { if (m_menu) { emit menuHidden(m_menu.data()->serviceName(), m_menu->menuObjectPath()); } } void AppMenuModule::itemActivationRequested(int winId, uint action) { Q_UNUSED(winId); emit showRequest(message().service(), QDBusObjectPath(message().path()), action); } // reload settings void AppMenuModule::reconfigure() { m_waitingAction = nullptr; hideMenu(); // hide window decoration menu if exists KConfigGroup config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Appmenu Style")); const QString &menuStyle = config.readEntry("Style", "InApplication"); // TODO enum or Kconfigxt or what not? if (menuStyle == QLatin1String("InApplication")) { delete m_menuImporter; m_menuImporter = nullptr; return; } // Setup a menu importer if needed if (!m_menuImporter) { m_menuImporter = new MenuImporter(this); connect(m_menuImporter, &MenuImporter::WindowRegistered, this, &AppMenuModule::slotWindowRegistered); m_menuImporter->connectToBus(); } } #include "appmenu.moc" diff --git a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp index d835d50c..bd5f2069 100644 --- a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp @@ -1,494 +1,498 @@ /*************************************************************************** * * * Copyright (C) 2009 Marco Martin * * Copyright (C) 2009 Matthieu Gallien * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "statusnotifieritemsource.h" #include "systemtraytypes.h" #include "statusnotifieritemservice.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class PlasmaDBusMenuImporter : public DBusMenuImporter { public: PlasmaDBusMenuImporter(const QString &service, const QString &path, KIconLoader *iconLoader, QObject *parent) : DBusMenuImporter(service, path, parent) , m_iconLoader(iconLoader) {} protected: QIcon iconForName(const QString &name) override { return QIcon(new KIconEngine(name, m_iconLoader)); } private: KIconLoader *m_iconLoader; }; StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent) : Plasma::DataContainer(parent), m_customIconLoader(0), m_menuImporter(0), m_refreshing(false), m_needsReRefreshing(false), m_titleUpdate(true), m_iconUpdate(true), m_tooltipUpdate(true), m_statusUpdate(true) { setObjectName(notifierItemId); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); m_typeId = notifierItemId; m_name = notifierItemId; //set the initial values for all the things //this is important as Plasma::DataModel has an unsolvable bug //when it gets data with a new key it tries to update the QAIM roleNames //from QML this achieves absolutely nothing as there is no signal to tell QQmlDelegateModel to reload the roleNames in QQmlAdapatorModel //no matter if the row changes or the model refreshes //this means it does not re-evaluate what bindings exist (watchedRoleIds) - and we get properties that don't bind and thus system tray icons //by setting everything up-front so that we have all role names when we call the first checkForUpdate() setData(QStringLiteral("AttentionIcon"), QIcon()); setData(QStringLiteral("AttentionIconName"), QString()); setData(QStringLiteral("AttentionMovieName"), QString()); setData(QStringLiteral("Category"), QString()); setData(QStringLiteral("Icon"), QIcon()); setData(QStringLiteral("IconName"), QString()); setData(QStringLiteral("IconsChanged"), false); setData(QStringLiteral("IconThemePath"), QString()); setData(QStringLiteral("Id"), QString()); setData(QStringLiteral("ItemIsMenu"), false); setData(QStringLiteral("OverlayIconName"), QString()); setData(QStringLiteral("StatusChanged"), false); setData(QStringLiteral("Status"), QString()); setData(QStringLiteral("TitleChanged"), false); setData(QStringLiteral("Title"), QString()); setData(QStringLiteral("ToolTipChanged"), false); setData(QStringLiteral("ToolTipIcon"), QString()); setData(QStringLiteral("ToolTipSubTitle"), QString()); setData(QStringLiteral("ToolTipTitle"), QString()); setData(QStringLiteral("WindowId"), QVariant()); int slash = notifierItemId.indexOf('/'); if (slash == -1) { qWarning() << "Invalid notifierItemId:" << notifierItemId; m_valid = false; m_statusNotifierItemInterface = 0; return; } QString service = notifierItemId.left(slash); QString path = notifierItemId.mid(slash); m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this); m_refreshTimer.setSingleShot(true); m_refreshTimer.setInterval(10); connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh); m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid(); if (m_valid) { connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString))); refresh(); } } StatusNotifierItemSource::~StatusNotifierItemSource() { delete m_statusNotifierItemInterface; } KIconLoader *StatusNotifierItemSource::iconLoader() const { return m_customIconLoader ? m_customIconLoader : KIconLoader::global(); } Plasma::Service *StatusNotifierItemSource::createService() { return new StatusNotifierItemService(this); } void StatusNotifierItemSource::syncStatus(QString status) { setData(QStringLiteral("TitleChanged"), false); setData(QStringLiteral("IconsChanged"), false); setData(QStringLiteral("TooltipChanged"), false); setData(QStringLiteral("StatusChanged"), true); setData(QStringLiteral("Status"), status); checkForUpdate(); } void StatusNotifierItemSource::refreshTitle() { m_titleUpdate = true; refresh(); } void StatusNotifierItemSource::refreshIcons() { m_iconUpdate = true; refresh(); } void StatusNotifierItemSource::refreshToolTip() { m_tooltipUpdate = true; refresh(); } void StatusNotifierItemSource::refresh() { if (!m_refreshTimer.isActive()) { m_refreshTimer.start(); } } void StatusNotifierItemSource::performRefresh() { if (m_refreshing) { m_needsReRefreshing = true; return; } m_refreshing = true; QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), m_statusNotifierItemInterface->path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("GetAll")); message << m_statusNotifierItemInterface->interface(); QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback); } /** \todo add a smart pointer to guard call and to automatically delete it at the end of the function */ void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) { m_refreshing = false; if (m_needsReRefreshing) { m_needsReRefreshing = false; performRefresh(); call->deleteLater(); return; } QDBusPendingReply reply = *call; if (reply.isError()) { m_valid = false; } else { // record what has changed setData(QStringLiteral("TitleChanged"), m_titleUpdate); m_titleUpdate = false; setData(QStringLiteral("IconsChanged"), m_iconUpdate); m_iconUpdate = false; setData(QStringLiteral("ToolTipChanged"), m_tooltipUpdate); m_tooltipUpdate = false; setData(QStringLiteral("StatusChanged"), m_statusUpdate); m_statusUpdate = false; //IconThemePath (handle this one first, because it has an impact on //others) QVariantMap properties = reply.argumentAt<0>(); QString path = properties[QStringLiteral("IconThemePath")].toString(); if (!path.isEmpty() && path != data()[QStringLiteral("IconThemePath")].toString()) { if (!m_customIconLoader) { m_customIconLoader = new KIconLoader(QString(), QStringList(), this); } // FIXME: If last part of path is not "icons", this won't work! QString appName; auto tokens = path.splitRef('/', QString::SkipEmptyParts); if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons")) appName = tokens.takeLast().toString(); //icons may be either in the root directory of the passed path or in a appdir format //i.e hicolor/32x32/iconname.png m_customIconLoader->reconfigure(appName, QStringList(path)); //add app dir requires an app name, though this is completely unused in this context m_customIconLoader->addAppDir(appName.size() ? appName : QStringLiteral("unused"), path); } setData(QStringLiteral("IconThemePath"), path); setData(QStringLiteral("Category"), properties[QStringLiteral("Category")]); setData(QStringLiteral("Status"), properties[QStringLiteral("Status")]); setData(QStringLiteral("Title"), properties[QStringLiteral("Title")]); setData(QStringLiteral("Id"), properties[QStringLiteral("Id")]); setData(QStringLiteral("WindowId"), properties[QStringLiteral("WindowId")]); setData(QStringLiteral("ItemIsMenu"), properties[QStringLiteral("ItemIsMenu")]); //Attention Movie setData(QStringLiteral("AttentionMovieName"), properties[QStringLiteral("AttentionMovieName")]); QIcon overlay; QStringList overlayNames; //Icon { KDbusImageVector image; QIcon icon; QString iconName; properties[QStringLiteral("OverlayIconPixmap")].value() >> image; if (image.isEmpty()) { QString iconName = properties[QStringLiteral("OverlayIconName")].toString(); setData(QStringLiteral("OverlayIconName"), iconName); if (!iconName.isEmpty()) { overlayNames << iconName; overlay = QIcon(new KIconEngine(iconName, iconLoader())); } } else { overlay = imageVectorToPixmap(image); } properties[QStringLiteral("IconPixmap")].value() >> image; if (image.isEmpty()) { iconName = properties[QStringLiteral("IconName")].toString(); if (!iconName.isEmpty()) { icon = QIcon(new KIconEngine(iconName, iconLoader())); if (overlayNames.isEmpty() && !overlay.isNull()) { overlayIcon(&icon, &overlay); } } } else { icon = imageVectorToPixmap(image); if (!icon.isNull() && !overlay.isNull()) { overlayIcon(&icon, &overlay); } } setData(QStringLiteral("Icon"), icon); setData(QStringLiteral("IconName"), iconName); } //Attention icon { KDbusImageVector image; QIcon attentionIcon; properties[QStringLiteral("AttentionIconPixmap")].value() >> image; if (image.isEmpty()) { QString iconName = properties[QStringLiteral("AttentionIconName")].toString(); setData(QStringLiteral("AttentionIconName"), iconName); if (!iconName.isEmpty()) { attentionIcon = QIcon(new KIconEngine(iconName, iconLoader())); if (overlayNames.isEmpty() && !overlay.isNull()) { overlayIcon(&attentionIcon, &overlay); } } } else { attentionIcon = imageVectorToPixmap(image); if (!attentionIcon.isNull() && !overlay.isNull()) { overlayIcon(&attentionIcon, &overlay); } } setData(QStringLiteral("AttentionIcon"), attentionIcon); } //ToolTip { KDbusToolTipStruct toolTip; properties[QStringLiteral("ToolTip")].value() >> toolTip; if (toolTip.title.isEmpty()) { setData(QStringLiteral("ToolTipTitle"), QString()); setData(QStringLiteral("ToolTipSubTitle"), QString()); setData(QStringLiteral("ToolTipIcon"), QString()); } else { QIcon toolTipIcon; if (toolTip.image.size() == 0) { toolTipIcon = QIcon(new KIconEngine(toolTip.icon, iconLoader())); } else { toolTipIcon = imageVectorToPixmap(toolTip.image); } setData(QStringLiteral("ToolTipTitle"), toolTip.title); setData(QStringLiteral("ToolTipSubTitle"), toolTip.subTitle); if (toolTipIcon.isNull() || toolTipIcon.availableSizes().isEmpty()) { setData(QStringLiteral("ToolTipIcon"), QString()); } else { setData(QStringLiteral("ToolTipIcon"), toolTipIcon); } } } //Menu if (!m_menuImporter) { QString menuObjectPath = properties[QStringLiteral("Menu")].value().path(); if (!menuObjectPath.isEmpty()) { if (menuObjectPath == QLatin1String("/NO_DBUSMENU")) { // This is a hack to make it possible to disable DBusMenu in an // application. The string "/NO_DBUSMENU" must be the same as in // KStatusNotifierItem::setContextMenu(). qWarning() << "DBusMenu disabled for this application"; } else { m_menuImporter = new PlasmaDBusMenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, iconLoader(), this); - connect(m_menuImporter, SIGNAL(menuUpdated()), this, SLOT(contextMenuReady())); + connect(m_menuImporter, &PlasmaDBusMenuImporter::menuUpdated, this, [this](QMenu *menu) { + if (menu == m_menuImporter->menu()) { + contextMenuReady(); + } + }); } } } } checkForUpdate(); call->deleteLater(); } void StatusNotifierItemSource::contextMenuReady() { emit contextMenuReady(m_menuImporter->menu()); } QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const { //swap from network byte order if we are little endian if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { uint *uintBuf = (uint *) image.data.data(); for (uint i = 0; i < image.data.size()/sizeof(uint); ++i) { *uintBuf = ntohl(*uintBuf); ++uintBuf; } } QImage iconImage(image.width, image.height, QImage::Format_ARGB32 ); memcpy(iconImage.bits(), (uchar*)image.data.data(), iconImage.byteCount()); return QPixmap::fromImage(iconImage); } QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const { QIcon icon; for (int i = 0; ipixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall); QPainter p(&m_iconPixmap); const int size = KIconLoader::SizeSmall/2; p.drawPixmap(QRect(size, size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); //if an m_icon exactly that size wasn't found don't add it to the vector m_iconPixmap = icon->pixmap(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium); if (m_iconPixmap.width() == KIconLoader::SizeSmallMedium) { const int size = KIconLoader::SizeSmall/2; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } m_iconPixmap = icon->pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium); if (m_iconPixmap.width() == KIconLoader::SizeMedium) { const int size = KIconLoader::SizeSmall/2; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } m_iconPixmap = icon->pixmap(KIconLoader::SizeLarge, KIconLoader::SizeLarge); if (m_iconPixmap.width() == KIconLoader::SizeLarge) { const int size = KIconLoader::SizeSmall; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } // We can't do 'm_icon->addPixmap()' because if 'm_icon' uses KIconEngine, // it will ignore the added pixmaps. This is not a bug in KIconEngine, // QIcon::addPixmap() doc says: "Custom m_icon engines are free to ignore // additionally added pixmaps". *icon = tmp; //hopefully huge and enormous not necessary right now, since it's quite costly } void StatusNotifierItemSource::activate(int x, int y) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Activate"), x, y); } } void StatusNotifierItemSource::secondaryActivate(int x, int y) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y); } } void StatusNotifierItemSource::scroll(int delta, const QString &direction) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction); } } void StatusNotifierItemSource::contextMenu(int x, int y) { if (m_menuImporter) { m_menuImporter->updateMenu(); } else { qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y); } } } #include "statusnotifieritemsource.moc" diff --git a/libdbusmenuqt/dbusmenuimporter.cpp b/libdbusmenuqt/dbusmenuimporter.cpp index 9265212d..8737fb25 100644 --- a/libdbusmenuqt/dbusmenuimporter.cpp +++ b/libdbusmenuqt/dbusmenuimporter.cpp @@ -1,535 +1,539 @@ /* 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 // 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 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; using ActionForId = QMap; ActionForId m_actionForId; QTimer *m_pendingLayoutUpdateTimer; QSet m_idsRefreshedByAboutToShow; QSet m_pendingLayoutUpdates; QDBusPendingCallWatcher *refresh(int id) { QDBusPendingCall call = m_interface->asyncCall(QStringLiteral("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 { 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(QStringLiteral("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_pendingLayoutUpdateTimer = new QTimer(this); d->m_pendingLayoutUpdateTimer->setSingleShot(true); connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates); QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, QStringLiteral("LayoutUpdated"), QStringLiteral("ui"), this, SLOT(slotLayoutUpdated(uint, int))); QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, QStringLiteral("ItemsPropertiesUpdated"), QStringLiteral("a(ia{sv})a(ias)"), this, SLOT(slotItemsPropertiesUpdated(DBusMenuItemList, DBusMenuItemKeysList))); QDBusConnection::sessionBus().connect(service, path, DBUSMENU_INTERFACE, QStringLiteral("ItemActivationRequested"), QStringLiteral("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) { 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 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(); + QMenu *menu = d->menuForId(parentId); + QDBusPendingReply reply = *watcher; if (!reply.isValid()) { qWarning() << reply.error().message(); - - emit menuUpdated(); + if (menu) { + emit menuUpdated(menu); + } 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; } //remove outdated actions QSet 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)) { 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 (action->menu()) { auto menu = action->menu(); connect(menu, &QMenu::aboutToShow, this, [menu, this]() { updateMenu(menu); }); } 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); } } - emit menuUpdated(); + 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(); QDBusPendingCall call = d->m_interface->asyncCall(QStringLiteral("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(); + QMenu *menu = d->menuForId(id); + QDBusPendingReply reply = *watcher; if (reply.isError()) { - menuUpdated(); qWarning() << "Call to AboutToShow() failed:" << reply.error().message(); + if (menu) { + 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>(); - QMenu *menu = d->menuForId(id); - DMRETURN_IF_FAIL(menu); if (needRefresh || menu->actions().isEmpty()) { d->m_idsRefreshedByAboutToShow << id; d->refresh(id); - } else { - menuUpdated(); + } else if (menu) { + menuUpdated(menu); } } 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, QStringLiteral("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, QStringLiteral("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/libdbusmenuqt/dbusmenuimporter.h b/libdbusmenuqt/dbusmenuimporter.h index 9ab87c6e..5c4d7c46 100644 --- a/libdbusmenuqt/dbusmenuimporter.h +++ b/libdbusmenuqt/dbusmenuimporter.h @@ -1,111 +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. */ #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); ~DBusMenuImporter() override; /** * 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(); + 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 */