diff --git a/appmenu/appmenu.cpp b/appmenu/appmenu.cpp index 366f8c7af..64f054798 100644 --- a/appmenu/appmenu.cpp +++ b/appmenu/appmenu.cpp @@ -1,204 +1,209 @@ /* 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, [=](QMenu *m) { QMenu *menu = importer->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()); + QAction *actiontoActivate = importer->actionForId(actionId); + emit menuShown(serviceName, menuObjectPath); - if (m_waitingAction) { - m_menu.data()->setActiveAction(m_waitingAction); - m_waitingAction = nullptr; + if (actiontoActivate) { + m_menu.data()->setActiveAction(actiontoActivate); } }); } void AppMenuModule::hideMenu() { if (m_menu) { emit menuHidden(m_menu.data()->serviceName(), m_menu->menuObjectPath()); } } -void AppMenuModule::itemActivationRequested(int winId, uint action) +void AppMenuModule::itemActivationRequested(int actionId, uint timeStamp) { - Q_UNUSED(winId); - emit showRequest(message().service(), QDBusObjectPath(message().path()), action); + Q_UNUSED(timeStamp); + emit showRequest(message().service(), QDBusObjectPath(message().path()), actionId); } // 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("Decoration")) { + QDBusConnection::sessionBus().connect({}, {}, QStringLiteral("com.canonical.dbusmenu"), + QStringLiteral("ItemActivationRequested"), + this, SLOT(itemActivationRequested(int,uint))); + } else { + QDBusConnection::sessionBus().disconnect({}, {}, QStringLiteral("com.canonical.dbusmenu"), + QStringLiteral("ItemActivationRequested"), + this, SLOT(itemActivationRequested(int,uint))); + } + 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/appmenu/appmenu.h b/appmenu/appmenu.h index 6fd22bc45..3369c5120 100644 --- a/appmenu/appmenu.h +++ b/appmenu/appmenu.h @@ -1,99 +1,97 @@ /* 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. */ #ifndef APPMENUMODULE_H #define APPMENUMODULE_H #include #include #include "menuimporter.h" class QDBusPendingCallWatcher; class KDBusMenuImporter; class AppmenuDBus; class TopMenuBar; class VerticalMenu; class AppMenuModule : public KDEDModule, protected QDBusContext { Q_OBJECT public: AppMenuModule(QObject* parent, const QList& list); ~AppMenuModule() override; Q_SIGNALS: /** * We do not know where is menu decoration button, so tell kwin to show menu */ void showRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId); /** * This signal is emitted whenever popup menu/menubar is shown * Useful for decorations to know if menu button should look pressed */ void menuShown(const QString &service, const QDBusObjectPath &objectPath); /** * This signal is emitted whenever popup menu/menubar is hidden * Useful for decorations to know if menu button should be release */ void menuHidden(const QString &service, const QDBusObjectPath &objectPath); private Q_SLOTS: /** * A new window was registered to AppMenu * * For compatibility this will set the DBus service name and menu object path as properties * on the window so we keep working with clients that use the DBusMenu "properly". */ void slotWindowRegistered(WId id, const QString &serviceName, const QDBusObjectPath &menuObjectPath); /** * Show menu at QPoint(x,y) for DBus serviceName and menuObjectPath * if x or y == -1, show in application window */ void slotShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId); /** * Reconfigure module */ void reconfigure(); - void itemActivationRequested(int winId, uint action); + void itemActivationRequested(int actionId, uint timeStamp); private: void hideMenu(); void fakeUnityAboutToShow(const QString &service, const QDBusObjectPath &menuObjectPath); KDBusMenuImporter *getImporter(const QString &service, const QString &path); MenuImporter *m_menuImporter = nullptr; AppmenuDBus *m_appmenuDBus; QPointer m_menu; - - QAction *m_waitingAction = nullptr; }; #endif diff --git a/libdbusmenuqt/dbusmenuimporter.cpp b/libdbusmenuqt/dbusmenuimporter.cpp index 2aac5cde4..677ffb4f8 100644 --- a/libdbusmenuqt/dbusmenuimporter.cpp +++ b/libdbusmenuqt/dbusmenuimporter.cpp @@ -1,538 +1,543 @@ /* 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" // Generated #include "dbusmenu_interface.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_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; DBusMenuInterface *m_interface; QMenu *m_menu; using ActionForId = QMap; ActionForId m_actionForId; QTimer *m_pendingLayoutUpdateTimer; QSet m_idsRefreshedByAboutToShow; QSet 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 { 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) { 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 = 0; 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 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()); } } } +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 reply = *watcher; if (!reply.isValid()) { qWarning() << 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) { 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(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); } void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) { int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); watcher->deleteLater(); QMenu *menu = d->menuForId(id); if (!menu) { return; } QDBusPendingReply reply = *watcher; if (reply.isError()) { qWarning() << "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(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 6d3fd4f85..6f91ee916 100644 --- a/libdbusmenuqt/dbusmenuimporter.h +++ b/libdbusmenuqt/dbusmenuimporter.h @@ -1,110 +1,113 @@ /* 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 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; + + 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 */ diff --git a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml index 942d0476d..132980f0a 100644 --- a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml +++ b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml @@ -1,170 +1,170 @@ /* * Copyright 2013 Marco Martin * Copyright 2014 Sebastian Kügler * * 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 2.010-1301, USA. */ import QtQuick 2.0 import QtQuick.Controls.Private 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents MouseArea { id: wallpaperDelegate width: wallpapersGrid.cellWidth height: wallpapersGrid.cellHeight property alias color: backgroundRect.color property bool selected: (wallpapersGrid.currentIndex == index) opacity: model.pendingDeletion ? 0.5 : 1 hoverEnabled: true //note: this *doesn't* use system colors since it represent a //skeymorphic photograph rather than a widget Rectangle { id: background color: "white" anchors { fill: parent margins: units.smallSpacing } opacity: 0.8 Rectangle { id: backgroundRect color: cfg_Color anchors { fill: parent margins: units.smallSpacing * 2 } QIconItem { anchors.centerIn: parent width: units.iconSizes.large height: width icon: "view-preview" visible: !walliePreview.visible } QPixmapItem { id: walliePreview anchors.fill: parent visible: model.screenshot != null smooth: true pixmap: model.screenshot fillMode: { if (cfg_FillMode == Image.Stretch) { return QPixmapItem.Stretch; } else if (cfg_FillMode == Image.PreserveAspectFit) { return QPixmapItem.PreserveAspectFit; } else if (cfg_FillMode == Image.PreserveAspectCrop) { return QPixmapItem.PreserveAspectCrop; } else if (cfg_FillMode == Image.Tile) { return QPixmapItem.Tile; } else if (cfg_FillMode == Image.TileVertically) { return QPixmapItem.TileVertically; } else if (cfg_FillMode == Image.TileHorizontally) { return QPixmapItem.TileHorizontally; } - return QPixmapItem.Pad; + return QPixmapItem.PreserveAspectFit; } } PlasmaComponents.ToolButton { anchors { top: parent.top right: parent.right margins: units.smallSpacing } iconSource: "list-remove" tooltip: i18nd("plasma_applet_org.kde.image", "Remove wallpaper") flat: false visible: model.removable && !model.pendingDeletion onClicked: { imageWallpaper.wallpaperModel.setPendingDeletion(index, true); if (wallpapersGrid.currentIndex === index) { wallpapersGrid.currentIndex = (index + 1) % wallpapersGrid.count; } } opacity: wallpaperDelegate.containsMouse ? 1 : 0 Behavior on opacity { PropertyAnimation { duration: units.longDuration easing.type: Easing.OutQuad } } } PlasmaComponents.ToolButton { anchors { top: parent.top right: parent.right margins: units.smallSpacing } iconSource: "edit-undo" tooltip: i18nd("plasma_applet_org.kde.image", "Restore wallpaper") flat: false visible: model.pendingDeletion onClicked: imageWallpaper.wallpaperModel.setPendingDeletion(index, !model.pendingDeletion) opacity: wallpaperDelegate.containsMouse ? 1 : 0 Behavior on opacity { PropertyAnimation { duration: units.longDuration easing.type: Easing.OutQuad } } } } } Rectangle { opacity: selected ? 1.0 : 0 anchors.fill: background border.width: units.smallSpacing * 2 border.color: syspal.highlight color: "transparent" Behavior on opacity { PropertyAnimation { duration: units.longDuration easing.type: Easing.OutQuad } } } Timer { interval: 1000 // FIXME TODO: Use platform value for tooltip activation delay. running: wallpaperDelegate.containsMouse && !pressed && model.display onTriggered: { if (model.author) { Tooltip.showText(wallpaperDelegate, Qt.point(wallpaperDelegate.mouseX, wallpaperDelegate.mouseY), i18nd("plasma_applet_org.kde.image", "%1 by %2", model.display, model.author)); } else { Tooltip.showText(wallpaperDelegate, Qt.point(wallpaperDelegate.mouseX, wallpaperDelegate.mouseY), model.display); } } } onClicked: { wallpapersGrid.currentIndex = index wallpapersGrid.forceActiveFocus(); cfg_Image = model.path } onExited: Tooltip.hideText() }