diff --git a/applets/taskmanager/CMakeLists.txt b/applets/taskmanager/CMakeLists.txt --- a/applets/taskmanager/CMakeLists.txt +++ b/applets/taskmanager/CMakeLists.txt @@ -26,6 +26,7 @@ KF5::I18n KF5::KIOCore KF5::KIOWidgets + KF5::KIOFileWidgets # KFilePlacesModel KF5::Plasma KF5::Service KF5::WindowSystem) diff --git a/applets/taskmanager/package/contents/ui/ContextMenu.qml b/applets/taskmanager/package/contents/ui/ContextMenu.qml --- a/applets/taskmanager/package/contents/ui/ContextMenu.qml +++ b/applets/taskmanager/package/contents/ui/ContextMenu.qml @@ -30,10 +30,13 @@ PlasmaComponents.ContextMenu { id: menu + property QtObject backend property QtObject mpris2Source property var modelIndex readonly property var atm: TaskManager.AbstractTasksModel + property bool showAllPlaces: false + placement: { if (plasmoid.location == PlasmaCore.Types.LeftEdge) { return PlasmaCore.Types.RightPosedTopAlignedPopup; @@ -57,6 +60,13 @@ } } + Component.onCompleted: { + // Cannot have "Connections" as child of PlasmaCoponents.ContextMenu. + backend.showAllPlaces.connect(function() { + visualParent.showContextMenu({showAllPlaces: true}); + }); + } + function get(modelProp) { return tasksModel.data(modelIndex, modelProp) } @@ -81,29 +91,23 @@ } function loadDynamicLaunchActions(launcherUrl) { - var actionList = backend.jumpListActions(launcherUrl, menu); - - for (var i = 0; i < actionList.length; ++i) { - var item = newMenuItem(menu); - item.action = actionList[i]; - menu.addMenuItem(item, virtualDesktopsMenuItem); - } - - if (actionList.length > 0) { - menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); - } - - var actionList = backend.recentDocumentActions(launcherUrl, menu); - - for (var i = 0; i < actionList.length; ++i) { - var item = newMenuItem(menu); - item.action = actionList[i]; - menu.addMenuItem(item, virtualDesktopsMenuItem); - } + var lists = [ + backend.jumpListActions(launcherUrl, menu), + backend.placesActions(launcherUrl, showAllPlaces, menu), + backend.recentDocumentActions(launcherUrl, menu) + ] + + lists.forEach(function (list) { + for (var i = 0; i < list.length; ++i) { + var item = newMenuItem(menu); + item.action = list[i]; + menu.addMenuItem(item, virtualDesktopsMenuItem); + } - if (actionList.length > 0) { - menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); - } + if (list.length > 0) { + menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); + } + }); // Add Media Player control actions var sourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl, get(atm.AppPid)); diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml --- a/applets/taskmanager/package/contents/ui/Task.qml +++ b/applets/taskmanager/package/contents/ui/Task.qml @@ -126,7 +126,13 @@ pressX = mouse.x; pressY = mouse.y; } else if (mouse.button == Qt.RightButton) { - tasks.createContextMenu(task, modelIndex()).show(); + // When we're a launcher, there's no window controls, so we can show all + // places without the menu getting super huge. + if (model.IsLauncher === true) { + showContextMenu({showAllPlaces: true}) + } else { + showContextMenu(); + } } } @@ -200,6 +206,10 @@ : tasksModel.makeModelIndex(index)); } + function showContextMenu(args) { + tasks.createContextMenu(task, modelIndex(), args).show(); + } + function updateAudioStreams() { var pa = pulseAudio.item; if (!pa) { diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml --- a/applets/taskmanager/package/contents/ui/main.qml +++ b/applets/taskmanager/package/contents/ui/main.qml @@ -429,12 +429,14 @@ dragSource = null; } - function createContextMenu(rootTask, modelIndex) { - return tasks.contextMenuComponent.createObject(rootTask, - { visualParent: rootTask, - modelIndex: modelIndex, - mpris2Source: mpris2Source - }); + function createContextMenu(rootTask, modelIndex, args) { + var initialArgs = args || {} + initialArgs.visualParent = rootTask; + initialArgs.modelIndex = modelIndex; + initialArgs.mpris2Source = mpris2Source; + initialArgs.backend = backend; + + return tasks.contextMenuComponent.createObject(rootTask, initialArgs); } Component.onCompleted: { diff --git a/applets/taskmanager/plugin/backend.h b/applets/taskmanager/plugin/backend.h --- a/applets/taskmanager/plugin/backend.h +++ b/applets/taskmanager/plugin/backend.h @@ -66,6 +66,7 @@ void setHighlightWindows(bool highlight); Q_INVOKABLE QVariantList jumpListActions(const QUrl &launcherUrl, QObject *parent); + Q_INVOKABLE QVariantList placesActions(const QUrl &launcherUrl, bool showAllPlaces, QObject *parent); Q_INVOKABLE QVariantList recentDocumentActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE void setActionGroup(QAction *action) const; @@ -91,6 +92,8 @@ void highlightWindowsChanged() const; void addLauncher(const QUrl &url) const; + void showAllPlaces(); + private Q_SLOTS: void toolTipWindowChanged(QQuickWindow *window); void handleJumpListAction() const; diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp --- a/applets/taskmanager/plugin/backend.cpp +++ b/applets/taskmanager/plugin/backend.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include @@ -157,6 +159,67 @@ return actions; } +QVariantList Backend::placesActions(const QUrl &launcherUrl, bool showAllPlaces, QObject *parent) +{ + QVariantList actions; + + if (!parent || !launcherUrl.isValid() || !launcherUrl.isLocalFile() + || !KDesktopFile::isDesktopFile(launcherUrl.toLocalFile())) { + return actions; + } + + KDesktopFile desktopFile(launcherUrl.toLocalFile()); + + // Since we can't have dynamic jump list actions, at least add the user's "Places" for file managers. + const QStringList &categories = desktopFile.desktopGroup().readXdgListEntry(QStringLiteral("Categories")); + if (!categories.contains(QLatin1String("FileManager"))) { + return actions; + } + + QScopedPointer placesModel(new KFilePlacesModel()); + for (int i = 0; i < placesModel->rowCount(); ++i) { + QModelIndex idx = placesModel->index(i, 0); + + if (placesModel->data(idx, KFilePlacesModel::HiddenRole).toBool()) { + continue; + } + + const QString &title = placesModel->data(idx, Qt::DisplayRole).toString(); + const QIcon &icon = placesModel->data(idx, Qt::DecorationRole).value(); + const QUrl &url = placesModel->data(idx, KFilePlacesModel::UrlRole).toUrl(); + + QAction *action = new QAction(icon, title, parent); + + connect(action, &QAction::triggered, this, [this, action, url, launcherUrl] { + KService::Ptr service = KService::serviceByDesktopPath(launcherUrl.toLocalFile()); + if (!service) { + return; + } + + KRun::runService(*service, {url}, QApplication::activeWindow()); + }); + + actions << QVariant::fromValue(action); + } + + // There is nothing more frustrating than having a "More" entry that ends up showing just one or two + // additional entries. Therefore we truncate to max. 5 entries only if there are more than 7 in total. + if (!showAllPlaces && actions.count() > 7) { + const int totalActionCount = actions.count(); + + while (actions.count() > 5) { + actions.removeLast(); + } + + QAction *action = new QAction(parent); + action->setText(i18ncp("Show all user Places", "%1 more Place", "%1 more Places", totalActionCount - actions.count())); + connect(action, &QAction::triggered, this, &Backend::showAllPlaces); + actions << QVariant::fromValue(action); + } + + return actions; +} + QVariantList Backend::recentDocumentActions(const QUrl &launcherUrl, QObject *parent) { QVariantList actions;