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/config/main.xml b/applets/taskmanager/package/contents/config/main.xml --- a/applets/taskmanager/package/contents/config/main.xml +++ b/applets/taskmanager/package/contents/config/main.xml @@ -75,6 +75,10 @@ + + + + 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 @@ -28,7 +28,11 @@ PlasmaComponents.ContextMenu { id: menu + property QtObject backend property QtObject mpris2Source + property var placesPreferences: ({}) + + property bool showAllPlaces placement: { if (plasmoid.location == PlasmaCore.Types.LeftEdge) { @@ -52,6 +56,13 @@ } } + Component.onCompleted: { + // Cannot have "Connections" as child of PlasmaCoponents.ContextMenu. + backend.showAllPlaces.connect(function() { + visualParent.showContextMenu({showAllPlaces: true}); + }); + } + function show() { loadDynamicLaunchActions(visualParent.m.LauncherUrlWithoutIcon); backend.ungrabMouse(visualParent); @@ -73,29 +84,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, placesPreferences, 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); 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 @@ -100,11 +100,7 @@ pressX = mouse.x; pressY = mouse.y; } else if (mouse.button == Qt.RightButton) { - if (plasmoid.configuration.showToolTips) { - toolTip.hideToolTip(); - } - - tasks.createContextMenu(task).show(); + showContextMenu(); } } @@ -169,6 +165,14 @@ : tasksModel.makeModelIndex(index)); } + function showContextMenu(args) { + if (plasmoid.configuration.showToolTips) { + toolTip.hideToolTip(); + } + + tasks.createContextMenu(task, args).show(); + } + Component { id: taskInitComponent 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 @@ -57,6 +57,21 @@ signal windowsHovered(variant winIds, bool hovered) signal presentWindows(variant winIds) + readonly property var placesPreferences: { + var preferences = {} + + var preferencesConfig = plasmoid.configuration.placesPreferences + if (preferencesConfig) { + try { + preferences = JSON.parse(preferencesConfig) + } catch (e) { + console.warn("Failed to parse Task Manager places preferencesConfig", e) + } + } + + return preferences + } + onWidthChanged: { taskList.width = LayoutManager.layoutWidth(); @@ -176,6 +191,17 @@ onAddLauncher: { tasks.addLauncher(url); } + + onTrackPlaceLaunch: { + var stringUrl = url.toString() + if (!stringUrl) { + return + } + + var preferences = placesPreferences + preferences[stringUrl] = (preferences[stringUrl] || 0) + 1 + plasmoid.configuration.placesPreferences = JSON.stringify(preferences) + } } PlasmaCore.DataSource { @@ -404,11 +430,13 @@ dragSource = null; } - function createContextMenu(task) { - var menu = tasks.contextMenuComponent.createObject(task); - menu.visualParent = task; - menu.mpris2Source = mpris2Source; - return menu; + function createContextMenu(task, args) { + var initialArgs = args || {} + initialArgs.visualParent = task; + initialArgs.mpris2Source = mpris2Source; + initialArgs.placesPreferences = tasks.placesPreferences; + initialArgs.backend = backend + return tasks.contextMenuComponent.createObject(task, 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, const QJsonObject &preferences, QObject *parent); Q_INVOKABLE QVariantList recentDocumentActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE void setActionGroup(QAction *action) const; @@ -91,6 +92,9 @@ void highlightWindowsChanged() const; void addLauncher(const QUrl &url) const; + void trackPlaceLaunch(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,14 +33,18 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include + namespace KAStats = KActivities::Stats; using namespace KAStats; @@ -157,6 +162,77 @@ return actions; } +QVariantList Backend::placesActions(const QUrl &launcherUrl, bool showAllPlaces, const QJsonObject &preferences, 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(parent); + action->setText(title); + action->setIcon(icon); + action->setProperty("preference", preferences.value(url.toString()).toInt()); + + 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()); + emit trackPlaceLaunch(url); + }); + + actions << QVariant::fromValue(action); + } + + if (!showAllPlaces && actions.count() > 7) { + const int totalActionCount = actions.count(); + + QVariantList preferenceSortedActions = actions; + std::sort(preferenceSortedActions.begin(), preferenceSortedActions.end(), [](const QVariant &a, const QVariant &b) { + auto *actionA = a.value(); + auto *actionB = b.value(); + return actionA->property("preference").toInt() > actionB->property("preference").toInt(); + }); + + while (actions.count() > 5) { + actions.removeOne(preferenceSortedActions.takeLast()); + } + + + 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; diff --git a/containments/panel/contents/code/LayoutManager.js b/containments/panel/contents/code/LayoutManager.js --- a/containments/panel/contents/code/LayoutManager.js +++ b/containments/panel/contents/code/LayoutManager.js @@ -29,30 +29,25 @@ //array, a cell for encoded item order var itemsArray = configString.split(";"); - //map applet id->order in panel - var idsOrder = new Object(); - //map order in panel -> applet pointer - var appletsOrder = new Object(); + var applets = plasmoid.applets + applets.sort(function (a, b) { + var aIdx = itemsArray.indexOf(String(a.id)) + var bIdx = itemsArray.indexOf(String(b.id)) + + // applets that aren't listed in the order are sorted last + if (aIdx === -1 && bIdx !== -1) { + return 1 + } else if (aIdx !== -1 && bIdx === -1) { + return -1 + } - for (var i = 0; i < itemsArray.length; i++) { - //property name: applet id - //property value: order - idsOrder[itemsArray[i]] = i; - } + return aIdx - bIdx + }) - for (var i = 0; i < plasmoid.applets.length; ++i) { - if (idsOrder[plasmoid.applets[i].id] !== undefined) { - appletsOrder[idsOrder[plasmoid.applets[i].id]] = plasmoid.applets[i]; - //ones that weren't saved in AppletOrder go to the end - } else { - appletsOrder["unordered"+i] = plasmoid.applets[i]; - } - } + applets.forEach(function (applet) { + root.addApplet(applet, -1, -1) + }) - //finally, restore the applets in the correct order - for (var i in appletsOrder) { - root.addApplet(appletsOrder[i], -1, -1) - } //rewrite, so if in the orders there were now invalid ids or if some were missing creates a correct list instead save(); }