diff --git a/applets/kicker/plugin/actionlist.cpp b/applets/kicker/plugin/actionlist.cpp index 16661da00..54a5a1c21 100644 --- a/applets/kicker/plugin/actionlist.cpp +++ b/applets/kicker/plugin/actionlist.cpp @@ -1,311 +1,405 @@ /*************************************************************************** * Copyright (C) 2013 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 "actionlist.h" +#include "menuentryeditor.h" + +#include #include +#include #include #include #include +#include #include #include #include #include #include "containmentinterface.h" +#ifdef HAVE_APPSTREAMQT +#include +#endif + namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; namespace Kicker { QVariantMap createActionItem(const QString &label, const QString &actionId, const QVariant &argument) { QVariantMap map; map["text"] = label; map["actionId"] = actionId; if (argument.isValid()) { map["actionArgument"] = argument; } return map; } QVariantMap createTitleActionItem(const QString &label) { QVariantMap map; map["text"] = label; map["type"] = "title"; return map; } QVariantMap createSeparatorActionItem() { QVariantMap map; map["type"] = "separator"; return map; } QVariantList createActionListForFileItem(const KFileItem &fileItem) { QVariantList list; KService::List services = KMimeTypeTrader::self()->query(fileItem.mimetype(), "Application"); if (!services.isEmpty()) { list << createTitleActionItem(i18n("Open with:")); foreach (const KService::Ptr service, services) { const QString text = service->name().replace('&', "&&"); QVariantMap item = createActionItem(text, "_kicker_fileItem_openWith", service->entryPath()); item["icon"] = service->icon(); list << item; } list << createSeparatorActionItem(); } list << createActionItem(i18n("Properties"), "_kicker_fileItem_properties"); return list; } bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close) { if (actionId == "_kicker_fileItem_properties") { KPropertiesDialog *dlg = new KPropertiesDialog(fileItem, QApplication::activeWindow()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); *close = false; return true; } if (actionId == "_kicker_fileItem_openWith") { const QString path = argument.toString(); const KService::Ptr service = KService::serviceByDesktopPath(path); if (!service) { return false; } KRun::runService(*service, QList() << fileItem.url(), QApplication::activeWindow()); *close = true; return true; } return false; } QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service) { QVariantList actionList; if (!service) { return actionList; } if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { actionList << Kicker::createActionItem(i18n("Add to Desktop"), "addToDesktop"); } if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { actionList << Kicker::createActionItem(i18n("Add to Panel (Widget)"), "addToPanel"); } if (service && ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, service->entryPath())) { actionList << Kicker::createActionItem(i18n("Pin to Task Manager"), "addToTaskManager"); } return actionList; } bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service) { if (!service) { return false; } if (actionId == QLatin1String("addToDesktop")) { if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Desktop)) { ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Desktop, service->entryPath()); } return true; } else if (actionId == QLatin1String("addToPanel")) { if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::Panel)) { ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::Panel, service->entryPath()); } return true; } else if (actionId == QLatin1String("addToTaskManager")) { if (ContainmentInterface::mayAddLauncher(appletInterface, ContainmentInterface::TaskManager, service->entryPath())) { ContainmentInterface::addLauncher(appletInterface, ContainmentInterface::TaskManager, service->entryPath()); } return true; } return false; } // HACK TEMP FIXME TODO IVAN QString storageIdFromService(KService::Ptr service) { QString storageId = service->storageId(); if (storageId.startsWith("org.kde.")) { storageId = storageId.right(storageId.length() - 8); } if (storageId.endsWith(".desktop")) { storageId = storageId.left(storageId.length() - 8); } return storageId; } QVariantList jumpListActions(KService::Ptr service) { QVariantList list; if (!service) { return list; } const auto &actions = service->actions(); foreach (const KServiceAction &action, actions) { if (action.text().isEmpty() || action.exec().isEmpty()) { continue; } QVariantMap item = createActionItem(action.text(), "_kicker_jumpListAction", action.exec()); item[QStringLiteral("icon")] = action.icon(); list << item; } return list; } QVariantList recentDocumentActions(KService::Ptr service) { QVariantList list; if (!service) { return list; } const QString storageId = storageIdFromService(service); if (storageId.isEmpty()) { return list; } auto query = UsedResources | RecentlyUsedFirst | Agent(storageId) | Type::any() | Activity::current() | Url::file(); ResultSet results(query); ResultSet::const_iterator resultIt; resultIt = results.begin(); while (list.count() < 6 && resultIt != results.end()) { const QString resource = (*resultIt).resource(); const QUrl url(resource); if (!url.isValid()) { continue; } const KFileItem fileItem(url); if (!fileItem.isFile()) { continue; } if (list.count() == 0) { list << createTitleActionItem(i18n("Recent Documents")); } QVariantMap item = createActionItem(url.fileName(), "_kicker_recentDocument", resource); item["icon"] = fileItem.iconName(); list << item; ++resultIt; } if (list.count()) { list << createActionItem(i18n("Forget Recent Documents"), "_kicker_forgetRecentDocuments"); } return list; } bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &_argument) { if (!service) { return false; } if (actionId == "_kicker_forgetRecentDocuments") { const QString storageId = storageIdFromService(service); if (storageId.isEmpty()) { return false; } auto query = UsedResources | Agent(storageId) | Type::any() | Activity::current() | Url::file(); KAStats::forgetResources(query); return false; } QString argument = _argument.toString(); if (argument.isEmpty()) { return false; } return (KRun::runService(*service, QList() << QUrl(argument), QApplication::activeWindow()) != 0); } +Q_GLOBAL_STATIC(MenuEntryEditor, menuEntryEditor) + +bool canEditApplication(const QString &entryPath) +{ + return menuEntryEditor->canEdit(entryPath); +} + +void editApplication(const QString &entryPath, const QString &menuId) +{ + menuEntryEditor->edit(entryPath, menuId); +} + +QVariantList editApplicationAction(const KService::Ptr &service) +{ + QVariantList actionList; + + if (canEditApplication(service->entryPath())) { + QVariantMap editAction = Kicker::createActionItem(i18n("Edit Application..."), "editApplication"); + editAction["icon"] = "kmenuedit"; // TODO: Using the KMenuEdit icon might be misleading. + actionList << editAction; + } + + return actionList; +} + +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service) +{ + + if (service && actionId == "editApplication" && canEditApplication(service->entryPath())) { + Kicker::editApplication(service->entryPath(), service->menuId()); + + return true; + } + + return false; +} + +#ifdef HAVE_APPSTREAMQT +Q_GLOBAL_STATIC(AppStream::Pool, appstreamPool) +#endif + +QVariantList appstreamActions(const KService::Ptr &service) +{ + QVariantList ret; + +#ifdef HAVE_APPSTREAMQT + const KService::Ptr appStreamHandler = KMimeTypeTrader::self()->preferredService(QStringLiteral("x-scheme-handler/appstream")); + + // Don't show action if we can't find any app to handle appstream:// URLs. + if (!appStreamHandler) { + if (!KProtocolInfo::isHelperProtocol(QStringLiteral("appstream")) + || KProtocolInfo::exec(QStringLiteral("appstream")).isEmpty()) { + return ret; + } + } + + if (!appstreamPool.exists()) { + appstreamPool->load(); + } + + const auto components = appstreamPool->componentsById(service->desktopEntryName()+QLatin1String(".desktop")); + for(const auto &component: components) { + const QString componentId = component.id(); + + QVariantMap appstreamAction = Kicker::createActionItem(i18nc("@action opens a software center with the application", "Manage '%1'...", component.name()), "manageApplication", QVariant(QStringLiteral("appstream://") + componentId)); + appstreamAction[QStringLiteral("icon")] = QStringLiteral("applications-other"); + ret << appstreamAction; + } +#else + Q_UNUSED(service) +#endif + + return ret; +} + +bool handleAppstreamActions(const QString &actionId, const QVariant &argument) +{ + if (actionId == "manageApplication") { + return QDesktopServices::openUrl(QUrl(argument.toString())); + } + + return false; +} + + } diff --git a/applets/kicker/plugin/actionlist.h b/applets/kicker/plugin/actionlist.h index 869354c9c..85fac2557 100644 --- a/applets/kicker/plugin/actionlist.h +++ b/applets/kicker/plugin/actionlist.h @@ -1,60 +1,68 @@ /*************************************************************************** * Copyright (C) 2013 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 #include class KFileItem; namespace Kicker { enum { DescriptionRole = Qt::UserRole + 1, GroupRole, FavoriteIdRole, IsSeparatorRole, IsDropPlaceholderRole, IsParentRole, HasChildrenRole, HasActionListRole, ActionListRole, UrlRole }; QVariantMap createActionItem(const QString &label, const QString &actionId, const QVariant &argument = QVariant()); QVariantMap createTitleActionItem(const QString &label); QVariantMap createSeparatorActionItem(); QVariantList createActionListForFileItem(const KFileItem &fileItem); bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close); QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service); bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service); QVariantList jumpListActions(KService::Ptr service); QVariantList recentDocumentActions(KService::Ptr service); bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &argument); +bool canEditApplication(const QString &entryPath); +void editApplication(const QString &entryPath, const QString &menuId); +QVariantList editApplicationAction(const KService::Ptr &service); +bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service); + +QVariantList appstreamActions(const KService::Ptr &service); +bool handleAppstreamActions(const QString &actionId, const QVariant &argument); + } diff --git a/applets/kicker/plugin/appentry.cpp b/applets/kicker/plugin/appentry.cpp index ee58657e4..6f2cc8f8a 100644 --- a/applets/kicker/plugin/appentry.cpp +++ b/applets/kicker/plugin/appentry.cpp @@ -1,354 +1,297 @@ /*************************************************************************** * Copyright (C) 201 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 #include "appentry.h" #include "actionlist.h" #include "appsmodel.h" #include "containmentinterface.h" -#include "menuentryeditor.h" #include -#include #include #include #include -#include #if HAVE_X11 #include #endif #include #include #include #include #include -#include #include #include #include #include #include #include -#ifdef HAVE_APPSTREAMQT -#include -#endif - -MenuEntryEditor *AppEntry::m_menuEntryEditor = nullptr; - AppEntry::AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat) : AbstractEntry(owner) , m_service(service) { if (m_service) { init(nameFormat); } } AppEntry::AppEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) { const QUrl url(id); if (url.scheme() == QStringLiteral("preferred")) { m_service = defaultAppByName(url.host()); m_id = id; } else { m_service = KService::serviceByStorageId(id); } if (m_service) { init((NameFormat)owner->rootModel()->property("appNameFormat").toInt()); } } void AppEntry::init(NameFormat nameFormat) { m_name = nameFromService(m_service, nameFormat); if (nameFormat == GenericNameOnly) { m_description = nameFromService(m_service, NameOnly); } else { m_description = nameFromService(m_service, GenericNameOnly); } - - if (!m_menuEntryEditor) { - m_menuEntryEditor = new MenuEntryEditor(); - } } bool AppEntry::isValid() const { return m_service; } QIcon AppEntry::icon() const { if (m_icon.isNull()) { m_icon = QIcon::fromTheme(m_service->icon(), QIcon::fromTheme("unknown")); } return m_icon; } QString AppEntry::name() const { return m_name; } QString AppEntry::description() const { return m_description; } KService::Ptr AppEntry::service() const { return m_service; } QString AppEntry::id() const { if (!m_id.isEmpty()) { return m_id; } return m_service->storageId(); } QUrl AppEntry::url() const { return QUrl::fromLocalFile(m_service->entryPath()); } bool AppEntry::hasActions() const { return true; } -#ifdef HAVE_APPSTREAMQT -Q_GLOBAL_STATIC(AppStream::Pool, appstreamPool) - -QVariantList appstreamActions(const KService::Ptr &service) -{ - QVariantList ret; - - const KService::Ptr appStreamHandler = KMimeTypeTrader::self()->preferredService(QStringLiteral("x-scheme-handler/appstream")); - - // Don't show action if we can't find any app to handle appstream:// URLs. - if (!appStreamHandler) { - if (!KProtocolInfo::isHelperProtocol(QStringLiteral("appstream")) - || KProtocolInfo::exec(QStringLiteral("appstream")).isEmpty()) { - return ret; - } - } - - if (!appstreamPool.exists()) { - appstreamPool->load(); - } - - const auto components = appstreamPool->componentsById(service->desktopEntryName()+QLatin1String(".desktop")); - for(const auto &component: components) { - const QString componentId = component.id(); - - QVariantMap appstreamAction = Kicker::createActionItem(i18nc("@action opens a software center with the application", "Manage '%1'...", component.name()), "manageApplication", QVariant(QStringLiteral("appstream://") + componentId)); - appstreamAction[QStringLiteral("icon")] = QStringLiteral("applications-other"); - ret << appstreamAction; - } - return ret; -} -#endif - QVariantList AppEntry::actions() const { QVariantList actionList; actionList << Kicker::jumpListActions(m_service); if (!actionList.isEmpty()) { actionList << Kicker::createSeparatorActionItem(); } QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); const bool systemImmutable = appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable; const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, m_service); if (!systemImmutable && !addLauncherActions.isEmpty()) { actionList << addLauncherActions << Kicker::createSeparatorActionItem(); } const QVariantList &recentDocuments = Kicker::recentDocumentActions(m_service); if (!recentDocuments.isEmpty()) { actionList << recentDocuments << Kicker::createSeparatorActionItem(); } // Don't allow adding launchers, editing, hiding, or uninstalling applications // when system is immutable. if (systemImmutable) { return actionList; } - if (m_menuEntryEditor->canEdit(m_service->entryPath())) { - actionList << Kicker::createSeparatorActionItem(); - - QVariantMap editAction = Kicker::createActionItem(i18n("Edit Application..."), "editApplication"); - editAction["icon"] = "kmenuedit"; // TODO: Using the KMenuEdit icon might be misleading. - actionList << editAction; - } - -#ifdef HAVE_APPSTREAMQT if (m_service->isApplication()) { - actionList << appstreamActions(m_service); + actionList << Kicker::createSeparatorActionItem(); + actionList << Kicker::editApplicationAction(m_service); + actionList << Kicker::appstreamActions(m_service); } -#endif QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (appletConfig && appletConfig->contains("hiddenApplications") && qobject_cast(m_owner)) { const QStringList &hiddenApps = appletConfig->value("hiddenApplications").toStringList(); if (!hiddenApps.contains(m_service->menuId())) { actionList << Kicker::createActionItem(i18n("Hide Application"), "hideApplication"); } } return actionList; } bool AppEntry::run(const QString& actionId, const QVariant &argument) { if (!m_service->isValid()) { return false; } if (actionId.isEmpty()) { quint32 timeStamp = 0; #if HAVE_X11 if (QX11Info::isPlatformX11()) { timeStamp = QX11Info::appUserTime(); } #endif // TODO Once we depend on KDE Frameworks 5.24 and D1902 is merged, use KRun::runApplication instead KRun::runService(*m_service, {}, nullptr, true, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + m_service->storageId()), "org.kde.plasma.kicker"); return true; } QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); if (Kicker::handleAddLauncherAction(actionId, appletInterface, m_service)) { return true; - } else if (actionId == "editApplication" && m_menuEntryEditor->canEdit(m_service->entryPath())) { - m_menuEntryEditor->edit(m_service->entryPath(), m_service->menuId()); - + } else if (Kicker::handleEditApplicationAction(actionId, m_service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { return true; - } else if (actionId == "manageApplication") { - return QDesktopServices::openUrl(QUrl(argument.toString())); } else if (actionId == "_kicker_jumpListAction") { return KRun::run(argument.toString(), {}, nullptr, m_service->name(), m_service->icon()); } return Kicker::handleRecentDocumentAction(m_service, actionId, argument); } QString AppEntry::nameFromService(const KService::Ptr service, NameFormat nameFormat) { const QString &name = service->name(); QString genericName = service->genericName(); if (genericName.isEmpty()) { genericName = service->comment(); } if (nameFormat == NameOnly || genericName.isEmpty() || name == genericName) { return name; } else if (nameFormat == GenericNameOnly) { return genericName; } else if (nameFormat == NameAndGenericName) { return i18nc("App name (Generic name)", "%1 (%2)", name, genericName); } else { return i18nc("Generic name (App name)", "%1 (%2)", genericName, name); } } KService::Ptr AppEntry::defaultAppByName(const QString& name) { if (name == QLatin1String("browser")) { KConfigGroup config(KSharedConfig::openConfig(), "General"); QString browser = config.readPathEntry("BrowserApplication", QString()); if (browser.isEmpty()) { return KMimeTypeTrader::self()->preferredService(QLatin1String("text/html")); } else if (browser.startsWith('!')) { browser = browser.mid(1); } return KService::serviceByStorageId(browser); } return KService::Ptr(); } AppGroupEntry::AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat) : AbstractGroupEntry(parentModel), m_group(group) { AppsModel* model = new AppsModel(group->entryPath(), paginate, pageSize, flat, sorted, separators, parentModel); model->setAppNameFormat(appNameFormat); m_childModel = model; QObject::connect(parentModel, &AppsModel::cleared, model, &AppsModel::deleteLater); QObject::connect(model, &AppsModel::countChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); QObject::connect(model, &AppsModel::hiddenEntriesChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); } QIcon AppGroupEntry::icon() const { if (m_icon.isNull()) { m_icon = QIcon::fromTheme(m_group->icon(), QIcon::fromTheme("unknown")); } return m_icon; } QString AppGroupEntry::name() const { return m_group->caption(); } bool AppGroupEntry::hasChildren() const { return m_childModel && m_childModel->count() > 0; } AbstractModel *AppGroupEntry::childModel() const { return m_childModel; } diff --git a/applets/kicker/plugin/runnermatchesmodel.cpp b/applets/kicker/plugin/runnermatchesmodel.cpp index 504d8da66..03911b942 100644 --- a/applets/kicker/plugin/runnermatchesmodel.cpp +++ b/applets/kicker/plugin/runnermatchesmodel.cpp @@ -1,230 +1,250 @@ /*************************************************************************** * 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 "runnermatchesmodel.h" #include "runnermodel.h" #include "actionlist.h" #include #include #include #include #include +#include + RunnerMatchesModel::RunnerMatchesModel(const QString &runnerId, const QString &name, Plasma::RunnerManager *manager, QObject *parent) : AbstractModel(parent) , m_runnerId(runnerId) , m_name(name) , m_runnerManager(manager) { } QString RunnerMatchesModel::description() const { return m_name; } QVariant RunnerMatchesModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_matches.count()) { return QVariant(); } Plasma::QueryMatch match = m_matches.at(index.row()); if (role == Qt::DisplayRole) { return match.text(); } else if (role == Qt::DecorationRole) { if (!match.iconName().isEmpty()) { return match.iconName(); } return match.icon(); } else if (role == Kicker::DescriptionRole) { return match.subtext(); } else if (role == Kicker::FavoriteIdRole) { if (match.runner()->id() == "services") { return match.data().toString(); } } else if (role == Kicker::UrlRole) { const QString &runnerId = match.runner()->id(); if (runnerId == QLatin1String("baloosearch") || runnerId == QLatin1String("bookmarks")) { return QUrl(match.data().toString()); } else if (runnerId == QLatin1String("recentdocuments") || runnerId == QLatin1String("services")) { KService::Ptr service = KService::serviceByStorageId(match.data().toString()); if (service) { return QUrl::fromLocalFile(service->entryPath()); } } } else if (role == Kicker::HasActionListRole) { // Hack to expose the protected Plasma::AbstractRunner::actions() method. class MyRunner : public Plasma::AbstractRunner { public: using Plasma::AbstractRunner::actions;; }; MyRunner *runner = static_cast(match.runner()); Q_ASSERT(runner); return match.runner()->id() == QLatin1String("services") || !runner->actions().isEmpty(); } else if (role == Kicker::ActionListRole) { QVariantList actionList; foreach (QAction *action, m_runnerManager->actionsForMatch(match)) { QVariantMap item = Kicker::createActionItem(action->text(), "runnerAction", QVariant::fromValue(action)); item["icon"] = action->icon(); actionList << item; } if (!actionList.isEmpty()) { actionList << Kicker::createSeparatorActionItem(); } - KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + if (service) { const QVariantList &jumpListActions = Kicker::jumpListActions(service); if (!jumpListActions.isEmpty()) { actionList << jumpListActions << Kicker::createSeparatorActionItem(); } QObject *appletInterface = static_cast(parent())->appletInterface(); + const bool systemImmutable = appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable; + const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, service); - if (!addLauncherActions.isEmpty()) { + if (!systemImmutable && !addLauncherActions.isEmpty()) { actionList << addLauncherActions << Kicker::createSeparatorActionItem(); } const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); if (!recentDocuments.isEmpty()) { actionList << recentDocuments << Kicker::createSeparatorActionItem(); } + + // Don't allow adding launchers, editing, hiding, or uninstalling applications + // when system is immutable. + if (systemImmutable) { + return actionList; + } + + if (service->isApplication()) { + actionList << Kicker::editApplicationAction(service); + actionList << Kicker::appstreamActions(service); + } } return actionList; } return QVariant(); } int RunnerMatchesModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_matches.count(); } bool RunnerMatchesModel::trigger(int row, const QString &actionId, const QVariant &argument) { if (row < 0 || row >= m_matches.count()) { return false; } Plasma::QueryMatch match = m_matches.at(row); if (!match.isEnabled()) { return false; } QObject *appletInterface = static_cast(parent())->appletInterface(); - KService::Ptr service = KService::serviceByStorageId(match.data().toString()); + const KService::Ptr service = KService::serviceByStorageId(match.data().toString()); if (Kicker::handleAddLauncherAction(actionId, appletInterface, service)) { return true; + } else if (Kicker::handleEditApplicationAction(actionId, service)) { + return true; + } else if (Kicker::handleAppstreamActions(actionId, argument)) { + return true; } else if (actionId == QLatin1String("_kicker_jumpListAction")) { return KRun::run(argument.toString(), {}, nullptr, service ? service->name() : QString(), service ? service->icon() : QString()); } else if (actionId == QLatin1String("_kicker_recentDocument") || actionId == QLatin1String("_kicker_forgetRecentDocuments")) { return Kicker::handleRecentDocumentAction(service, actionId, argument); } if (!actionId.isEmpty()) { QObject *obj = argument.value(); if (!obj) { return false; } QAction *action = qobject_cast(obj); if (!action) { return false; } match.setSelectedAction(action); } m_runnerManager->run(match); return true; } void RunnerMatchesModel::setMatches(const QList< Plasma::QueryMatch > &matches) { int oldCount = m_matches.count(); int newCount = matches.count(); bool emitCountChange = (oldCount != newCount); int ceiling = qMin(oldCount, newCount); bool emitDataChange = false; for (int row = 0; row < ceiling; ++row) { if (!(m_matches.at(row) == matches.at(row))) { emitDataChange = true; break; } } if (newCount > oldCount) { beginInsertRows(QModelIndex(), oldCount, newCount - 1); m_matches = matches; endInsertRows(); } else if (newCount < oldCount) { beginRemoveRows(QModelIndex(), newCount, oldCount - 1); m_matches = matches; endRemoveRows(); } if (emitDataChange) { m_matches = matches; emit dataChanged(index(0, 0), index(ceiling - 1, 0)); } if (emitCountChange) { emit countChanged(); } } AbstractModel *RunnerMatchesModel::favoritesModel() { return static_cast(parent())->favoritesModel(); }