diff --git a/applets/appmenu/lib/appmenuapplet.cpp b/applets/appmenu/lib/appmenuapplet.cpp index fd7b660d7..8b25ebc2b 100644 --- a/applets/appmenu/lib/appmenuapplet.cpp +++ b/applets/appmenu/lib/appmenuapplet.cpp @@ -1,296 +1,296 @@ /* * Copyright 2016 Kai Uwe Broulik * * 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 "appmenuapplet.h" #include "../plugin/appmenumodel.h" #include #include #include #include #include #include #include #include #include #include int AppMenuApplet::s_refs = 0; namespace { QString viewService() { return QStringLiteral("org.kde.kappmenuview"); } } AppMenuApplet::AppMenuApplet(QObject *parent, const QVariantList &data) : Plasma::Applet(parent, data) { ++s_refs; //if we're the first, regster the service if (s_refs == 1) { QDBusConnection::sessionBus().interface()->registerService(viewService(), QDBusConnectionInterface::QueueService, QDBusConnectionInterface::DontAllowReplacement); } /*it registers or unregisters the service when the destroyed value of the applet change, and not in the dtor, because: when we "delete" an applet, it just hides it for about a minute setting its status to destroyed, in order to be able to do a clean undo: if we undo, there will be another destroyedchanged and destroyed will be false. When this happens, if we are the only appmenu applet existing, the dbus interface will have to be registered again*/ - connect(this, &Applet::destroyedChanged, this, [this](bool destroyed) { + connect(this, &Applet::destroyedChanged, this, [](bool destroyed) { if (destroyed) { //if we were the last, unregister if (--s_refs == 0) { QDBusConnection::sessionBus().interface()->unregisterService(viewService()); } } else { //if we're the first, regster the service if (++s_refs == 1) { QDBusConnection::sessionBus().interface()->registerService(viewService(), QDBusConnectionInterface::QueueService, QDBusConnectionInterface::DontAllowReplacement); } } }); } AppMenuApplet::~AppMenuApplet() = default; void AppMenuApplet::init() { } AppMenuModel *AppMenuApplet::model() const { return m_model; } void AppMenuApplet::setModel(AppMenuModel *model) { if (m_model != model) { m_model = model; emit modelChanged(); } } int AppMenuApplet::view() const { return m_viewType; } void AppMenuApplet::setView(int type) { if (m_viewType != type) { m_viewType = type; emit viewChanged(); } } int AppMenuApplet::currentIndex() const { return m_currentIndex; } void AppMenuApplet::setCurrentIndex(int currentIndex) { if (m_currentIndex != currentIndex) { m_currentIndex = currentIndex; emit currentIndexChanged(); } } QQuickItem *AppMenuApplet::buttonGrid() const { return m_buttonGrid; } void AppMenuApplet::setButtonGrid(QQuickItem *buttonGrid) { if (m_buttonGrid != buttonGrid) { m_buttonGrid = buttonGrid; emit buttonGridChanged(); } } QMenu *AppMenuApplet::createMenu(int idx) const { QMenu *menu = nullptr; QAction *action = nullptr; if (view() == CompactView) { menu = new QMenu(); for (int i=0; irowCount(); i++) { const QModelIndex index = m_model->index(i, 0); const QVariant data = m_model->data(index, AppMenuModel::ActionRole); action = (QAction *)data.value(); menu->addAction(action); } menu->setAttribute(Qt::WA_DeleteOnClose); } else if (view() == FullView) { const QModelIndex index = m_model->index(idx, 0); const QVariant data = m_model->data(index, AppMenuModel::ActionRole); action = (QAction *)data.value(); if (action) { menu = action->menu(); } } return menu; } void AppMenuApplet::onMenuAboutToHide() { setCurrentIndex(-1); } void AppMenuApplet::trigger(QQuickItem *ctx, int idx) { if (m_currentIndex == idx) { return; } if (!ctx || !ctx->window() || !ctx->window()->screen()) { return; } QMenu *actionMenu = createMenu(idx); if (actionMenu) { //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [ctx]() { if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) { // FIXME event forge thing enters press and hold move mode :/ ctx->window()->mouseGrabberItem()->ungrabMouse(); } }; QTimer::singleShot(0, ctx, ungrabMouseHack); //end workaround const auto &geo = ctx->window()->screen()->availableVirtualGeometry(); QPoint pos = ctx->window()->mapToGlobal(ctx->mapToScene(QPointF()).toPoint()); if (location() == Plasma::Types::TopEdge) { pos.setY(pos.y() + ctx->height()); } actionMenu->adjustSize(); pos = QPoint(qBound(geo.x(), pos.x(), geo.x() + geo.width() - actionMenu->width()), qBound(geo.y(), pos.y(), geo.y() + geo.height() - actionMenu->height())); if (view() == FullView) { actionMenu->installEventFilter(this); } actionMenu->winId();//create window handle actionMenu->windowHandle()->setTransientParent(ctx->window()); actionMenu->popup(pos); if (view() == FullView) { // hide the old menu only after showing the new one to avoid brief flickering // in other windows as they briefly re-gain focus QMenu *oldMenu = m_currentMenu; m_currentMenu = actionMenu; if (oldMenu && oldMenu != actionMenu) { //don't initialize the currentIndex when another menu is already shown disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide); oldMenu->hide(); } } setCurrentIndex(idx); // FIXME TODO connect only once connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection); } else { // is it just an action without a menu? const QVariant data = m_model->index(idx, 0).data(AppMenuModel::ActionRole); QAction *action = static_cast(data.value()); if (action) { Q_ASSERT(!action->menu()); action->trigger(); } } } // FIXME TODO doesn't work on submenu bool AppMenuApplet::eventFilter(QObject *watched, QEvent *event) { auto *menu = qobject_cast(watched); if (!menu) { return false; } if (event->type() == QEvent::KeyPress) { auto *e = static_cast(event); // TODO right to left languages if (e->key() == Qt::Key_Left) { int desiredIndex = m_currentIndex - 1; emit requestActivateIndex(desiredIndex); return true; } else if (e->key() == Qt::Key_Right) { if (menu->activeAction() && menu->activeAction()->menu()) { return false; } int desiredIndex = m_currentIndex + 1; emit requestActivateIndex(desiredIndex); return true; } } else if (event->type() == QEvent::MouseMove) { auto *e = static_cast(event); if (!m_buttonGrid || !m_buttonGrid->window()) { return false; } // FIXME the panel margin breaks Fitt's law :( const QPointF &windowLocalPos = m_buttonGrid->window()->mapFromGlobal(e->globalPos()); const QPointF &buttonGridLocalPos = m_buttonGrid->mapFromScene(windowLocalPos); auto *item = m_buttonGrid->childAt(buttonGridLocalPos.x(), buttonGridLocalPos.y()); if (!item) { return false; } bool ok; const int buttonIndex = item->property("buttonIndex").toInt(&ok); if (!ok) { return false; } emit requestActivateIndex(buttonIndex); } return false; } K_EXPORT_PLASMA_APPLET_WITH_JSON(appmenu, AppMenuApplet, "metadata.json") #include "appmenuapplet.moc" diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp index 397838d75..ffeb9a342 100644 --- a/applets/appmenu/plugin/appmenumodel.cpp +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -1,405 +1,405 @@ /****************************************************************** * 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 #include #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"); #if HAVE_X11 static QHash s_atoms; #endif class KDBusMenuImporter : public DBusMenuImporter { public: KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) : DBusMenuImporter(service, path, parent) { } protected: QIcon iconForName(const QString &name) override { return QIcon::fromTheme(name); } }; AppMenuModel::AppMenuModel(QObject *parent) : QAbstractListModel(parent), m_serviceWatcher(new QDBusServiceWatcher(this)) { connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); connect(KWindowSystem::self() , static_cast(&KWindowSystem::windowChanged) , this , &AppMenuModel::onWindowChanged); connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { if (!m_updatePending) { m_updatePending = true; QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } }); connect(this, &AppMenuModel::screenGeometryChanged, this, [this] { onWindowChanged(m_currentWindowId); }); onActiveWindowChanged(KWindowSystem::activeWindow()); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); //if our current DBus connection gets lost, close the menu //we'll select the new menu when the focus changes connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) { if (serviceName == m_serviceName) { setMenuAvailable(false); emit modelNeedsUpdate(); } }); } AppMenuModel::~AppMenuModel() = default; bool AppMenuModel::menuAvailable() const { return m_menuAvailable; } void AppMenuModel::setMenuAvailable(bool set) { if (m_menuAvailable != set) { m_menuAvailable = set; setVisible(true); emit menuAvailableChanged(); } } bool AppMenuModel::visible() const { return m_visible; } void AppMenuModel::setVisible(bool visible) { if (m_visible != visible) { m_visible = visible; emit visibleChanged(); } } QRect AppMenuModel::screenGeometry() const { return m_screenGeometry; } void AppMenuModel::setScreenGeometry(QRect geometry) { if (m_screenGeometry == geometry) { return; } m_screenGeometry = geometry; emit screenGeometryChanged(); } int AppMenuModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (!m_menuAvailable || !m_menu) { return 0; } return m_menu->actions().count(); } void AppMenuModel::update() { beginResetModel(); endResetModel(); m_updatePending = false; } void AppMenuModel::onActiveWindowChanged(WId id) { qApp->removeNativeEventFilter(this); if (!id) { setMenuAvailable(false); emit modelNeedsUpdate(); return; } #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { auto *c = QX11Info::connection(); - auto getWindowPropertyString = [c, this](WId id, const QByteArray &name) -> QByteArray { + auto getWindowPropertyString = [c](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, 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, nullptr)); if (propertyReply.isNull()) { return value; } if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) { const char *data = (const char *) xcb_get_property_value(propertyReply.data()); int len = propertyReply->value_len; if (data) { value = QByteArray(data, data[len - 1] ? len : len - 1); } } return value; }; auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { updateApplicationMenu(serviceName, menuObjectPath); return true; } return false; }; KWindowInfo info(id, NET::WMState | NET::WMWindowType, NET::WM2TransientFor); if (info.hasState(NET::SkipTaskbar) || info.windowType(NET::UtilityMask) == NET::Utility || info.windowType(NET::DesktopMask) == NET::Desktop) { return; } m_currentWindowId = id; WId transientId = info.transientFor(); // lok at transient windows first while (transientId) { if (updateMenuFromWindowIfHasMenu(transientId)) { setVisible(true); return; } transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor(); } if (updateMenuFromWindowIfHasMenu(id)) { setVisible(true); return; } // monitor whether an app menu becomes available later // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) qApp->installNativeEventFilter(this); m_delayedMenuWindowId = id; //no menu found, set it to unavailable setMenuAvailable(false); emit modelNeedsUpdate(); } #endif } void AppMenuModel::onWindowChanged(WId id) { if (m_currentWindowId == id) { KWindowInfo info(id, NET::WMState | NET::WMGeometry); //! HACK: if the user has enabled screen scaling under X11 environment //! then the window and screen geometries can not be trusted for comparison //! before windows coordinates be adjusted properly. //! BUG: 404500 QPoint windowCenter = info.geometry().center(); if (KWindowSystem::isPlatformX11()) { windowCenter /= qApp->devicePixelRatio(); } const bool contained = m_screenGeometry.isNull() || m_screenGeometry.contains(windowCenter); setVisible(contained && !info.isMinimized()); } } QHash AppMenuModel::roleNames() const { QHash roleNames; roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); roleNames[ActionRole] = QByteArrayLiteral("activeActions"); return roleNames; } QVariant AppMenuModel::data(const QModelIndex &index, int role) const { const int row = index.row(); if (row < 0 || !m_menuAvailable || !m_menu) { return QVariant(); } const auto actions = m_menu->actions(); if (row >= actions.count()) { return QVariant(); } if (role == MenuRole) { // TODO this should be Qt::DisplayRole return actions.at(row)->text(); } else if (role == ActionRole) { return QVariant::fromValue((void *) actions.at(row)); } return QVariant(); } void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) { if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { if (m_importer) { QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); } return; } m_serviceName = serviceName; m_serviceWatcher->setWatchedServices(QStringList({m_serviceName})); m_menuObjectPath = menuObjectPath; if (m_importer) { m_importer->deleteLater(); } m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { m_menu = m_importer->menu(); if (m_menu.isNull() || menu != m_menu) { return; } //cache first layer of sub menus, which we'll be popping up for(QAction *a: m_menu->actions()) { // signal dataChanged when the action changes connect(a, &QAction::changed, this, [this, a] { if (m_menuAvailable && m_menu) { const int actionIdx = m_menu->actions().indexOf(a); if (actionIdx > -1) { const QModelIndex modelIdx = index(actionIdx, 0); emit dataChanged(modelIdx, modelIdx); } } }); connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); if (a->menu()) { m_importer->updateMenu(a->menu()); } } setMenuAvailable(true); emit modelNeedsUpdate(); }); connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { // TODO submenus if (!m_menuAvailable || !m_menu) { return; } const auto actions = m_menu->actions(); auto it = std::find(actions.begin(), actions.end(), action); if (it != actions.end()) { requestActivateIndex(it - actions.begin()); } }); } bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") { return false; } #if HAVE_X11 auto e = static_cast(message); const uint8_t type = e->response_type & ~0x80; if (type == XCB_PROPERTY_NOTIFY) { auto *event = reinterpret_cast(e); if (event->window == m_delayedMenuWindowId) { auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen if (event->atom == serviceNameAtom || event->atom == objectPathAtom) { // see if we now have a menu onActiveWindowChanged(KWindowSystem::activeWindow()); } } } } #else Q_UNUSED(message); #endif return false; } diff --git a/applets/icon/iconapplet.cpp b/applets/icon/iconapplet.cpp index e745b1712..dec37e37c 100644 --- a/applets/icon/iconapplet.cpp +++ b/applets/icon/iconapplet.cpp @@ -1,579 +1,579 @@ /* * Copyright 2016 Kai Uwe Broulik * * 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 "iconapplet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include IconApplet::IconApplet(QObject *parent, const QVariantList &data) : Plasma::Applet(parent, data) { } IconApplet::~IconApplet() { // in a handler connected to IconApplet::appletDeleted m_localPath will be empty?! if (destroyed()) { QFile::remove(m_localPath); } } void IconApplet::init() { populate(); } void IconApplet::configChanged() { populate(); } void IconApplet::populate() { m_url = config().readEntry(QStringLiteral("url"), QUrl()); if (!m_url.isValid()) { // the old applet that used a QML plugin and stored its url // in plasmoid.configuration.url had its entries stored in [Configuration][General] // so we look here as well to provide an upgrade path m_url = config().group("General").readEntry(QStringLiteral("url"), QUrl()); } // our backing desktop file already exists? just read all the things from it const QString path = localPath(); if (QFileInfo::exists(path)) { populateFromDesktopFile(path); return; } if (!m_url.isValid()) { // invalid url, use dummy data populateFromDesktopFile(QString()); return; } const QString plasmaIconsFolderPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_icons"); if (!QDir().mkpath(plasmaIconsFolderPath)) { setLaunchErrorMessage(i18n("Failed to create icon widgets folder '%1'", plasmaIconsFolderPath)); return; } setBusy(true); // unset in populateFromDesktopFile where we'll end up in if all goes well auto *statJob = KIO::stat(m_url, KIO::HideProgressInfo); connect(statJob, &KIO::StatJob::finished, this, [=] { QString desiredDesktopFileName = m_url.fileName(); // in doubt, just encode the entire URL, e.g. http://www.kde.org/ has no filename if (desiredDesktopFileName.isEmpty()) { desiredDesktopFileName = KIO::encodeFileName(m_url.toDisplayString()); } // We always want it to be a .desktop file (e.g. also for the "Type=Link" at the end) if (!desiredDesktopFileName.endsWith(QLatin1String(".desktop"))) { desiredDesktopFileName.append(QLatin1String(".desktop")); } QString backingDesktopFile = plasmaIconsFolderPath + QLatin1Char('/'); // KFileUtils::suggestName always appends a suffix, i.e. it expects that we already know the file already exists if (QFileInfo::exists(backingDesktopFile + desiredDesktopFileName)) { desiredDesktopFileName = KFileUtils::suggestName(QUrl::fromLocalFile(plasmaIconsFolderPath), desiredDesktopFileName); } backingDesktopFile.append(desiredDesktopFileName); QString name; // ends up as "Name" in the .desktop file for "Link" files below const QUrl url = statJob->mostLocalUrl(); if (url.isLocalFile()) { const QString localUrlString = url.toLocalFile(); // if desktop file just copy it over if (KDesktopFile::isDesktopFile(localUrlString)) { // if this restriction is set, KIO won't allow running desktop files from outside // registered services, applications, and so on, in this case we'll use the original // .desktop file and lose the ability to customize it if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { populateFromDesktopFile(localUrlString); // we don't call setLocalPath here as we don't want to store localPath to be a system-location // so that the fact that we cannot edit is re-evaluated every time return; } if (!QFile::copy(localUrlString, backingDesktopFile)) { setLaunchErrorMessage(i18n("Failed to copy icon widget desktop file from '%1' to '%2'", localUrlString, backingDesktopFile)); setBusy(false); return; } // set executable flag on the desktop file so KIO doesn't complain about executing it QFile file(backingDesktopFile); file.setPermissions(file.permissions() | QFile::ExeOwner); populateFromDesktopFile(backingDesktopFile); setLocalPath(backingDesktopFile); return; } } // in all other cases just make it a link QString iconName; QString genericName; if (!statJob->error()) { KFileItem item(statJob->statResult(), url); if (name.isEmpty()) { name = item.text(); } if (item.mimetype() != QLatin1String("application/octet-stream")) { iconName = item.iconName(); genericName = item.mimeComment(); } } // KFileItem might return "." as text for e.g. root folders if (name == QLatin1Char('.')) { name.clear(); } if (name.isEmpty()) { name = url.fileName(); } if (name.isEmpty()) { // TODO would be cool to just show the parent folder name instead of the full path name = url.path(); } // For websites the filename e.g. "index.php" is usually not what you want // also "/" isn't very descript when it's not our local "root" folder if (name.isEmpty() || url.scheme().startsWith(QLatin1String("http")) || (!url.isLocalFile() && name == QLatin1String("/"))) { name = url.host(); } if (iconName.isEmpty()) { // In doubt ask KIO::iconNameForUrl, KFileItem can't cope with http:// URLs for instance iconName = KIO::iconNameForUrl(url); } bool downloadFavIcon = false; if (url.scheme().startsWith(QLatin1String("http"))) { const QString favIcon = KIO::favIconForUrl(url); if (!favIcon.isEmpty()) { iconName = favIcon; } else { downloadFavIcon = true; } } KDesktopFile linkDesktopFile(backingDesktopFile); auto desktopGroup = linkDesktopFile.desktopGroup(); desktopGroup.writeEntry(QStringLiteral("Name"), name); desktopGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Link")); desktopGroup.writeEntry(QStringLiteral("URL"), url); desktopGroup.writeEntry(QStringLiteral("Icon"), iconName); if (!genericName.isEmpty()) { desktopGroup.writeEntry(QStringLiteral("GenericName"), genericName); } linkDesktopFile.sync(); populateFromDesktopFile(backingDesktopFile); setLocalPath(backingDesktopFile); if (downloadFavIcon) { KIO::FavIconRequestJob *job = new KIO::FavIconRequestJob(m_url); connect(job, &KIO::FavIconRequestJob::result, this, [job, backingDesktopFile, this](KJob *){ if (!job->error()) { KDesktopFile(backingDesktopFile).desktopGroup().writeEntry(QStringLiteral("Icon"), job->iconFile()); m_iconName = job->iconFile(); emit iconNameChanged(m_iconName); } }); } }); } void IconApplet::populateFromDesktopFile(const QString &path) { // path empty? just set icon to "unknown" and call it a day if (path.isEmpty()) { setIconName({}); return; } KDesktopFile desktopFile(path); const QString &name = desktopFile.readName(); if (m_name != name) { m_name = name; emit nameChanged(name); } const QString &genericName = desktopFile.readGenericName(); if (m_genericName != genericName) { m_genericName = genericName; emit genericNameChanged(genericName); } setIconName(desktopFile.readIcon()); delete m_openContainingFolderAction; m_openContainingFolderAction = nullptr; m_openWithActions.clear(); m_jumpListActions.clear(); m_localPath = path; setBusy(false); } QUrl IconApplet::url() const { return m_url; } void IconApplet::setUrl(const QUrl &url) { if (m_url != url) { m_url = url; urlChanged(url); config().writeEntry(QStringLiteral("url"), url); populate(); } } void IconApplet::setIconName(const QString &iconName) { const QString newIconName = (!iconName.isEmpty() ? iconName : QStringLiteral("unknown")); if (m_iconName != newIconName) { m_iconName = newIconName; emit iconNameChanged(newIconName); } } QString IconApplet::name() const { return m_name; } QString IconApplet::iconName() const { return m_iconName; } QString IconApplet::genericName() const { return m_genericName; } QList IconApplet::contextualActions() { QList actions; if (m_localPath.isEmpty()) { return actions; } KDesktopFile desktopFile(m_localPath); if (m_jumpListActions.isEmpty()) { const QStringList actions = desktopFile.readActions(); for (const QString &actionName : actions) { const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); if (!actionGroup.isValid() || !actionGroup.exists()) { continue; } const QString name = actionGroup.readEntry(QStringLiteral("Name")); const QString exec = actionGroup.readEntry(QStringLiteral("Exec")); if (name.isEmpty() || exec.isEmpty()) { continue; } QAction *action = new QAction(QIcon::fromTheme(actionGroup.readEntry("Icon")), name, this); connect(action, &QAction::triggered, this, [this, exec] { KRun::run(exec, {}, nullptr, m_name, m_iconName); }); m_jumpListActions << action; } } actions << m_jumpListActions; if (!actions.isEmpty()) { if (!m_separatorAction) { m_separatorAction = new QAction(this); m_separatorAction->setSeparator(true); } actions << m_separatorAction; } if (desktopFile.hasLinkType()) { const QUrl linkUrl = QUrl(desktopFile.readUrl()); if (m_openWithActions.isEmpty()) { if (!m_fileItemActions) { m_fileItemActions = new KFileItemActions(this); } KFileItemListProperties itemProperties(KFileItemList({KFileItem(linkUrl)})); m_fileItemActions->setItemListProperties(itemProperties); if (!m_openWithMenu) { m_openWithMenu.reset(new QMenu()); } m_openWithMenu->clear(); m_fileItemActions->addOpenWithActionsTo(m_openWithMenu.data()); m_openWithActions = m_openWithMenu->actions(); } if (!m_openContainingFolderAction) { if (KProtocolManager::supportsListing(linkUrl)) { m_openContainingFolderAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), this); - connect(m_openContainingFolderAction, &QAction::triggered, this, [this, linkUrl] { + connect(m_openContainingFolderAction, &QAction::triggered, this, [ linkUrl] { KIO::highlightInFileManager({linkUrl}); }); } } } actions << m_openWithActions; if (m_openContainingFolderAction) { actions << m_openContainingFolderAction; } return actions; } void IconApplet::run() { if (!m_startupTasksModel) { m_startupTasksModel = new TaskManager::StartupTasksModel(this); auto handleRow = [this](bool busy, const QModelIndex &parent, int first, int last) { Q_UNUSED(parent); for (int i = first; i <= last; ++i) { const QModelIndex idx = m_startupTasksModel->index(i, 0); if (idx.data(TaskManager::AbstractTasksModel::LauncherUrlWithoutIcon).toUrl() == QUrl::fromLocalFile(m_localPath)) { setBusy(busy); break; } } }; using namespace std::placeholders; connect(m_startupTasksModel, &QAbstractItemModel::rowsInserted, this, std::bind(handleRow, true /*busy*/, _1, _2, _3)); connect(m_startupTasksModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, std::bind(handleRow, false /*busy*/, _1, _2, _3)); } new KRun(QUrl::fromLocalFile(m_localPath), QApplication::desktop()); } void IconApplet::processDrop(QObject *dropEvent) { Q_ASSERT(dropEvent); Q_ASSERT(isAcceptableDrag(dropEvent)); const auto &urls = urlsFromDrop(dropEvent); if (urls.isEmpty()) { return; } const QString &localPath = m_url.toLocalFile(); if (KDesktopFile::isDesktopFile(localPath)) { KRun::runService(KService(localPath), urls, nullptr); return; } QMimeDatabase db; const QMimeType mimeType = db.mimeTypeForUrl(m_url); if (isExecutable(mimeType)) { // isAcceptableDrag has the KAuthorized check for this QProcess::startDetached(m_url.toLocalFile(), QUrl::toStringList(urls)); return; } if (mimeType.inherits(QStringLiteral("inode/directory"))) { QMimeData mimeData; mimeData.setUrls(urls); // DeclarativeDropEvent isn't public QDropEvent de(QPointF(dropEvent->property("x").toInt(), dropEvent->property("y").toInt()), static_cast(dropEvent->property("proposedActions").toInt()), &mimeData, static_cast(dropEvent->property("buttons").toInt()), static_cast(dropEvent->property("modifiers").toInt())); KIO::DropJob *dropJob = KIO::drop(&de, m_url); KJobWidgets::setWindow(dropJob, QApplication::desktop()); return; } } bool IconApplet::isAcceptableDrag(QObject *dropEvent) { Q_ASSERT(dropEvent); const auto &urls = urlsFromDrop(dropEvent); if (urls.isEmpty()) { return false; } const QString &localPath = m_url.toLocalFile(); if (KDesktopFile::isDesktopFile(localPath)) { return true; } QMimeDatabase db; const QMimeType mimeType = db.mimeTypeForUrl(m_url); if (KAuthorized::authorize(QStringLiteral("shell_access")) && isExecutable(mimeType)) { return true; } if (mimeType.inherits(QStringLiteral("inode/directory"))) { return true; } return false; } QList IconApplet::urlsFromDrop(QObject *dropEvent) { // DeclarativeDropEvent and co aren't public const QObject *mimeData = qvariant_cast(dropEvent->property("mimeData")); Q_ASSERT(mimeData); const QJsonArray &droppedUrls = mimeData->property("urls").toJsonArray(); QList urls; urls.reserve(droppedUrls.count()); for (const QJsonValue &droppedUrl : droppedUrls) { const QUrl url(droppedUrl.toString()); if (url.isValid()) { urls.append(url); } } return urls; } bool IconApplet::isExecutable(const QMimeType &mimeType) { return (mimeType.inherits(QStringLiteral("application/x-executable")) || mimeType.inherits(QStringLiteral("application/x-shellscript"))); } void IconApplet::configure() { KPropertiesDialog *dialog = m_configDialog.data(); if (dialog) { dialog->show(); dialog->raise(); return; } dialog = new KPropertiesDialog(QUrl::fromLocalFile(m_localPath)); m_configDialog = dialog; connect(dialog, &KPropertiesDialog::applied, this, [this] { KDesktopFile desktopFile(m_localPath); if (desktopFile.hasLinkType()) { const QUrl newUrl(desktopFile.readUrl()); if (m_url != newUrl) { // make sure to fully repopulate in case the user changed the Link URL QFile::remove(m_localPath); setUrl(newUrl); // calls populate() itself, but only if it changed return; } } populate(); }); dialog->setAttribute(Qt::WA_DeleteOnClose, true); dialog->setFileNameReadOnly(true); dialog->setWindowTitle(i18n("Properties for %1", m_name)); dialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); dialog->show(); } QString IconApplet::localPath() const { return config().readEntry(QStringLiteral("localPath")); } void IconApplet::setLocalPath(const QString &localPath) { m_localPath = localPath; config().writeEntry(QStringLiteral("localPath"), localPath); } K_EXPORT_PLASMA_APPLET_WITH_JSON(icon, IconApplet, "metadata.json") #include "iconapplet.moc" diff --git a/applets/kicker/plugin/forwardingmodel.cpp b/applets/kicker/plugin/forwardingmodel.cpp index a0cb86a8f..96ec8878d 100644 --- a/applets/kicker/plugin/forwardingmodel.cpp +++ b/applets/kicker/plugin/forwardingmodel.cpp @@ -1,265 +1,265 @@ /*************************************************************************** * Copyright (C) 2015 by Eike Hein * * * * 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 "forwardingmodel.h" ForwardingModel::ForwardingModel(QObject *parent) : AbstractModel(parent) { } ForwardingModel::~ForwardingModel() { } QString ForwardingModel::description() const { if (!m_sourceModel) { return QString(); } AbstractModel *abstractModel = qobject_cast(m_sourceModel); if (!abstractModel) { return QString(); } return abstractModel->description(); } QAbstractItemModel *ForwardingModel::sourceModel() const { return m_sourceModel; } void ForwardingModel::setSourceModel(QAbstractItemModel *sourceModel) { disconnectSignals(); beginResetModel(); m_sourceModel = sourceModel; connectSignals(); endResetModel(); emit countChanged(); emit sourceModelChanged(); emit descriptionChanged(); } bool ForwardingModel::canFetchMore(const QModelIndex &parent) const { if (!m_sourceModel) { return false; } return m_sourceModel->canFetchMore(indexToSourceIndex(parent)); } void ForwardingModel::fetchMore(const QModelIndex &parent) { if (m_sourceModel) { m_sourceModel->fetchMore(indexToSourceIndex(parent)); } } QModelIndex ForwardingModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent) if (!m_sourceModel) { return QModelIndex(); } return createIndex(row, column); } QModelIndex ForwardingModel::parent(const QModelIndex &index) const { Q_UNUSED(index) return QModelIndex(); } QVariant ForwardingModel::data(const QModelIndex &index, int role) const { if (!m_sourceModel) { return QVariant(); } return m_sourceModel->data(indexToSourceIndex(index), role); } int ForwardingModel::rowCount(const QModelIndex &parent) const { if (!m_sourceModel) { return 0; } return m_sourceModel->rowCount(indexToSourceIndex(parent)); } QModelIndex ForwardingModel::indexToSourceIndex(const QModelIndex& index) const { if (!m_sourceModel || !index.isValid()) { return QModelIndex(); } return m_sourceModel->index(index.row(), index.column(), index.parent().isValid() ? indexToSourceIndex(index.parent()) : QModelIndex()); } bool ForwardingModel::trigger(int row, const QString &actionId, const QVariant &argument) { if (!m_sourceModel) { return false; } AbstractModel *abstractModel = qobject_cast(m_sourceModel); if (!abstractModel) { return false; } return abstractModel->trigger(row, actionId, argument); } QString ForwardingModel::labelForRow(int row) { if (!m_sourceModel) { return QString(); } AbstractModel *abstractModel = qobject_cast(m_sourceModel); if (!abstractModel) { return QString(); } return abstractModel->labelForRow(row); } AbstractModel* ForwardingModel::modelForRow(int row) { if (!m_sourceModel) { return nullptr; } AbstractModel *abstractModel = qobject_cast(m_sourceModel); if (!abstractModel) { return nullptr; } return abstractModel->modelForRow(row); } AbstractModel* ForwardingModel::favoritesModel() { AbstractModel *sourceModel = qobject_cast(m_sourceModel); if (sourceModel) { return sourceModel->favoritesModel(); } return AbstractModel::favoritesModel(); } int ForwardingModel::separatorCount() const { if (!m_sourceModel) { return 0; } AbstractModel *abstractModel = qobject_cast(m_sourceModel); if (!abstractModel) { return 0; } return abstractModel->separatorCount(); } void ForwardingModel::reset() { beginResetModel(); endResetModel(); emit countChanged(); emit separatorCountChanged(); } void ForwardingModel::connectSignals() { if (!m_sourceModel) { return; } connect(m_sourceModel, SIGNAL(destroyed()), this, SLOT(reset())); - connect(m_sourceModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), - this, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), + connect(m_sourceModel.data(), &QAbstractItemModel::dataChanged, + this, &QAbstractItemModel::dataChanged, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), - this, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeInserted, + this, &QAbstractItemModel::rowsAboutToBeInserted, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), - this, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeMoved, + this, &QAbstractItemModel::rowsAboutToBeMoved, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), - this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QAbstractItemModel::rowsAboutToBeRemoved, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SIGNAL(layoutAboutToBeChanged(QList,QAbstractItemModel::LayoutChangeHint)), + connect(m_sourceModel.data(), &QAbstractItemModel::layoutAboutToBeChanged, + this, &QAbstractItemModel::layoutAboutToBeChanged, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SIGNAL(rowsInserted(QModelIndex,int,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsInserted, + this, &QAbstractItemModel::rowsInserted, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SIGNAL(countChanged()), Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsInserted, + this, &AbstractModel::countChanged, Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::rowsMoved, + this, &QAbstractItemModel::rowsMoved, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SIGNAL(rowsRemoved(QModelIndex,int,int)), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsRemoved, + this, &QAbstractItemModel::rowsRemoved, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SIGNAL(countChanged()), Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(modelAboutToBeReset()), - this, SIGNAL(modelAboutToBeReset()), + connect(m_sourceModel.data(), &QAbstractItemModel::rowsRemoved, + this, &AbstractModel::countChanged, Qt::UniqueConnection); + connect(m_sourceModel.data(), &QAbstractItemModel::modelAboutToBeReset, + this, &QAbstractItemModel::modelAboutToBeReset, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(modelReset()), - this, SIGNAL(modelReset()), + connect(m_sourceModel.data(), &QAbstractItemModel::modelReset, + this, &QAbstractItemModel::modelReset, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(modelReset()), - this, SIGNAL(countChanged()), + connect(m_sourceModel.data(), &QAbstractItemModel::modelReset, + this, &AbstractModel::countChanged, Qt::UniqueConnection); - connect(m_sourceModel, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), - this, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + connect(m_sourceModel.data(), &QAbstractItemModel::layoutChanged, + this, &QAbstractItemModel::layoutChanged, Qt::UniqueConnection); } void ForwardingModel::disconnectSignals() { if (!m_sourceModel) { return; } disconnect(m_sourceModel, nullptr, this, nullptr); } diff --git a/applets/kicker/plugin/recentcontactsmodel.cpp b/applets/kicker/plugin/recentcontactsmodel.cpp index 5b875b59e..7f3396647 100644 --- a/applets/kicker/plugin/recentcontactsmodel.cpp +++ b/applets/kicker/plugin/recentcontactsmodel.cpp @@ -1,246 +1,246 @@ /*************************************************************************** * Copyright (C) 2012 by Aurélien Gâteau * * Copyright (C) 2014-2015 by Eike Hein * * * * 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 "recentcontactsmodel.h" #include "actionlist.h" #include "contactentry.h" #include #include #include #include #include //FIXME TODO: Pretty include in KPeople broken. #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; RecentContactsModel::RecentContactsModel(QObject *parent) : ForwardingModel(parent) { refresh(); } RecentContactsModel::~RecentContactsModel() { } QString RecentContactsModel::description() const { return i18n("Contacts"); } QVariant RecentContactsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } QString id = sourceModel()->data(index, ResultModel::ResourceRole).toString(); KPeople::PersonData *data = nullptr; if (m_idToData.contains(id)) { data = m_idToData[id]; } if (!data) { const_cast(this)->insertPersonData(id, index.row()); return QVariant(); } if (role == Qt::DisplayRole) { return data->name(); } else if (role == Qt::DecorationRole) { return data->presenceIconName(); } else if (role == Kicker::FavoriteIdRole) { return id; } else if (role == Kicker::HasActionListRole) { return true; } else if (role == Kicker::ActionListRole) { QVariantList actionList ; const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Contact"), QStringLiteral("forget")); actionList << forgetAction; const QVariantMap &forgetAllAction = Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("forgetAll")); actionList << forgetAllAction; actionList << Kicker::createSeparatorActionItem(); actionList << Kicker::createActionItem(i18n("Show Contact Information..."), QStringLiteral("showContactInfo")); return actionList; } else if (role == Kicker::DescriptionRole) { return QString(); } return QVariant(); } bool RecentContactsModel::trigger(int row, const QString &actionId, const QVariant &argument) { Q_UNUSED(argument) bool withinBounds = row >= 0 && row < rowCount(); if (actionId.isEmpty() && withinBounds) { QString id = sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString(); const QList actionList = KPeople::actionsForPerson(id, this); if (!actionList.isEmpty()) { QAction *chat = nullptr; foreach (QAction *action, actionList) { const QVariant &actionType = action->property("actionType"); if (!actionType.isNull() && actionType.toInt() == KPeople::ActionType::TextChatAction) { chat = action; } } if (chat) { chat->trigger(); return true; } } return false; } else if (actionId == QLatin1String("showContactInfo") && withinBounds) { ContactEntry::showPersonDetailsDialog(sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString()); } else if (actionId == QLatin1String("forget") && withinBounds) { if (sourceModel()) { ResultModel *resultModel = static_cast(sourceModel()); resultModel->forgetResource(row); } return false; } else if (actionId == QLatin1String("forgetAll")) { if (sourceModel()) { ResultModel *resultModel = static_cast(sourceModel()); resultModel->forgetAllResources(); } return false; } return false; } bool RecentContactsModel::hasActions() const { return rowCount(); } QVariantList RecentContactsModel::actions() const { QVariantList actionList; if (rowCount()) { actionList << Kicker::createActionItem(i18n("Forget All Contacts"), QStringLiteral("forgetAll")); } return actionList; } void RecentContactsModel::refresh() { QObject *oldModel = sourceModel(); auto query = UsedResources | RecentlyUsedFirst | Agent(QStringLiteral("KTp")) | Type::any() | Activity::current() | Url::startsWith(QStringLiteral("ktp")) | Limit(15); ResultModel *model = new ResultModel(query); QModelIndex index; if (model->canFetchMore(index)) { model->fetchMore(index); } // FIXME TODO: Don't wipe entire cache on transactions. - connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), - this, SLOT(buildCache()), Qt::UniqueConnection); - connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), - this, SLOT(buildCache()), Qt::UniqueConnection); - connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), - this, SLOT(buildCache()), Qt::UniqueConnection); - connect(model, SIGNAL(modelReset()), - this, SLOT(buildCache()), Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsInserted, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsRemoved, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::rowsMoved, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); + connect(model, &QAbstractItemModel::modelReset, + this, &RecentContactsModel::buildCache, Qt::UniqueConnection); setSourceModel(model); buildCache(); delete oldModel; } void RecentContactsModel::buildCache() { qDeleteAll(m_idToData); m_idToData.clear(); m_dataToRow.clear(); QString id; for(int i = 0; i < sourceModel()->rowCount(); ++i) { id = sourceModel()->data(sourceModel()->index(i, 0), ResultModel::ResourceRole).toString(); if (!m_idToData.contains(id)) { insertPersonData(id, i); } } } void RecentContactsModel::insertPersonData(const QString& id, int row) { KPeople::PersonData *data = new KPeople::PersonData(id); m_idToData[id] = data; m_dataToRow[data] = row; - connect(data, SIGNAL(dataChanged()), this, SLOT(personDataChanged())); + connect(data, &KPeople::PersonData::dataChanged, this, &RecentContactsModel::personDataChanged); } void RecentContactsModel::personDataChanged() { KPeople::PersonData *data = static_cast(sender()); if (m_dataToRow.contains(data)) { int row = m_dataToRow[data]; QModelIndex idx = sourceModel()->index(row, 0); emit dataChanged(idx, idx); } } diff --git a/applets/kicker/plugin/runnermodel.cpp b/applets/kicker/plugin/runnermodel.cpp index 6a9b8188e..0d7b9178b 100644 --- a/applets/kicker/plugin/runnermodel.cpp +++ b/applets/kicker/plugin/runnermodel.cpp @@ -1,337 +1,337 @@ /*************************************************************************** * Copyright (C) 2012 by Aurélien Gâteau * * Copyright (C) 2014 by Eike Hein * * * * 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 "runnermodel.h" #include "runnermatchesmodel.h" #include #include #include #include RunnerModel::RunnerModel(QObject *parent) : QAbstractListModel(parent) , m_favoritesModel(nullptr) , m_appletInterface(nullptr) , m_runnerManager(nullptr) , m_mergeResults(false) , m_deleteWhenEmpty(false) { m_queryTimer.setSingleShot(true); m_queryTimer.setInterval(10); - connect(&m_queryTimer, SIGNAL(timeout()), this, SLOT(startQuery())); + connect(&m_queryTimer, &QTimer::timeout, this, &RunnerModel::startQuery); } RunnerModel::~RunnerModel() { } QHash RunnerModel::roleNames() const { return {{ Qt::DisplayRole, "display" }}; } AbstractModel *RunnerModel::favoritesModel() const { return m_favoritesModel; } void RunnerModel::setFavoritesModel(AbstractModel *model) { if (m_favoritesModel != model) { m_favoritesModel = model; clear(); if (!m_query.isEmpty()) { m_queryTimer.start(); } emit favoritesModelChanged(); } } QObject *RunnerModel::appletInterface() const { return m_appletInterface; } void RunnerModel::setAppletInterface(QObject *appletInterface) { if (m_appletInterface != appletInterface) { m_appletInterface = appletInterface; clear(); if (!m_query.isEmpty()) { m_queryTimer.start(); } emit appletInterfaceChanged(); } } bool RunnerModel::deleteWhenEmpty() const { return m_deleteWhenEmpty; } void RunnerModel::setDeleteWhenEmpty(bool deleteWhenEmpty) { if (m_deleteWhenEmpty != deleteWhenEmpty) { m_deleteWhenEmpty = deleteWhenEmpty; clear(); if (!m_query.isEmpty()) { m_queryTimer.start(); } emit deleteWhenEmptyChanged(); } } bool RunnerModel::mergeResults() const { return m_mergeResults; } void RunnerModel::setMergeResults(bool merge) { if (m_mergeResults != merge) { m_mergeResults = merge; clear(); if (!m_query.isEmpty()) { m_queryTimer.start(); } emit mergeResultsChanged(); } } QVariant RunnerModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_models.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return m_models.at(index.row())->name(); } return QVariant(); } int RunnerModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_models.count(); } int RunnerModel::count() const { return rowCount(); } QObject *RunnerModel::modelForRow(int row) { if (row < 0 || row >= m_models.count()) { return nullptr; } return m_models.at(row); } QStringList RunnerModel::runners() const { return m_runners; } void RunnerModel::setRunners(const QStringList &runners) { if (m_runners.toSet() != runners.toSet()) { m_runners = runners; if (m_runnerManager) { m_runnerManager->setAllowedRunners(runners); } emit runnersChanged(); } } QString RunnerModel::query() const { return m_query; } void RunnerModel::setQuery(const QString &query) { if (m_query != query) { m_query = query; m_queryTimer.start(); emit queryChanged(); } } void RunnerModel::startQuery() { if (m_query.isEmpty()) { clear(); } if (m_query.isEmpty() && m_runnerManager) { return; } createManager(); m_runnerManager->launchQuery(m_query); } void RunnerModel::matchesChanged(const QList &matches) { // Group matches by runner. // We do not use a QMultiHash here because it keeps values in LIFO order, while we want FIFO. QHash > matchesForRunner; foreach (const Plasma::QueryMatch &match, matches) { auto it = matchesForRunner.find(match.runner()->id()); if (it == matchesForRunner.end()) { it = matchesForRunner.insert(match.runner()->id(), QList()); } it.value().append(match); } // Sort matches for all runners in descending order. This allows the best // match to win whilest preserving order between runners. for (auto &list : matchesForRunner) { std::sort(list.begin(), list.end(), qGreater()); } if (m_mergeResults) { RunnerMatchesModel *matchesModel = nullptr; if (m_models.isEmpty()) { matchesModel = new RunnerMatchesModel(QString(), i18n("Search results"), m_runnerManager, this); beginInsertRows(QModelIndex(), 0, 0); m_models.append(matchesModel); endInsertRows(); emit countChanged(); } else { matchesModel = m_models.at(0); } QList matches; foreach (const QString &runnerId, m_runners) { matches.append(matchesForRunner.take(runnerId)); } matchesModel->setMatches(matches); return; } // Assign matches to existing models. If there is no match for a model, delete it. for (int row = m_models.count() - 1; row >= 0; --row) { RunnerMatchesModel *matchesModel = m_models.at(row); QList matches = matchesForRunner.take(matchesModel->runnerId()); if (m_deleteWhenEmpty && matches.isEmpty()) { beginRemoveRows(QModelIndex(), row, row); m_models.removeAt(row); delete matchesModel; endRemoveRows(); emit countChanged(); } else { matchesModel->setMatches(matches); } } // At this point, matchesForRunner contains only matches for runners which // do not have a model yet. Create new models for them. if (!matchesForRunner.isEmpty()) { auto it = matchesForRunner.constBegin(); auto end = matchesForRunner.constEnd(); int appendCount = 0; for (; it != end; ++it) { QList matches = it.value(); Q_ASSERT(!matches.isEmpty()); RunnerMatchesModel *matchesModel = new RunnerMatchesModel(it.key(), matches.first().runner()->name(), m_runnerManager, this); matchesModel->setMatches(matches); if (it.key() == QLatin1String("services")) { beginInsertRows(QModelIndex(), 0, 0); m_models.prepend(matchesModel); endInsertRows(); emit countChanged(); } else { m_models.append(matchesModel); ++appendCount; } } if (appendCount > 0) { beginInsertRows(QModelIndex(), rowCount() - appendCount, rowCount() - 1); endInsertRows(); emit countChanged(); } } } void RunnerModel::createManager() { if (!m_runnerManager) { m_runnerManager = new Plasma::RunnerManager(this); // FIXME: Which KConfigGroup is this using now? m_runnerManager->setAllowedRunners(m_runners); - connect(m_runnerManager, SIGNAL(matchesChanged(QList)), - this, SLOT(matchesChanged(QList))); + connect(m_runnerManager, &Plasma::RunnerManager::matchesChanged, + this, &RunnerModel::matchesChanged); } } void RunnerModel::clear() { if (m_runnerManager) { m_runnerManager->reset(); } if (m_models.isEmpty()) { return; } beginResetModel(); qDeleteAll(m_models); m_models.clear(); endResetModel(); emit countChanged(); } diff --git a/components/containmentlayoutmanager/appletslayout.cpp b/components/containmentlayoutmanager/appletslayout.cpp index a2f8ad765..e0cbb9db0 100644 --- a/components/containmentlayoutmanager/appletslayout.cpp +++ b/components/containmentlayoutmanager/appletslayout.cpp @@ -1,742 +1,742 @@ /* * Copyright 2019 by Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "appletslayout.h" #include "appletcontainer.h" #include "gridlayoutmanager.h" #include #include #include #include // Plasma #include #include #include AppletsLayout::AppletsLayout(QQuickItem *parent) : QQuickItem(parent) { m_layoutManager = new GridLayoutManager(this); setFlags(QQuickItem::ItemIsFocusScope); setAcceptedMouseButtons(Qt::LeftButton); m_saveLayoutTimer = new QTimer(this); m_saveLayoutTimer->setSingleShot(true); m_saveLayoutTimer->setInterval(100); connect(m_layoutManager, &AbstractLayoutManager::layoutNeedsSaving, m_saveLayoutTimer, QOverload<>::of(&QTimer::start)); connect(m_saveLayoutTimer, &QTimer::timeout, this, [this] () { if (!m_configKey.isEmpty() && m_containment && m_containment->corona()->isStartupCompleted()) { const QString serializedConfig = m_layoutManager->serializeLayout(); m_containment->config().writeEntry(m_configKey, serializedConfig); //FIXME: something more efficient m_layoutManager->parseLayout(serializedConfig); m_savedSize = size(); m_containment->corona()->requireConfigSync(); } }); m_configKeyChangeTimer = new QTimer(this); m_configKeyChangeTimer->setSingleShot(true); m_configKeyChangeTimer->setInterval(100); connect(m_configKeyChangeTimer, &QTimer::timeout, this, [this] () { if (!m_configKey.isEmpty() && m_containment) { m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); if (width() > 0 && height() > 0) { m_layoutManager->resetLayoutFromConfig(); m_savedSize = size(); } } }); m_pressAndHoldTimer = new QTimer(this); m_pressAndHoldTimer->setSingleShot(true); connect(m_pressAndHoldTimer, &QTimer::timeout, this, [this]() { setEditMode(true); }); m_sizeSyncTimer = new QTimer(this); m_sizeSyncTimer->setSingleShot(true); m_sizeSyncTimer->setInterval(150); connect(m_sizeSyncTimer, &QTimer::timeout, this, [this]() { const QRect newGeom(x(), y(), width(), height()); // The size has been restored from the last one it has been saved: restore that exact same layout if (newGeom.size() == m_savedSize) { m_layoutManager->resetLayoutFromConfig(); // If the resize is consequence of a screen resolution change, queue a relayout maintaining the distance between screen edges } else if (!m_geometryBeforeResolutionChange.isEmpty()) { m_layoutManager->layoutGeometryChanged(newGeom, m_geometryBeforeResolutionChange); m_geometryBeforeResolutionChange = QRectF(); // Heuristically relayout items only when the plasma startup is fully completed } else { polish(); } }); } AppletsLayout::~AppletsLayout() { } PlasmaQuick::AppletQuickItem *AppletsLayout::containment() const { return m_containmentItem; } void AppletsLayout::setContainment(PlasmaQuick::AppletQuickItem *containmentItem) { // Forbid changing containmentItem at runtime if (m_containmentItem || containmentItem == m_containmentItem || !containmentItem->applet() || !containmentItem->applet()->isContainment()) { qWarning() << "Error: cannot change the containment to AppletsLayout"; return; } // Can't assign containments that aren't parents QQuickItem *candidate = parentItem(); while (candidate) { if (candidate == m_containmentItem) { break; } candidate = candidate->parentItem(); } if (candidate != m_containmentItem) { return; } m_containmentItem = containmentItem; m_containment = static_cast(m_containmentItem->applet()); connect(m_containmentItem, SIGNAL(appletAdded(QObject *, int, int)), this, SLOT(appletAdded(QObject *, int, int))); connect(m_containmentItem, SIGNAL(appletRemoved(QObject *)), this, SLOT(appletRemoved(QObject *))); emit containmentChanged(); } QString AppletsLayout::configKey() const { return m_configKey; } void AppletsLayout::setConfigKey(const QString &key) { if (m_configKey == key) { return; } m_configKey = key; // Reloading everything from the new config is expansive, event compress it m_configKeyChangeTimer->start(); emit configKeyChanged(); } QJSValue AppletsLayout::acceptsAppletCallback() const { return m_acceptsAppletCallback; } qreal AppletsLayout::minimumItemWidth() const { return m_minimumItemSize.width(); } void AppletsLayout::setMinimumItemWidth(qreal width) { if (qFuzzyCompare(width, m_minimumItemSize.width())) { return; } m_minimumItemSize.setWidth(width); emit minimumItemWidthChanged(); } qreal AppletsLayout::minimumItemHeight() const { return m_minimumItemSize.height(); } void AppletsLayout::setMinimumItemHeight(qreal height) { if (qFuzzyCompare(height, m_minimumItemSize.height())) { return; } m_minimumItemSize.setHeight(height); emit minimumItemHeightChanged(); } qreal AppletsLayout::defaultItemWidth() const { return m_defaultItemSize.width(); } void AppletsLayout::setDefaultItemWidth(qreal width) { if (qFuzzyCompare(width, m_defaultItemSize.width())) { return; } m_defaultItemSize.setWidth(width); emit defaultItemWidthChanged(); } qreal AppletsLayout::defaultItemHeight() const { return m_defaultItemSize.height(); } void AppletsLayout::setDefaultItemHeight(qreal height) { if (qFuzzyCompare(height, m_defaultItemSize.height())) { return; } m_defaultItemSize.setHeight(height); emit defaultItemHeightChanged(); } qreal AppletsLayout::cellWidth() const { return m_layoutManager->cellSize().width(); } void AppletsLayout::setCellWidth(qreal width) { if (qFuzzyCompare(width, m_layoutManager->cellSize().width())) { return; } m_layoutManager->setCellSize(QSizeF(width, m_layoutManager->cellSize().height())); emit cellWidthChanged(); } qreal AppletsLayout::cellHeight() const { return m_layoutManager->cellSize().height(); } void AppletsLayout::setCellHeight(qreal height) { if (qFuzzyCompare(height, m_layoutManager->cellSize().height())) { return; } m_layoutManager->setCellSize(QSizeF(m_layoutManager->cellSize().width(), height)); emit cellHeightChanged(); } void AppletsLayout::setAcceptsAppletCallback(const QJSValue& callback) { if (m_acceptsAppletCallback.strictlyEquals(callback)) { return; } if (!callback.isNull() && !callback.isCallable()) { return; } m_acceptsAppletCallback = callback; Q_EMIT acceptsAppletCallbackChanged(); } QQmlComponent *AppletsLayout::appletContainerComponent() const { return m_appletContainerComponent; } void AppletsLayout::setAppletContainerComponent(QQmlComponent *component) { if (m_appletContainerComponent == component) { return; } m_appletContainerComponent = component; emit appletContainerComponentChanged(); } AppletsLayout::EditModeCondition AppletsLayout::editModeCondition() const { return m_editModeCondition; } void AppletsLayout::setEditModeCondition(AppletsLayout::EditModeCondition condition) { if (m_editModeCondition == condition) { return; } if (m_editModeCondition == Locked) { setEditMode(false); } m_editModeCondition = condition; emit editModeConditionChanged(); } bool AppletsLayout::editMode() const { return m_editMode; } void AppletsLayout::setEditMode(bool editMode) { if (m_editMode == editMode) { return; } m_editMode = editMode; emit editModeChanged(); } ItemContainer *AppletsLayout::placeHolder() const { return m_placeHolder; } void AppletsLayout::setPlaceHolder(ItemContainer *placeHolder) { if (m_placeHolder == placeHolder) { return; } m_placeHolder = placeHolder; m_placeHolder->setParentItem(this); m_placeHolder->setZ(9999); m_placeHolder->setOpacity(false); emit placeHolderChanged(); } QQuickItem *AppletsLayout::eventManagerToFilter() const { return m_eventManagerToFilter; } void AppletsLayout::setEventManagerToFilter(QQuickItem *item) { if (m_eventManagerToFilter == item) { return; } m_eventManagerToFilter = item; setFiltersChildMouseEvents(m_eventManagerToFilter); emit eventManagerToFilterChanged(); } void AppletsLayout::save() { m_saveLayoutTimer->start(); } void AppletsLayout::showPlaceHolderAt(const QRectF &geom) { if (!m_placeHolder) { return; } m_placeHolder->setPosition(geom.topLeft()); m_placeHolder->setSize(geom.size()); m_layoutManager->positionItem(m_placeHolder); m_placeHolder->setProperty("opacity", 1); } void AppletsLayout::showPlaceHolderForItem(ItemContainer *item) { if (!m_placeHolder) { return; } m_placeHolder->setPreferredLayoutDirection(item->preferredLayoutDirection()); m_placeHolder->setPosition(item->position()); m_placeHolder->setSize(item->size()); m_layoutManager->positionItem(m_placeHolder); m_placeHolder->setProperty("opacity", 1); } void AppletsLayout::hidePlaceHolder() { if (!m_placeHolder) { return; } m_placeHolder->setProperty("opacity", 0); } bool AppletsLayout::isRectAvailable(qreal x, qreal y, qreal width, qreal height) { return m_layoutManager->isRectAvailable(QRectF(x, y, width, height)); } bool AppletsLayout::itemIsManaged(ItemContainer *item) { if (!item) { return false; } return m_layoutManager->itemIsManaged(item); } void AppletsLayout::positionItem(ItemContainer *item) { if (!item) { return; } item->setParent(this); m_layoutManager->positionItemAndAssign(item); } void AppletsLayout::restoreItem(ItemContainer *item) { m_layoutManager->restoreItem(item); } void AppletsLayout::releaseSpace(ItemContainer *item) { if (!item) { return; } m_layoutManager->releaseSpace(item); } void AppletsLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { // Ignore completely moves without resize if (newGeometry.size() == oldGeometry.size()) { QQuickItem::geometryChanged(newGeometry, oldGeometry); return; } // Don't care for anything happening before startup completion if (!m_containment || !m_containment->corona() || !m_containment->corona()->isStartupCompleted()) { QQuickItem::geometryChanged(newGeometry, oldGeometry); return; } // Only do a layouting procedure if we received a valid size if (!newGeometry.isEmpty()) { m_sizeSyncTimer->start(); } QQuickItem::geometryChanged(newGeometry, oldGeometry); } void AppletsLayout::updatePolish() { m_layoutManager->resetLayout(); m_savedSize = size(); } void AppletsLayout::componentComplete() { if (!m_containment || !m_containmentItem) { QQuickItem::componentComplete(); return; } if (!m_configKey.isEmpty()) { m_layoutManager->parseLayout(m_containment->config().readEntry(m_configKey, "")); } QList appletObjects = m_containmentItem->property("applets").value >(); for (auto *obj : appletObjects) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(obj); if (!obj) { continue; } AppletContainer *container = createContainerForApplet(appletItem); if (width() > 0 && height() > 0) { m_layoutManager->positionItemAndAssign(container); } } //layout all extra non applet items if (width() > 0 && height() > 0) { for (auto *child : childItems()) { ItemContainer *item = qobject_cast(child); if (item && item != m_placeHolder && !m_layoutManager->itemIsManaged(item)) { m_layoutManager->positionItemAndAssign(item); } } } if (m_containment && m_containment->corona()) { - connect(m_containment->corona(), &Plasma::Corona::startupCompleted, this, [this](){ + connect(m_containment->corona(), &Plasma::Corona::startupCompleted, this, [](){ // m_savedSize = size(); }); // When the screen geometry changes, we need to know the geometry just before it did, so we can apply out heuristic of keeping the distance with borders constant connect(m_containment->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id){ if (m_containment->screen() == id) { m_geometryBeforeResolutionChange = QRectF(x(), y(), width(), height()); } }); } QQuickItem::componentComplete(); } bool AppletsLayout::childMouseEventFilter(QQuickItem *item, QEvent *event) { if (item != m_eventManagerToFilter) { return QQuickItem::childMouseEventFilter(item, event); } switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::LeftButton) { mousePressEvent(me); } break; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); mouseMoveEvent(me); break; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); mouseReleaseEvent(me); break; } case QEvent::UngrabMouse: mouseUngrabEvent(); break; default: break; } return QQuickItem::childMouseEventFilter(item, event); } void AppletsLayout::mousePressEvent(QMouseEvent *event) { forceActiveFocus(Qt::MouseFocusReason); if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { return; } if (!m_editMode && m_editModeCondition == AppletsLayout::AfterPressAndHold) { m_pressAndHoldTimer->start(QGuiApplication::styleHints()->mousePressAndHoldInterval()); } m_mouseDownWasEditMode = m_editMode; m_mouseDownPosition = event->windowPos(); //event->setAccepted(false); } void AppletsLayout::mouseMoveEvent(QMouseEvent *event) { if (!m_editMode && m_editModeCondition == AppletsLayout::Manual) { return; } if (!m_editMode && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { m_pressAndHoldTimer->stop(); } } void AppletsLayout::mouseReleaseEvent(QMouseEvent *event) { if (m_editMode && m_mouseDownWasEditMode // By only accepting synthetyzed events, this makes the // close by tapping in any empty area only work with real // touch events, as we want a different behavior between desktop // and tablet mode && (event->source() == Qt::MouseEventSynthesizedBySystem || event->source() == Qt::MouseEventSynthesizedByQt) && QPointF(event->windowPos() - m_mouseDownPosition).manhattanLength() < QGuiApplication::styleHints()->startDragDistance()) { setEditMode(false); } m_pressAndHoldTimer->stop(); if (!m_editMode) { for (auto *child : childItems()) { ItemContainer *item = qobject_cast(child); if (item && item != m_placeHolder) { item->setEditMode(false); } } } } void AppletsLayout::mouseUngrabEvent() { m_pressAndHoldTimer->stop(); } void AppletsLayout::appletAdded(QObject *applet, int x, int y) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); //maybe even an assert? if (!appletItem) { return; } if (m_acceptsAppletCallback.isCallable()) { QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); Q_ASSERT(engine); QJSValueList args; args << engine->newQObject(applet) << QJSValue(x) << QJSValue(y); if (!m_acceptsAppletCallback.call(args).toBool()) { emit appletRefused(applet, x, y); return; } } AppletContainer *container = createContainerForApplet(appletItem); container->setPosition(QPointF(x, y)); container->setVisible(true); m_layoutManager->positionItemAndAssign(container); } void AppletsLayout::appletRemoved(QObject *applet) { PlasmaQuick::AppletQuickItem *appletItem = qobject_cast(applet); //maybe even an assert? if (!appletItem) { return; } AppletContainer *container = m_containerForApplet.value(appletItem); if (!container) { return; } m_layoutManager->releaseSpace(container); m_containerForApplet.remove(appletItem); appletItem->setParentItem(this); container->deleteLater(); } AppletContainer *AppletsLayout::createContainerForApplet(PlasmaQuick::AppletQuickItem *appletItem) { AppletContainer *container = m_containerForApplet.value(appletItem); if (container) { return container; } bool createdFromQml = true; if (m_appletContainerComponent) { QQmlContext *context = QQmlEngine::contextForObject(this); Q_ASSERT(context); QObject *instance = m_appletContainerComponent->beginCreate(context); container = qobject_cast(instance); if (container) { container->setParentItem(this); } else { qWarning() << "Error: provided component not an AppletContainer instance"; if (instance) { instance->deleteLater(); } createdFromQml = false; } } if (!container) { container = new AppletContainer(this); } container->setVisible(false); const QSizeF appletSize = appletItem->size(); container->setContentItem(appletItem); m_containerForApplet[appletItem] = container; container->setLayout(this); container->setKey(QLatin1String("Applet-") + QString::number(appletItem->applet()->id())); const bool geometryWasSaved = m_layoutManager->restoreItem(container); if (!geometryWasSaved) { container->setPosition(QPointF(appletItem->x() - container->leftPadding(), appletItem->y() - container->topPadding())); if (!appletSize.isEmpty()) { container->setSize(QSizeF(qMax(m_minimumItemSize.width(), appletSize.width() + container->leftPadding() + container->rightPadding()), qMax(m_minimumItemSize.height(), appletSize.height() + container->topPadding() + container->bottomPadding()))); } } if (m_appletContainerComponent && createdFromQml) { m_appletContainerComponent->completeCreate(); } //NOTE: This has to be done here as we need the component completed to have all the bindings evaluated if (!geometryWasSaved && appletSize.isEmpty()) { if (container->initialSize().width() > m_minimumItemSize.width() && container->initialSize().height() > m_minimumItemSize.height()) { const QSizeF size = m_layoutManager->cellAlignedContainingSize( container->initialSize()); container->setSize(size); } else { container->setSize(QSizeF(qMax(m_minimumItemSize.width(), m_defaultItemSize.width()), qMax(m_minimumItemSize.height(), m_defaultItemSize.height()))); } } container->setVisible(true); appletItem->setVisible(true); return container; } #include "moc_appletslayout.cpp" diff --git a/components/sessionsprivate/sessionsmodel.cpp b/components/sessionsprivate/sessionsmodel.cpp index 25d3f3d19..71e6fd21d 100644 --- a/components/sessionsprivate/sessionsmodel.cpp +++ b/components/sessionsprivate/sessionsmodel.cpp @@ -1,297 +1,297 @@ /* Copyright 2015 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 "sessionsmodel.h" #include #include #include #include "kscreensaversettings.h" #include "screensaver_interface.h" SessionsModel::SessionsModel(QObject *parent) : QAbstractListModel(parent) , m_screensaverInterface( new org::freedesktop::ScreenSaver(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus(), this) ) { reload(); // wait for the screen locker to be ready before actually switching connect(m_screensaverInterface, &org::freedesktop::ScreenSaver::ActiveChanged, this, [this](bool active) { if (active) { if (m_pendingVt) { m_displayManager.switchVT(m_pendingVt); emit switchedUser(m_pendingVt); } else if (m_pendingReserve) { m_displayManager.startReserve(); emit startedNewSession(); } m_pendingVt = 0; m_pendingReserve = false; } }); } bool SessionsModel::canSwitchUser() const { return const_cast(this)->m_displayManager.isSwitchable() && KAuthorized::authorizeAction(QLatin1String("switch_user")); } bool SessionsModel::canStartNewSession() const { return const_cast(this)->m_displayManager.numReserve() > 0 && KAuthorized::authorizeAction(QLatin1String("start_new_session")); } bool SessionsModel::shouldLock() const { return m_shouldLock; } bool SessionsModel::includeUnusedSessions() const { return m_includeUnusedSessions; } void SessionsModel::setIncludeUnusedSessions(bool includeUnusedSessions) { if (m_includeUnusedSessions != includeUnusedSessions) { m_includeUnusedSessions = includeUnusedSessions; reload(); emit includeUnusedSessionsChanged(); } } void SessionsModel::switchUser(int vt, bool shouldLock) { if (vt < 0) { startNewSession(shouldLock); return; } if (!canSwitchUser()) { return; } if (!shouldLock) { m_displayManager.switchVT(vt); emit switchedUser(vt); return; } checkScreenLocked([this, vt](bool locked) { if (locked) { // already locked, switch right away m_displayManager.switchVT(vt); emit switchedUser(vt); } else { m_pendingReserve = false; m_pendingVt = vt; emit aboutToLockScreen(); m_screensaverInterface->Lock(); } }); } void SessionsModel::startNewSession(bool shouldLock) { if (!canStartNewSession()) { return; } if (!shouldLock) { m_displayManager.startReserve(); emit startedNewSession(); return; } checkScreenLocked([this](bool locked) { if (locked) { // already locked, switch right away m_displayManager.startReserve(); emit startedNewSession(); } else { m_pendingReserve = true; m_pendingVt = 0; emit aboutToLockScreen(); m_screensaverInterface->Lock(); } }); } void SessionsModel::reload() { static QHash kusers; const bool oldShouldLock = m_shouldLock; m_shouldLock = KAuthorized::authorizeAction(QStringLiteral("lock_screen")) && KScreenSaverSettings::autolock(); if (m_shouldLock != oldShouldLock) { emit shouldLockChanged(); } SessList sessions; m_displayManager.localSessions(sessions); const int oldCount = m_data.count(); beginResetModel(); m_data.clear(); m_data.reserve(sessions.count()); foreach (const SessEnt &session, sessions) { if (!session.vt || session.self) { continue; } if (!m_includeUnusedSessions && session.session.isEmpty()) { continue; } SessionEntry entry; entry.name = session.user; entry.displayNumber = session.display; entry.vtNumber = session.vt; entry.session = session.session; entry.isTty = session.tty; auto it = kusers.constFind(session.user); if (it != kusers.constEnd()) { entry.realName = it->property(KUser::FullName).toString(); entry.icon = it->faceIconPath(); } else { KUser user(session.user); entry.realName = user.property(KUser::FullName).toString(); entry.icon = user.faceIconPath(); kusers.insert(session.user, user); } m_data.append(entry); } endResetModel(); if (oldCount != m_data.count()) { emit countChanged(); } } void SessionsModel::checkScreenLocked(const std::function &cb) { auto reply = m_screensaverInterface->GetActive(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, cb](QDBusPendingCallWatcher *watcher) { + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [ cb](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (!reply.isError()) { cb(reply.value()); } watcher->deleteLater(); }); } void SessionsModel::setShowNewSessionEntry(bool showNewSessionEntry) { if (!canStartNewSession()) { return; } if (showNewSessionEntry == m_showNewSessionEntry) { return; } int row = m_data.size(); if (showNewSessionEntry) { beginInsertRows(QModelIndex(), row, row); m_showNewSessionEntry = showNewSessionEntry; endInsertRows(); } else { beginRemoveRows(QModelIndex(), row, row); m_showNewSessionEntry = showNewSessionEntry; endRemoveRows(); } emit countChanged(); } QVariant SessionsModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() > rowCount(QModelIndex())) { return QVariant(); } if (index.row() == m_data.count()) { switch (static_cast(role)) { case Role::RealName: return i18n("New Session"); case Role::IconName: return QStringLiteral("system-switch-user"); case Role::Name: return i18n("New Session"); case Role::DisplayNumber: return 0; //NA case Role::VtNumber: return -1; //an invalid VtNumber - which we'll use to indicate it's to start a new session case Role::Session: return 0; //NA case Role::IsTty: return false; //NA default: return QVariant(); } } const SessionEntry &item = m_data.at(index.row()); switch (static_cast(role)) { case Role::RealName: return item.realName; case Role::Icon: return item.icon; case Role::Name: return item.name; case Role::DisplayNumber: return item.displayNumber; case Role::VtNumber: return item.vtNumber; case Role::Session: return item.session; case Role::IsTty: return item.isTty; default: return QVariant(); } } int SessionsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_data.count() + (m_showNewSessionEntry ? 1 : 0); } QHash SessionsModel::roleNames() const { return { {static_cast(Role::Name), QByteArrayLiteral("name")}, {static_cast(Role::RealName), QByteArrayLiteral("realName")}, {static_cast(Role::Icon), QByteArrayLiteral("icon")}, {static_cast(Role::IconName), QByteArrayLiteral("iconName")}, {static_cast(Role::DisplayNumber), QByteArrayLiteral("displayNumber")}, {static_cast(Role::VtNumber), QByteArrayLiteral("vtNumber")}, {static_cast(Role::Session), QByteArrayLiteral("session")}, {static_cast(Role::IsTty), QByteArrayLiteral("isTty")} }; } diff --git a/containmentactions/switchwindow/switch.cpp b/containmentactions/switchwindow/switch.cpp index 3181208b2..fff60145d 100644 --- a/containmentactions/switchwindow/switch.cpp +++ b/containmentactions/switchwindow/switch.cpp @@ -1,277 +1,277 @@ /* * Copyright 2009 by Chani Armitage * Copyright 2018 by Eike Hein * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "switch.h" #include "abstracttasksmodel.h" #include "activityinfo.h" #include "tasksmodel.h" #include "virtualdesktopinfo.h" #include #include using namespace TaskManager; ActivityInfo *SwitchWindow::s_activityInfo = nullptr; TasksModel *SwitchWindow::s_tasksModel = nullptr;int SwitchWindow::s_instanceCount = 0; SwitchWindow::SwitchWindow(QObject *parent, const QVariantList &args) : Plasma::ContainmentActions(parent, args) , m_mode(AllFlat) , m_virtualDesktopInfo(new VirtualDesktopInfo(this)) { ++s_instanceCount; if (!s_activityInfo) { s_activityInfo = new ActivityInfo(); } if (!s_tasksModel) { s_tasksModel = new TasksModel(); s_tasksModel->setGroupMode(TasksModel::GroupDisabled); s_tasksModel->setActivity(s_activityInfo->currentActivity()); s_tasksModel->setFilterByActivity(true); connect(s_activityInfo, &ActivityInfo::currentActivityChanged, - this, [this]() { s_tasksModel->setActivity(s_activityInfo->currentActivity()); }); + this, []() { s_tasksModel->setActivity(s_activityInfo->currentActivity()); }); } } SwitchWindow::~SwitchWindow() { --s_instanceCount; if (!s_instanceCount) { delete s_activityInfo; s_activityInfo = nullptr; delete s_tasksModel; s_tasksModel = nullptr; } qDeleteAll(m_actions); } void SwitchWindow::restore(const KConfigGroup &config) { m_mode = (MenuMode)config.readEntry("mode", (int)AllFlat); } QWidget *SwitchWindow::createConfigurationInterface(QWidget *parent) { QWidget *widget = new QWidget(parent); m_ui.setupUi(widget); widget->setWindowTitle(i18nc("plasma_containmentactions_switchwindow", "Configure Switch Window Plugin")); switch (m_mode) { case AllFlat: m_ui.flatButton->setChecked(true); break; case DesktopSubmenus: m_ui.subButton->setChecked(true); break; case CurrentDesktop: m_ui.curButton->setChecked(true); break; } return widget; } void SwitchWindow::configurationAccepted() { if (m_ui.flatButton->isChecked()) { m_mode = AllFlat; } else if (m_ui.subButton->isChecked()) { m_mode = DesktopSubmenus; } else { m_mode = CurrentDesktop; } } void SwitchWindow::save(KConfigGroup &config) { config.writeEntry("mode", (int)m_mode); } void SwitchWindow::makeMenu() { qDeleteAll(m_actions); m_actions.clear(); if (s_tasksModel->rowCount() == 0) { return; } QMultiMap desktops; QList allDesktops; // Make all the window actions. for (int i = 0; i < s_tasksModel->rowCount(); ++i) { const QModelIndex &idx = s_tasksModel->index(i, 0); if (!idx.data(AbstractTasksModel::IsWindow).toBool()) { continue; } const QString &name = idx.data().toString(); if (name.isEmpty()) { continue; } QAction *action = new QAction(name, this); action->setIcon(idx.data(Qt::DecorationRole).value()); action->setData(idx.data(AbstractTasksModel::WinIdList).toList()); const QVariantList &desktopList = idx.data(AbstractTasksModel::VirtualDesktops).toList(); for (const QVariant &desktop : desktopList) { desktops.insert(desktop, action); } if (idx.data(AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { allDesktops << action; } connect(action, &QAction::triggered, [=]() { switchTo(action); }); } // Sort into menu(s). if (m_mode == CurrentDesktop) { const QVariant ¤tDesktop = m_virtualDesktopInfo->currentDesktop(); QAction *a = new QAction(i18nc("plasma_containmentactions_switchwindow", "Windows"), this); a->setSeparator(true); m_actions << a; m_actions << desktops.values(currentDesktop); m_actions << allDesktops; } else { const QVariantList &desktopIds = m_virtualDesktopInfo->desktopIds(); const QStringList &desktopNames = m_virtualDesktopInfo->desktopNames(); if (m_mode == AllFlat) { for (int i = 0; i < desktopIds.count(); ++i) { const QVariant &desktop = desktopIds.at(i); if (desktops.contains(desktop)) { const QString &name = QStringLiteral("%1: %2").arg(QString::number(i + 1), desktopNames.at(i)); QAction *a = new QAction(name, this); a->setSeparator(true); m_actions << a; m_actions << desktops.values(desktop); } } if (allDesktops.count()) { QAction *a = new QAction(i18nc("plasma_containmentactions_switchwindow", "All Desktops"), this); a->setSeparator(true); m_actions << a; m_actions << allDesktops; } } else { // Submenus. for (int i = 0; i < desktopIds.count(); ++i) { const QVariant &desktop = desktopIds.at(i); if (desktops.contains(desktop)) { const QString &name = QStringLiteral("%1: %2").arg(QString::number(i + 1), desktopNames.at(i)); QMenu *subMenu = new QMenu(name); subMenu->addActions(desktops.values(desktop)); QAction *a = new QAction(name, this); a->setMenu(subMenu); m_actions << a; } } if (allDesktops.count()) { QMenu *subMenu = new QMenu(i18nc("plasma_containmentactions_switchwindow", "All Desktops")); subMenu->addActions(allDesktops); QAction *a = new QAction(i18nc("plasma_containmentactions_switchwindow", "All Desktops"), this); a->setMenu(subMenu); m_actions << a; } } } } QList SwitchWindow::contextualActions() { makeMenu(); return m_actions; } void SwitchWindow::switchTo(QAction *action) { const QVariantList &idList = action->data().toList(); for (int i = 0; i < s_tasksModel->rowCount(); ++i) { const QModelIndex &idx = s_tasksModel->index(i, 0); if (idList == idx.data(AbstractTasksModel::WinIdList).toList()) { s_tasksModel->requestActivate(idx); return; } } } void SwitchWindow::performNextAction() { doSwitch(true); } void SwitchWindow::performPreviousAction() { doSwitch(false); } void SwitchWindow::doSwitch(bool up) { const QModelIndex &activeTask = s_tasksModel->activeTask(); if (!activeTask.isValid()) { return; } if (up) { const QModelIndex &next = activeTask.sibling(activeTask.row() + 1, 0); if (next.isValid()) { s_tasksModel->requestActivate(next); } else if (s_tasksModel->rowCount() > 1) { s_tasksModel->requestActivate(s_tasksModel->index(0, 0)); } } else { const QModelIndex &previous = activeTask.sibling(activeTask.row() - 1, 0); if (previous.isValid()) { s_tasksModel->requestActivate(previous); } else if (s_tasksModel->rowCount() > 1) { s_tasksModel->requestActivate(s_tasksModel->index(s_tasksModel->rowCount() - 1, 0)); } } } K_EXPORT_PLASMA_CONTAINMENTACTIONS_WITH_JSON(switchwindow, SwitchWindow, "plasma-containmentactions-switchwindow.json") #include "switch.moc" diff --git a/dataengines/statusnotifieritem/statusnotifieritemjob.cpp b/dataengines/statusnotifieritem/statusnotifieritemjob.cpp index d4047ee9e..809f6b45d 100644 --- a/dataengines/statusnotifieritem/statusnotifieritemjob.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemjob.cpp @@ -1,62 +1,62 @@ /* * Copyright 2008 Alain Boyer * Copyright (C) 2009 Matthieu Gallien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2 as * published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "statusnotifieritemjob.h" #include StatusNotifierItemJob::StatusNotifierItemJob(StatusNotifierItemSource *source, const QString &operation, QMap ¶meters, QObject *parent) : ServiceJob(source->objectName(), operation, parameters, parent), m_source(source) { connect(source, SIGNAL(contextMenuReady(QMenu*)), this, SLOT(contextMenuReady(QMenu*))); - connect(source, SIGNAL(activateResult(bool)), this, SLOT(activateCallback(bool))); + connect(source, &StatusNotifierItemSource::activateResult, this, &StatusNotifierItemJob::activateCallback); } StatusNotifierItemJob::~StatusNotifierItemJob() { } void StatusNotifierItemJob::start() { if (operationName() == QString::fromLatin1("Activate")) { m_source->activate(parameters()[QStringLiteral("x")].toInt(), parameters()[QStringLiteral("y")].toInt()); } else if (operationName() == QString::fromLatin1("SecondaryActivate")) { m_source->secondaryActivate(parameters()[QStringLiteral("x")].toInt(), parameters()[QStringLiteral("y")].toInt()); setResult(0); } else if (operationName() == QString::fromLatin1("ContextMenu")) { m_source->contextMenu(parameters()[QStringLiteral("x")].toInt(), parameters()[QStringLiteral("y")].toInt()); } else if (operationName() == QString::fromLatin1("Scroll")) { m_source->scroll(parameters()[QStringLiteral("delta")].toInt(), parameters()[QStringLiteral("direction")].toString()); setResult(0); } } void StatusNotifierItemJob::activateCallback(bool success) { if (operationName() == QString::fromLatin1("Activate")) { setResult(QVariant(success)); } } void StatusNotifierItemJob::contextMenuReady(QMenu *menu) { if (operationName() == QString::fromLatin1("ContextMenu")) { setResult(QVariant::fromValue((QObject*)menu)); } } diff --git a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp index 1370a981d..d4e45d547 100644 --- a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp @@ -1,520 +1,520 @@ /*************************************************************************** * * * 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(nullptr), m_menuImporter(nullptr), 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 = nullptr; 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))); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus); 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(), overlayNames)); 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(), overlayNames)); 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, &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; } } if (image.width == 0 || image.height == 0) { return QPixmap(); } //avoid a deep copy of the image data //we need to keep a reference to the image.data alive for the lifespan of the image, even if the image is copied //we create a new QByteArray with a shallow copy of the original data on the heap, then delete this in the QImage cleanup auto dataRef = new QByteArray(image.data); QImage iconImage(reinterpret_cast(dataRef->data()), image.width, image.height, QImage::Format_ARGB32, [](void* ptr) { delete static_cast(ptr); }, dataRef); 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()) { QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), m_statusNotifierItemInterface->path(), m_statusNotifierItemInterface->interface(), QStringLiteral("Activate")); message << x << y; QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::activateCallback); } } void StatusNotifierItemSource::activateCallback(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; emit activateResult(!reply.isError()); call->deleteLater(); } 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); } } } diff --git a/gmenu-dbusmenu-proxy/menu.cpp b/gmenu-dbusmenu-proxy/menu.cpp index b45661500..8f986df0f 100644 --- a/gmenu-dbusmenu-proxy/menu.cpp +++ b/gmenu-dbusmenu-proxy/menu.cpp @@ -1,354 +1,354 @@ /* * Copyright (C) 2018 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "menu.h" #include "debug.h" #include #include #include #include #include #include #include #include "utils.h" static const QString s_orgGtkMenus = QStringLiteral("org.gtk.Menus"); Menu::Menu(const QString &serviceName, const QString &objectPath, QObject *parent) : QObject(parent) , m_serviceName(serviceName) , m_objectPath(objectPath) { Q_ASSERT(!serviceName.isEmpty()); Q_ASSERT(!m_objectPath.isEmpty()); if (!QDBusConnection::sessionBus().connect(m_serviceName, m_objectPath, s_orgGtkMenus, QStringLiteral("Changed"), this, SLOT(onMenuChanged(GMenuChangeList)))) { qCWarning(DBUSMENUPROXY) << "Failed to subscribe to menu changes for" << parent << "on" << serviceName << "at" << objectPath; } } Menu::~Menu() = default; void Menu::cleanup() { stop(m_subscriptions); } void Menu::start(uint id) { if (m_subscriptions.contains(id)) { return; } // TODO watch service disappearing? // dbus-send --print-reply --session --dest=:1.103 /org/libreoffice/window/104857641/menus/menubar org.gtk.Menus.Start array:uint32:0 QDBusMessage msg = QDBusMessage::createMethodCall(m_serviceName, m_objectPath, s_orgGtkMenus, QStringLiteral("Start")); msg.setArguments({ QVariant::fromValue(QList{id}) }); QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(msg); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id](QDBusPendingCallWatcher *watcher) { QScopedPointer watcherPtr(watcher); QDBusPendingReply reply = *watcherPtr; if (reply.isError()) { qCWarning(DBUSMENUPROXY) << "Failed to start subscription to" << id << "on" << m_serviceName << "at" << m_objectPath << reply.error(); emit failedToSubscribe(id); } else { const bool hadMenu = !m_menus.isEmpty(); const auto menus = reply.value(); for (auto menu : menus) { m_menus[menu.id].append(menus); } // LibreOffice on startup fails to give us some menus right away, we'll also subscribe in onMenuChanged() if necessary if (menus.isEmpty()) { qCWarning(DBUSMENUPROXY) << "Got an empty menu for" << id << "on" << m_serviceName << "at" << m_objectPath; return; } // TODO are we subscribed to all it returns or just to the ones we requested? m_subscriptions.append(id); // do we have a menu now? let's tell everyone if (!hadMenu && !m_menus.isEmpty()) { emit menuAppeared(); } emit subscribed(id); } }); } void Menu::stop(const QList &ids) { QDBusMessage msg = QDBusMessage::createMethodCall(m_serviceName, m_objectPath, s_orgGtkMenus, QStringLiteral("End")); msg.setArguments({ QVariant::fromValue(ids) // don't let it unwrap it, hence in a variant }); QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(msg); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, ids](QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { qCWarning(DBUSMENUPROXY) << "Failed to stop subscription to" << ids << "on" << m_serviceName << "at" << m_objectPath << reply.error(); } else { // remove all subscriptions that we unsubscribed from // TODO is there a nicer algorithm for that? // TODO remove all m_menus also? m_subscriptions.erase(std::remove_if(m_subscriptions.begin(), m_subscriptions.end(), std::bind(&QList::contains, m_subscriptions, std::placeholders::_1)), m_subscriptions.end()); if (m_subscriptions.isEmpty()) { emit menuDisappeared(); } } watcher->deleteLater(); }); } bool Menu::hasMenu() const { return !m_menus.isEmpty(); } bool Menu::hasSubscription(uint subscription) const { return m_subscriptions.contains(subscription); } GMenuItem Menu::getSection(int id, bool *ok) const { int subscription; int section; int index; Utils::intToTreeStructure(id, subscription, section, index); return getSection(subscription, section, ok); } GMenuItem Menu::getSection(int subscription, int section, bool *ok) const { const auto menu = m_menus.value(subscription); auto it = std::find_if(menu.begin(), menu.end(), [section](const GMenuItem &item) { return item.section == section; }); if (it == menu.end()) { if (ok) { *ok = false; } return GMenuItem(); } if (ok) { *ok = true; } return *it; } QVariantMap Menu::getItem(int id) const { int subscription; int section; int index; Utils::intToTreeStructure(id, subscription, section, index); return getItem(subscription, section, index); } QVariantMap Menu::getItem(int subscription, int sectionId, int index) const { bool ok; const GMenuItem section = getSection(subscription, sectionId, &ok); if (!ok) { return QVariantMap(); } const auto items = section.items; if (items.count() < index) { qCWarning(DBUSMENUPROXY) << "Cannot get action" << subscription << sectionId << index << "which is out of bounds"; return QVariantMap(); } // 0 is the menu itself, items start at 1 return items.at(index - 1); } void Menu::onMenuChanged(const GMenuChangeList &changes) { const bool hadMenu = !m_menus.isEmpty(); QVector dirtyMenus; QVector dirtyItems; for (const auto &change : changes) { auto updateSection = [&](GMenuItem §ion) { // Check if the amount of inserted items is identical to the items to be removed, // just update the existing items and signal a change for that. // LibreOffice tends to do that e.g. to update its Undo menu entry if (change.itemsToRemoveCount == change.itemsToInsert.count()) { for (int i = 0; i < change.itemsToInsert.count(); ++i) { const auto &newItem = change.itemsToInsert.at(i); section.items[change.changePosition + i] = newItem; // 0 is the menu itself, items start at 1 dirtyItems.append(Utils::treeStructureToInt(change.subscription, change.menu, change.changePosition + i + 1)); } } else { for (int i = 0; i < change.itemsToRemoveCount; ++i) { section.items.removeAt(change.changePosition); // TODO bounds check } for (int i = 0; i < change.itemsToInsert.count(); ++i) { section.items.insert(change.changePosition + i, change.itemsToInsert.at(i)); } dirtyMenus.append(Utils::treeStructureToInt(change.subscription, change.menu, 0)); } }; // shouldn't happen, it says only Start() subscribes to changes if (!m_subscriptions.contains(change.subscription)) { qCDebug(DBUSMENUPROXY) << "Got menu change for menu" << change.subscription << "that we are not subscribed to, subscribing now"; // LibreOffice doesn't give us a menu right away but takes a while and then signals us a change start(change.subscription); continue; } auto &menu = m_menus[change.subscription]; bool sectionFound = false; // TODO findSectionRef for (GMenuItem §ion : menu) { if (section.section != change.menu) { continue; } qCInfo(DBUSMENUPROXY) << "Updating existing section" << change.menu << "in subscription" << change.subscription; sectionFound = true; updateSection(section); break; } // Insert new section if (!sectionFound) { qCInfo(DBUSMENUPROXY) << "Creating new section" << change.menu << "in subscription" << change.subscription; if (change.itemsToRemoveCount > 0) { qCWarning(DBUSMENUPROXY) << "Menu change requested to remove items from a new (and as such empty) section"; } GMenuItem newSection; newSection.id = change.subscription; newSection.section = change.menu; updateSection(newSection); menu.append(newSection); } } // do we have a menu now? let's tell everyone if (!hadMenu && !m_menus.isEmpty()) { emit menuAppeared(); } else if (hadMenu && m_menus.isEmpty()) { emit menuDisappeared(); } if (!dirtyItems.isEmpty()) { emit itemsChanged(dirtyItems); } emit menusChanged(dirtyMenus); } void Menu::actionsChanged(const QStringList &dirtyActions, const QString &prefix) { auto forEachMenuItem = [this](const std::function &cb) { for (auto it = m_menus.constBegin(), end = m_menus.constEnd(); it != end; ++it) { const int subscription = it.key(); for (const auto &menu : it.value()) { const int section = menu.section; int count = 0; const auto items = menu.items; for (const auto &item : items) { ++count; // 0 is a menu, entries start at 1 if (!cb(subscription, section, count, item)) { goto loopExit; // hell yeah break; } } } } loopExit: // loop exit return; }; // now find in which menus these actions are and emit a change accordingly QVector dirtyItems; for (const QString &action : dirtyActions) { const QString prefixedAction = prefix + action; - forEachMenuItem([this, &prefixedAction, &dirtyItems](int subscription, int section, int index, const QVariantMap &item) { + forEachMenuItem([ &prefixedAction, &dirtyItems](int subscription, int section, int index, const QVariantMap &item) { const QString actionName = Utils::itemActionName(item); if (actionName == prefixedAction) { dirtyItems.append(Utils::treeStructureToInt(subscription, section, index)); return false; // break } return true; // continue }); } if (!dirtyItems.isEmpty()) { emit itemsChanged(dirtyItems); } } diff --git a/libdbusmenuqt/dbusmenuimporter.cpp b/libdbusmenuqt/dbusmenuimporter.cpp index 9f7028b40..50a8f4d83 100644 --- a/libdbusmenuqt/dbusmenuimporter.cpp +++ b/libdbusmenuqt/dbusmenuimporter.cpp @@ -1,547 +1,547 @@ /* 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" #include "debug.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 static QTime sChrono; #endif #define DMRETURN_IF_FAIL(cond) if (!(cond)) { \ qCWarning(DBUSMENUQT) << "Condition failed: " #cond; \ return; \ } static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id"; static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name"; static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash"; static QAction *createKdeTitle(QAction *action, QWidget *parent) { QToolButton *titleWidget = new QToolButton(nullptr); QFont font = titleWidget->font(); font.setBold(true); titleWidget->setFont(font); titleWidget->setIcon(action->icon()); titleWidget->setText(action->text()); titleWidget->setDown(true); titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QWidgetAction *titleAction = new QWidgetAction(parent); titleAction->setDefaultWidget(titleWidget); return titleAction; } class DBusMenuImporterPrivate { public: DBusMenuImporter *q; DBusMenuInterface *m_interface; QMenu *m_menu; using ActionForId = QMap; 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 { qDebug(DBUSMENUQT) << "Unhandled property update" << key; } } void updateActionLabel(QAction *action, const QVariant &value) { QString text = swapMnemonicChar(value.toString(), '_', '&'); action->setText(text); } void updateActionEnabled(QAction *action, const QVariant &value) { action->setEnabled(value.isValid() ? value.toBool(): true); } void updateActionChecked(QAction *action, const QVariant &value) { if (action->isCheckable() && value.isValid()) { action->setChecked(value.toInt() == 1); } } void updateActionIconByName(QAction *action, const QVariant &value) { const QString iconName = value.toString(); const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString(); if (previous == iconName) { return; } action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName); if (iconName.isEmpty()) { action->setIcon(QIcon()); return; } action->setIcon(q->iconForName(iconName)); } void updateActionIconByData(QAction *action, const QVariant &value) { const QByteArray data = value.toByteArray(); uint dataHash = qHash(data); uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt(); if (previousDataHash == dataHash) { return; } action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash); QPixmap pix; if (!pix.loadFromData(data)) { qDebug(DBUSMENUQT) << "Failed to decode icon-data property for action" << action->text(); action->setIcon(QIcon()); return; } action->setIcon(QIcon(pix)); } void updateActionVisible(QAction *action, const QVariant &value) { action->setVisible(value.isValid() ? value.toBool() : true); } void updateActionShortcut(QAction *action, const QVariant &value) { QDBusArgument arg = value.value(); DBusMenuShortcut dmShortcut; arg >> dmShortcut; QKeySequence keySequence = dmShortcut.toKeySequence(); action->setShortcut(keySequence); } QMenu *menuForId(int id) const { if (id == 0) { return q->menu(); } QAction *action = m_actionForId.value(id); if (!action) { return nullptr; } return action->menu(); } void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); void sendEvent(int id, const QString &eventId) { m_interface->Event(id, eventId, QDBusVariant(QString()), 0u); } }; DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent) : QObject(parent) , d(new DBusMenuImporterPrivate) { DBusMenuTypes_register(); d->q = this; d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this); d->m_menu = nullptr; d->m_pendingLayoutUpdateTimer = new QTimer(this); d->m_pendingLayoutUpdateTimer->setSingleShot(true); connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates); connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated); connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested); connect(d->m_interface, &DBusMenuInterface::ItemsPropertiesUpdated, this, [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { d->slotItemsPropertiesUpdated(updatedList, removedList); }); d->refresh(0); } DBusMenuImporter::~DBusMenuImporter() { // Do not use "delete d->m_menu": even if we are being deleted we should // leave enough time for the menu to finish what it was doing, for example // if it was being displayed. d->m_menu->deleteLater(); delete d; } void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId) { Q_UNUSED(revision) if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { return; } d->m_pendingLayoutUpdates << parentId; if (!d->m_pendingLayoutUpdateTimer->isActive()) { d->m_pendingLayoutUpdateTimer->start(); } } void DBusMenuImporter::processPendingLayoutUpdates() { QSet ids = d->m_pendingLayoutUpdates; d->m_pendingLayoutUpdates.clear(); Q_FOREACH(int id, ids) { d->refresh(id); } } QMenu *DBusMenuImporter::menu() const { if (!d->m_menu) { d->m_menu = d->createMenu(nullptr); } return d->m_menu; } void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { Q_FOREACH(const DBusMenuItem &item, updatedList) { QAction *action = m_actionForId.value(item.id); if (!action) { // We don't know this action. It probably is in a menu we haven't fetched yet. continue; } QVariantMap::ConstIterator it = item.properties.constBegin(), end = item.properties.constEnd(); for(; it != end; ++it) { updateActionProperty(action, it.key(), it.value()); } } Q_FOREACH(const DBusMenuItemKeys &item, removedList) { QAction *action = m_actionForId.value(item.id); if (!action) { // We don't know this action. It probably is in a menu we haven't fetched yet. continue; } Q_FOREACH(const QString &key, item.properties) { updateActionProperty(action, key, QVariant()); } } } QAction *DBusMenuImporter::actionForId(int id) const { return d->m_actionForId.value(id); } void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/) { QAction *action = d->m_actionForId.value(id); DMRETURN_IF_FAIL(action); actionActivationRequested(action); } void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher) { int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); watcher->deleteLater(); QMenu *menu = d->menuForId(parentId); QDBusPendingReply reply = *watcher; if (!reply.isValid()) { qDebug(DBUSMENUQT) << reply.error().message(); if (menu) { emit menuUpdated(menu); } return; } #ifdef BENCHMARK DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; #endif DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); if (!menu) { qDebug(DBUSMENUQT) << "No menu for id" << parentId; return; } //remove outdated actions QSet 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)) { // Not calling removeAction() as QMenu will immediately close when it becomes empty, // which can happen when an application completely reloads this menu. // When the action is deleted deferred, it is removed from the menu. 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]() { + connect(action, &QAction::triggered, this, [ id, this]() { sendClickedEvent(id); }); if (QMenu *menuAction = action->menu()) { connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection); } connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection); menu->addAction(action); } else { action = *it; QStringList filteredKeys = dbusMenuItem.properties.keys(); filteredKeys.removeOne("type"); filteredKeys.removeOne("toggle-type"); filteredKeys.removeOne("children-display"); d->updateAction(*it, dbusMenuItem.properties, filteredKeys); // Move the action to the tail so we can keep the order same as the dbus request. menu->removeAction(action); menu->addAction(action); } } emit menuUpdated(menu); } void DBusMenuImporter::sendClickedEvent(int id) { d->sendEvent(id, QStringLiteral("clicked")); } void DBusMenuImporter::updateMenu() { updateMenu(DBusMenuImporter::menu()); } void DBusMenuImporter::updateMenu(QMenu * menu) { Q_ASSERT(menu); QAction *action = menu->menuAction(); Q_ASSERT(action); int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); auto call = d->m_interface->AboutToShow(id); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); watcher->setProperty(DBUSMENU_PROPERTY_ID, id); connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusMenuImporter::slotAboutToShowDBusCallFinished); // Firefox deliberately ignores "aboutToShow" whereas Qt ignores" opened", so we'll just send both all the time... d->sendEvent(id, QStringLiteral("opened")); } void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) { int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); watcher->deleteLater(); QMenu *menu = d->menuForId(id); if (!menu) { return; } QDBusPendingReply reply = *watcher; if (reply.isError()) { qDebug(DBUSMENUQT) << "Call to AboutToShow() failed:" << reply.error().message(); menuUpdated(menu); return; } //Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before //this returns, which equates to the same thing bool needRefresh = reply.argumentAt<0>(); if (needRefresh || menu->actions().isEmpty()) { d->m_idsRefreshedByAboutToShow << id; d->refresh(id); } else if (menu) { menuUpdated(menu); } } void DBusMenuImporter::slotMenuAboutToHide() { QMenu *menu = qobject_cast(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); updateMenu(menu); } QMenu *DBusMenuImporter::createMenu(QWidget *parent) { return new QMenu(parent); } QIcon DBusMenuImporter::iconForName(const QString &/*name*/) { return QIcon(); } #include "moc_dbusmenuimporter.cpp" diff --git a/shell/currentcontainmentactionsmodel.cpp b/shell/currentcontainmentactionsmodel.cpp index 92f83898a..48300d36f 100644 --- a/shell/currentcontainmentactionsmodel.cpp +++ b/shell/currentcontainmentactionsmodel.cpp @@ -1,289 +1,289 @@ /* * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "currentcontainmentactionsmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CurrentContainmentActionsModel::CurrentContainmentActionsModel(Plasma::Containment *containment, QObject *parent) : QStandardItemModel(parent), m_containment(containment), m_tempConfigParent(QString(), KConfig::SimpleConfig) { m_baseCfg = KConfigGroup(m_containment->corona()->config(), "ActionPlugins"); m_baseCfg = KConfigGroup(&m_baseCfg, QString::number(m_containment->containmentType())); QHash actions = containment->containmentActions(); QHashIterator i(actions); while (i.hasNext()) { i.next(); QStandardItem *item = new QStandardItem(); item->setData(i.key(), ActionRole); item->setData(i.value()->pluginInfo().pluginName(), PluginNameRole); m_plugins[i.key()] = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, i.value()->pluginInfo().pluginName()); m_plugins[i.key()]->setContainment(m_containment); KConfigGroup cfg(&m_baseCfg, i.key()); m_plugins[i.key()]->restore(cfg); item->setData(m_plugins[i.key()]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); appendRow(item); } } CurrentContainmentActionsModel::~CurrentContainmentActionsModel() { } QHash CurrentContainmentActionsModel::roleNames() const { return { { ActionRole, "action" }, { PluginNameRole, "pluginName" }, { HasConfigurationInterfaceRole, "hasConfigurationInterface" } }; } QString CurrentContainmentActionsModel::mouseEventString(int mouseButton, int modifiers) { QMouseEvent *mouse = new QMouseEvent(QEvent::MouseButtonRelease, QPoint(), (Qt::MouseButton)mouseButton, (Qt::MouseButton)mouseButton, (Qt::KeyboardModifiers) modifiers); const QString string = Plasma::ContainmentActions::eventToString(mouse); delete mouse; return string; } QString CurrentContainmentActionsModel::wheelEventString(const QPointF &delta, int mouseButtons, int modifiers) { QWheelEvent *wheel = new QWheelEvent(QPointF(), QPointF(), delta.toPoint(), QPoint(), 0, qAbs(delta.x()) > qAbs(delta.y()) ? Qt::Horizontal : Qt::Vertical, (Qt::MouseButtons)mouseButtons, (Qt::KeyboardModifiers) modifiers); const QString string = Plasma::ContainmentActions::eventToString(wheel); delete wheel; return string; } bool CurrentContainmentActionsModel::isTriggerUsed(const QString &trigger) { return m_plugins.contains(trigger); } bool CurrentContainmentActionsModel::append(const QString &action, const QString &plugin) { if (m_plugins.contains(action)) { return false; } QStandardItem *item = new QStandardItem(); item->setData(action, ActionRole); item->setData(plugin, PluginNameRole); Plasma::ContainmentActions *actions = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, plugin); if (!actions) { return false; } m_plugins[action] = actions; m_plugins[action]->setContainment(m_containment); //empty config: the new one will ne in default state KConfigGroup tempConfig(&m_tempConfigParent, "test"); m_plugins[action]->restore(tempConfig); item->setData(m_plugins[action]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); m_removedTriggers.removeAll(action); appendRow(item); emit configurationChanged(); return true; } void CurrentContainmentActionsModel::update(int row, const QString &action, const QString &plugin) { const QString oldPlugin = itemData(index(row, 0)).value(PluginNameRole).toString(); const QString oldTrigger = itemData(index(row, 0)).value(ActionRole).toString(); if (oldTrigger == action && oldPlugin == plugin) { return; } QModelIndex idx = index(row, 0); if (idx.isValid()) { setData(idx, action, ActionRole); setData(idx, plugin, PluginNameRole); delete m_plugins[oldTrigger]; m_plugins.remove(oldTrigger); if (oldPlugin != plugin) { m_removedTriggers << oldTrigger; } if (!m_plugins.contains(action) || oldPlugin != plugin) { delete m_plugins[action]; m_plugins[action] = Plasma::PluginLoader::self()->loadContainmentActions(m_containment, plugin); m_plugins[action]->setContainment(m_containment); //empty config: the new one will ne in default state KConfigGroup tempConfig(&m_tempConfigParent, "test"); m_plugins[action]->restore(tempConfig); setData(idx, m_plugins[action]->pluginInfo().property(QStringLiteral("X-Plasma-HasConfigurationInterface")).toBool(), HasConfigurationInterfaceRole); } emit configurationChanged(); } } void CurrentContainmentActionsModel::remove(int row) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); removeRows(row, 1); if (m_plugins.contains(action)) { delete m_plugins[action]; m_plugins.remove(action); m_removedTriggers << action; emit configurationChanged(); } } void CurrentContainmentActionsModel::showConfiguration(int row, QQuickItem *ctx) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); if (!m_plugins.contains(action)) { return; } QDialog *configDlg = new QDialog(); configDlg->setAttribute(Qt::WA_DeleteOnClose); QLayout *lay = new QVBoxLayout(configDlg); configDlg->setLayout(lay); if (ctx && ctx->window()) { configDlg->setWindowModality(Qt::WindowModal); configDlg->winId(); // so it creates the windowHandle(); configDlg->windowHandle()->setTransientParent(ctx->window()); } Plasma::ContainmentActions *pluginInstance = m_plugins[action]; //put the config in the dialog QWidget *w = pluginInstance->createConfigurationInterface(configDlg); QString title; if (w) { lay->addWidget(w); title = w->windowTitle(); } configDlg->setWindowTitle(title.isEmpty() ? i18n("Configure Mouse Actions Plugin") :title); //put buttons below QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, configDlg); lay->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, configDlg, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, configDlg, &QDialog::reject); QObject::connect(configDlg, &QDialog::accepted, pluginInstance, - [configDlg, pluginInstance] () { + [ pluginInstance] () { pluginInstance->configurationAccepted(); }); connect(configDlg, &QDialog::accepted, this, &CurrentContainmentActionsModel::configurationChanged); connect(pluginInstance, &QObject::destroyed, configDlg, &QDialog::reject); configDlg->show(); } void CurrentContainmentActionsModel::showAbout(int row, QQuickItem *ctx) { const QString action = itemData(index(row, 0)).value(ActionRole).toString(); if (!m_plugins.contains(action)) { return; } KPluginInfo info = m_plugins[action]->pluginInfo(); KAboutData aboutData(info.name(), ki18n(info.name().toUtf8()).toString(), info.version(), ki18n(info.comment().toUtf8()).toString(), KAboutLicense::byKeyword(info.license()).key(), QString(), QString(), info.website(), info.email()); aboutData.addAuthor(ki18n(info.author().toUtf8()).toString(), QString(), info.email()); KAboutApplicationDialog *aboutDialog = new KAboutApplicationDialog(aboutData, qobject_cast(parent())); aboutDialog->setWindowIcon(QIcon::fromTheme(info.icon())); aboutDialog->setAttribute(Qt::WA_DeleteOnClose); if (ctx && ctx->window()) { aboutDialog->setWindowModality(Qt::WindowModal); aboutDialog->winId(); // so it creates the windowHandle(); aboutDialog->windowHandle()->setTransientParent(ctx->window()); } aboutDialog->show(); } void CurrentContainmentActionsModel::save() { foreach (const QString &removedTrigger, m_removedTriggers) { m_containment->setContainmentActions(removedTrigger, QString()); } m_removedTriggers.clear(); QHashIterator i(m_plugins); while (i.hasNext()) { i.next(); KConfigGroup cfg(&m_baseCfg, i.key()); i.value()->save(cfg); m_containment->setContainmentActions(i.key(), i.value()->pluginInfo().pluginName()); } } #include "moc_currentcontainmentactionsmodel.cpp"