diff --git a/applets/taskmanager/package/contents/ui/ContextMenu.qml b/applets/taskmanager/package/contents/ui/ContextMenu.qml index 240ac4f48..3288bcda1 100644 --- a/applets/taskmanager/package/contents/ui/ContextMenu.qml +++ b/applets/taskmanager/package/contents/ui/ContextMenu.qml @@ -1,625 +1,633 @@ /*************************************************************************** * Copyright (C) 2012-2016 by Eike Hein * * Copyright (C) 2016 by 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) 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.taskmanager 0.1 as TaskManager + PlasmaComponents.ContextMenu { id: menu property QtObject mpris2Source + property var modelIndex + readonly property var atm: TaskManager.AbstractTasksModel placement: { if (plasmoid.location == PlasmaCore.Types.LeftEdge) { return PlasmaCore.Types.RightPosedTopAlignedPopup; } else if (plasmoid.location == PlasmaCore.Types.TopEdge) { return PlasmaCore.Types.BottomPosedLeftAlignedPopup; } else { return PlasmaCore.Types.TopPosedLeftAlignedPopup; } } minimumWidth: visualParent.width onStatusChanged: { - if (visualParent && visualParent.m.LauncherUrlWithoutIcon != null && status == PlasmaComponents.DialogStatus.Open) { - launcherToggleAction.checked = (tasksModel.launcherPosition(visualParent.m.LauncherUrlWithoutIcon) != -1); + if (visualParent && get(atm.LauncherUrlWithoutIcon) != null && status == PlasmaComponents.DialogStatus.Open) { + launcherToggleAction.checked = (tasksModel.launcherPosition(get(atm.LauncherUrlWithoutIcon)) != -1); activitiesDesktopsMenu.refresh(); } else if (status == PlasmaComponents.DialogStatus.Closed) { menu.destroy(); } } + function get(modelProp) { + return tasksModel.data(modelIndex, modelProp) + } + function show() { - loadDynamicLaunchActions(visualParent.m.LauncherUrlWithoutIcon); + loadDynamicLaunchActions(get(atm.LauncherUrlWithoutIcon)); backend.ungrabMouse(visualParent); openRelative(); } function newMenuItem(parent) { return Qt.createQmlObject( "import org.kde.plasma.components 2.0 as PlasmaComponents;" + "PlasmaComponents.MenuItem {}", parent); } function newSeparator(parent) { return Qt.createQmlObject( "import org.kde.plasma.components 2.0 as PlasmaComponents;" + "PlasmaComponents.MenuItem { separator: true }", parent); } 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); } if (actionList.length > 0) { menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); } - // Add Media Player control actions - var sourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl); + // Add Media Player control actions (but not if grouped) TODO: doch, wenn auf tooltip rechts geklickt!! + var sourceName = mpris2Source.sourceNameForLauncherUrl(launcherUrl, get(atm.AppPid)); - if (sourceName) { + if (sourceName && !(get(atm.LegacyWinIdList) != undefined && get(atm.LegacyWinIdList).length > 1)) { var playerData = mpris2Source.data[sourceName] if (playerData.CanControl) { var menuItem = menu.newMenuItem(menu); menuItem.text = i18nc("Play previous track", "Previous Track"); menuItem.icon = "media-skip-backward"; menuItem.enabled = Qt.binding(function() { return playerData.CanGoPrevious; }); menuItem.clicked.connect(function() { mpris2Source.goPrevious(sourceName); }); menu.addMenuItem(menuItem, virtualDesktopsMenuItem); menuItem = menu.newMenuItem(menu); // PlasmaCore Menu doesn't actually handle icons or labels changing at runtime... menuItem.text = Qt.binding(function() { return playerData.PlaybackStatus === "Playing" ? i18nc("Pause playback", "Pause") : i18nc("Start playback", "Play"); }); menuItem.icon = Qt.binding(function() { return playerData.PlaybackStatus === "Playing" ? "media-playback-pause" : "media-playback-start"; }); menuItem.clicked.connect(function() { mpris2Source.playPause(sourceName); }); menu.addMenuItem(menuItem, virtualDesktopsMenuItem); menuItem = menu.newMenuItem(menu); menuItem.text = i18nc("Stop playback", "Stop"); menuItem.icon = "media-playback-stop"; menuItem.clicked.connect(function() { mpris2Source.stop(sourceName); }); menu.addMenuItem(menuItem, virtualDesktopsMenuItem); menuItem = menu.newMenuItem(menu); menuItem.text = i18nc("Play next track", "Next Track"); menuItem.icon = "media-skip-forward"; menuItem.enabled = Qt.binding(function() { return playerData.CanGoNext; }); menuItem.clicked.connect(function() { mpris2Source.goNext(sourceName); }); menu.addMenuItem(menuItem, virtualDesktopsMenuItem); menu.addMenuItem(newSeparator(menu), virtualDesktopsMenuItem); // If we don't have a window associated with the player but we can quit // it through MPRIS we'll offer a "Quit" option instead of "Close" if (!closeWindowItem.visible && playerData.CanQuit) { menuItem = menu.newMenuItem(menu); menuItem.text = i18nc("Quit media player app", "Quit"); menuItem.icon = "application-exit"; menuItem.visible = Qt.binding(function() { return !closeWindowItem.visible; }); menuItem.clicked.connect(function() { mpris2Source.quit(sourceName); }); menu.addMenuItem(menuItem); } // If we don't have a window associated with the player but we can raise // it through MPRIS we'll offer a "Restore" option if (!startNewInstanceItem.visible && playerData.CanRaise) { menuItem = menu.newMenuItem(menu); menuItem.text = i18nc("Open or bring to the front window of media player app", "Restore"); menuItem.icon = playerData["Desktop Icon Name"]; menuItem.visible = Qt.binding(function() { return !startNewInstanceItem.visible; }); menuItem.clicked.connect(function() { mpris2Source.raise(sourceName); }); menu.addMenuItem(menuItem, startNewInstanceItem); } } } } PlasmaComponents.MenuItem { id: virtualDesktopsMenuItem visible: virtualDesktopInfo.numberOfDesktops > 1 - && (visualParent && visualParent.m.IsLauncher !== true - && visualParent.m.IsStartup !== true - && visualParent.m.IsVirtualDesktopChangeable === true) + && (visualParent && get(atm.IsLauncher) !== true + && get(atm.IsStartup) !== true + && get(atm.IsVirtualDesktopChangeable) === true) enabled: visible text: i18n("Move To &Desktop") Connections { target: virtualDesktopInfo onNumberOfDesktopsChanged: virtualDesktopsMenu.refresh() onDesktopNamesChanged: virtualDesktopsMenu.refresh() } PlasmaComponents.ContextMenu { id: virtualDesktopsMenu visualParent: virtualDesktopsMenuItem.action function refresh() { clearMenuItems(); if (virtualDesktopInfo.numberOfDesktops <= 1) { return; } var menuItem = menu.newMenuItem(virtualDesktopsMenu); menuItem.text = i18n("Move &To Current Desktop"); menuItem.enabled = Qt.binding(function() { - return menu.visualParent && menu.visualParent.m.VirtualDesktop != virtualDesktopInfo.currentDesktop; + return menu.visualParent && menu.get(atm.VirtualDesktop) != virtualDesktopInfo.currentDesktop; }); menuItem.clicked.connect(function() { - tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), virtualDesktopInfo.currentDesktop); + tasksModel.requestVirtualDesktop(menu.modelIndex, virtualDesktopInfo.currentDesktop); }); menuItem = menu.newMenuItem(virtualDesktopsMenu); menuItem.text = i18n("&All Desktops"); menuItem.checkable = true; menuItem.checked = Qt.binding(function() { - return menu.visualParent && menu.visualParent.m.IsOnAllVirtualDesktops === true; + return menu.visualParent && menu.get(atm.IsOnAllVirtualDesktops) === true; }); menuItem.clicked.connect(function() { - tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), 0); + tasksModel.requestVirtualDesktop(menu.modelIndex, 0); }); backend.setActionGroup(menuItem.action); menu.newSeparator(virtualDesktopsMenu); for (var i = 0; i < virtualDesktopInfo.desktopNames.length; ++i) { menuItem = menu.newMenuItem(virtualDesktopsMenu); menuItem.text = i18nc("1 = number of desktop, 2 = desktop name", "&%1 Desktop %2", i + 1, virtualDesktopInfo.desktopNames[i]); menuItem.checkable = true; menuItem.checked = Qt.binding((function(i) { - return function() { return menu.visualParent && menu.visualParent.m.VirtualDesktop == (i + 1) }; + return function() { return menu.visualParent && menu.get(atm.VirtualDesktop) == (i + 1) }; })(i)); menuItem.clicked.connect((function(i) { - return function() { return tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), i + 1); }; + return function() { return tasksModel.requestVirtualDesktop(menu.modelIndex, i + 1); }; })(i)); backend.setActionGroup(menuItem.action); } menu.newSeparator(virtualDesktopsMenu); menuItem = menu.newMenuItem(virtualDesktopsMenu); menuItem.text = i18n("&New Desktop"); menuItem.clicked.connect(function() { - tasksModel.requestVirtualDesktop(menu.visualParent.modelIndex(), virtualDesktopInfo.numberOfDesktops + 1) + tasksModel.requestVirtualDesktop(menu.modelIndex, virtualDesktopInfo.numberOfDesktops + 1) }); } Component.onCompleted: refresh() } } PlasmaComponents.MenuItem { id: activitiesDesktopsMenuItem visible: activityInfo.numberOfRunningActivities > 1 - && (visualParent && !visualParent.m.IsLauncher - && !visualParent.m.IsStartup) + && (visualParent && !get(atm.IsLauncher) + && !get(atm.IsStartup)) enabled: visible text: i18n("Move To &Activity") Connections { target: activityInfo onNumberOfRunningActivitiesChanged: activitiesDesktopsMenu.refresh() } PlasmaComponents.ContextMenu { id: activitiesDesktopsMenu visualParent: activitiesDesktopsMenuItem.action function refresh() { clearMenuItems(); if (activityInfo.numberOfRunningActivities <= 1) { return; } var menuItem = menu.newMenuItem(activitiesDesktopsMenu); menuItem.text = i18n("Add To Current Activity"); menuItem.enabled = Qt.binding(function() { - return menu.visualParent && menu.visualParent.m.Activities.length > 0 && - menu.visualParent.m.Activities.indexOf(activityInfo.currentActivity) < 0; + return menu.visualParent && menu.get(atm.Activities).length > 0 && + menu.get(atm.Activities).indexOf(activityInfo.currentActivity) < 0; }); menuItem.clicked.connect(function() { - tasksModel.requestActivities(menu.visualParent.modelIndex(), menu.visualParent.m.Activities.concat(activityInfo.currentActivity)); + tasksModel.requestActivities(menu.modelIndex, menu.get(atm.Activities).concat(activityInfo.currentActivity)); }); menuItem = menu.newMenuItem(activitiesDesktopsMenu); menuItem.text = i18n("All Activities"); menuItem.checkable = true; menuItem.checked = Qt.binding(function() { - return menu.visualParent && menu.visualParent.m.Activities.length === 0; + return menu.visualParent && menu.get(atm.Activities).length === 0; }); menuItem.clicked.connect(function() { var checked = menuItem.checked; var newActivities = undefined; // will cast to an empty QStringList i.e all activities if (!checked) { newActivities = new Array(activityInfo.currentActivity); } - tasksModel.requestActivities(menu.visualParent.modelIndex(), newActivities); + tasksModel.requestActivities(menu.modelIndex, newActivities); }); menu.newSeparator(activitiesDesktopsMenu); var runningActivities = activityInfo.runningActivities(); for (var i = 0; i < runningActivities.length; ++i) { var activityId = runningActivities[i]; menuItem = menu.newMenuItem(activitiesDesktopsMenu); menuItem.text = activityInfo.activityName(runningActivities[i]); menuItem.checkable = true; menuItem.checked = Qt.binding( (function(activityId) { return function() { - return menu.visualParent && menu.visualParent.m.Activities.indexOf(activityId) >= 0; + return menu.visualParent && menu.get(atm.Activities).indexOf(activityId) >= 0; }; })(activityId)); menuItem.clicked.connect((function(activityId) { return function () { var checked = menuItem.checked; - var newActivities = menu.visualParent.m.Activities; + var newActivities = menu.get(atm.Activities); if (checked) { newActivities = newActivities.concat(activityId); } else { var index = newActivities.indexOf(activityId) if (index < 0) { return; } newActivities = newActivities.splice(index, 1); } - return tasksModel.requestActivities(menu.visualParent.modelIndex(), newActivities); + return tasksModel.requestActivities(menu.modelIndex, newActivities); }; })(activityId)); } menu.newSeparator(activitiesDesktopsMenu); } Component.onCompleted: refresh() } } PlasmaComponents.MenuItem { - visible: (visualParent && visualParent.m.IsLauncher !== true && visualParent.m.IsStartup !== true) + visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true) - enabled: visualParent && visualParent.m.IsMinimizable === true + enabled: visualParent && get(atm.IsMinimizable) === true checkable: true - checked: visualParent && visualParent.m.IsMinimized === true + checked: visualParent && get(atm.IsMinimized) === true text: i18n("Mi&nimize") - onClicked: tasksModel.requestToggleMinimized(visualParent.modelIndex()) + onClicked: tasksModel.requestToggleMinimized(modelIndex) } PlasmaComponents.MenuItem { - visible: (visualParent && visualParent.m.IsLauncher !== true && visualParent.m.IsStartup !== true) + visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true) - enabled: visualParent && visualParent.m.IsMaximizable === true + enabled: visualParent && get(atm.IsMaximizable) === true checkable: true - checked: visualParent && visualParent.m.IsMaximized === true + checked: visualParent && get(atm.IsMaximized) === true text: i18n("Ma&ximize") - onClicked: tasksModel.requestToggleMaximized(visualParent.modelIndex()) + onClicked: tasksModel.requestToggleMaximized(modelIndex) } PlasmaComponents.MenuItem { id: startNewInstanceItem - visible: (visualParent && visualParent.m.IsLauncher !== true && visualParent.m.IsStartup !== true) + visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true) - enabled: visualParent && visualParent.m.LauncherUrlWithoutIcon != null + enabled: visualParent && get(atm.LauncherUrlWithoutIcon) != null text: i18n("Start New Instance") icon: "system-run" - onClicked: tasksModel.requestNewInstance(visualParent.modelIndex()) + onClicked: tasksModel.requestNewInstance(modelIndex) } PlasmaComponents.MenuItem { id: launcherToggleAction visible: visualParent - && visualParent.m.IsLauncher !== true - && visualParent.m.IsStartup !== true + && get(atm.IsLauncher) !== true + && get(atm.IsStartup) !== true && plasmoid.immutability !== PlasmaCore.Types.SystemImmutable && (activityInfo.numberOfRunningActivities < 2) - enabled: visualParent && visualParent.m.LauncherUrlWithoutIcon != "" + enabled: visualParent && get(atm.LauncherUrlWithoutIcon) != "" checkable: true text: i18nc("Toggle action for showing a launcher button while the application is not running", "&Pin") onClicked: { - if (tasksModel.launcherPosition(visualParent.m.LauncherUrlWithoutIcon) != -1) { - tasksModel.requestRemoveLauncher(visualParent.m.LauncherUrlWithoutIcon); + if (tasksModel.launcherPosition(get(atm.LauncherUrlWithoutIcon)) != -1) { + tasksModel.requestRemoveLauncher(get(atm.LauncherUrlWithoutIcon)); } else { - tasksModel.requestAddLauncher(visualParent.m.LauncherUrl); + tasksModel.requestAddLauncher(get(atm.LauncherUrl)); } } } PlasmaComponents.MenuItem { id: showLauncherInActivitiesItem text: i18n("&Pin") visible: visualParent - && visualParent.m.IsLauncher !== true - && visualParent.m.IsStartup !== true + && get(atm.IsLauncher) !== true + && get(atm.IsStartup) !== true && plasmoid.immutability !== PlasmaCore.Types.SystemImmutable && (activityInfo.numberOfRunningActivities >= 2) Connections { target: activityInfo onNumberOfRunningActivitiesChanged: activitiesDesktopsMenu.refresh() } PlasmaComponents.ContextMenu { id: activitiesLaunchersMenu visualParent: showLauncherInActivitiesItem.action function refresh() { clearMenuItems(); if (menu.visualParent === null) return; var createNewItem = function(id, title, url, activities) { var result = menu.newMenuItem(activitiesLaunchersMenu); result.text = title; result.visible = true; result.checkable = true; result.checked = activities.some(function(activity) { return activity === id }); result.clicked.connect( function() { if (result.checked) { tasksModel.requestAddLauncherToActivity(url, id); } else { tasksModel.requestRemoveLauncherFromActivity(url, id); } } ); return result; } if (menu.visualParent === null) return; - var url = menu.visualParent.m.LauncherUrlWithoutIcon; + var url = menu.get(atm.LauncherUrlWithoutIcon); var activities = tasksModel.launcherActivities(url); var NULL_UUID = "00000000-0000-0000-0000-000000000000"; createNewItem(NULL_UUID, i18n("On All Activities"), url, activities); if (activityInfo.numberOfRunningActivities <= 1) { return; } createNewItem(activityInfo.currentActivity, i18n("On The Current Activity"), url, activities); menu.newSeparator(activitiesLaunchersMenu); var runningActivities = activityInfo.runningActivities(); runningActivities.forEach(function(id) { createNewItem(id, activityInfo.activityName(id), url, activities); }); } Component.onCompleted: { menu.onVisualParentChanged.connect(refresh); refresh(); } } } PlasmaComponents.MenuItem { - visible: (visualParent && visualParent.m.IsLauncher === true) && plasmoid.immutability !== PlasmaCore.Types.SystemImmutable + visible: (visualParent && get(atm.IsLauncher) === true) && plasmoid.immutability !== PlasmaCore.Types.SystemImmutable text: i18nc("Remove launcher button for application shown while it is not running", "Unpin") - onClicked: tasksModel.requestRemoveLauncher(visualParent.m.LauncherUrlWithoutIcon); + onClicked: tasksModel.requestRemoveLauncher(get(atm.LauncherUrlWithoutIcon)); } PlasmaComponents.MenuItem { id: moreActionsMenuItem - visible: (visualParent && visualParent.m.IsLauncher !== true && visualParent.m.IsStartup !== true) + visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true) enabled: visible text: i18n("More Actions") PlasmaComponents.ContextMenu { visualParent: moreActionsMenuItem.action PlasmaComponents.MenuItem { - enabled: menu.visualParent && menu.visualParent.m.IsMovable === true + enabled: menu.visualParent && menu.get(atm.IsMovable) === true text: i18n("&Move") icon: "transform-move" - onClicked: tasksModel.requestMove(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestMove(menu.modelIndex) } PlasmaComponents.MenuItem { - enabled: menu.visualParent && menu.visualParent.m.IsResizable === true + enabled: menu.visualParent && menu.get(atm.IsResizable) === true text: i18n("Re&size") - onClicked: tasksModel.requestResize(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestResize(menu.modelIndex) } PlasmaComponents.MenuItem { checkable: true - checked: menu.visualParent && menu.visualParent.m.IsKeepAbove === true + checked: menu.visualParent && menu.get(atm.IsKeepAbove) === true text: i18n("Keep &Above Others") icon: "go-up" - onClicked: tasksModel.requestToggleKeepAbove(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestToggleKeepAbove(menu.modelIndex) } PlasmaComponents.MenuItem { checkable: true - checked: menu.visualParent && menu.visualParent.m.IsKeepBelow === true + checked: menu.visualParent && menu.get(atm.IsKeepBelow) === true text: i18n("Keep &Below Others") icon: "go-down" - onClicked: tasksModel.requestToggleKeepBelow(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestToggleKeepBelow(menu.modelIndex) } PlasmaComponents.MenuItem { - enabled: menu.visualParent && menu.visualParent.m.IsFullScreenable === true + enabled: menu.visualParent && menu.get(atm.IsFullScreenable) === true checkable: true - checked: menu.visualParent && menu.visualParent.m.IsFullScreen === true + checked: menu.visualParent && menu.get(atm.IsFullScreen) === true text: i18n("&Fullscreen") icon: "view-fullscreen" - onClicked: tasksModel.requestToggleFullScreen(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestToggleFullScreen(menu.modelIndex) } PlasmaComponents.MenuItem { - enabled: menu.visualParent && menu.visualParent.m.IsShadeable === true + enabled: menu.visualParent && menu.get(atm.IsShadeable) === true checkable: true - checked: menu.visualParent && menu.visualParent.m.IsShaded === true + checked: menu.visualParent && menu.get(atm.IsShaded) === true text: i18n("&Shade") - onClicked: tasksModel.requestToggleShaded(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestToggleShaded(menu.modelIndex) } PlasmaComponents.MenuItem { separator: true } PlasmaComponents.MenuItem { - visible: (plasmoid.configuration.groupingStrategy != 0) && menu.visualParent.m.IsWindow === true + visible: (plasmoid.configuration.groupingStrategy != 0) && menu.get(atm.IsWindow) === true checkable: true - checked: menu.visualParent && menu.visualParent.m.IsGroupable === true + checked: menu.visualParent && menu.get(atm.IsGroupable) === true text: i18n("Allow this program to be grouped") - onClicked: tasksModel.requestToggleGrouping(menu.visualParent.modelIndex()) + onClicked: tasksModel.requestToggleGrouping(menu.modelIndex) } } } PlasmaComponents.MenuItem { property QtObject configureAction: null enabled: configureAction && configureAction.enabled text: configureAction ? configureAction.text : "" icon: configureAction ? configureAction.icon : "" onClicked: configureAction.trigger() Component.onCompleted: configureAction = plasmoid.action("configure") } PlasmaComponents.MenuItem { separator: true } PlasmaComponents.MenuItem { id: closeWindowItem - visible: (visualParent && visualParent.m.IsLauncher !== true && visualParent.m.IsStartup !== true) + visible: (visualParent && get(atm.IsLauncher) !== true && get(atm.IsStartup) !== true) - enabled: visualParent && visualParent.m.IsClosable === true + enabled: visualParent && get(atm.IsClosable) === true text: i18n("&Close") icon: "window-close" - onClicked: tasksModel.requestClose(visualParent.modelIndex()) + onClicked: tasksModel.requestClose(modelIndex) } } diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml index 23fd246b0..03fd3e011 100644 --- a/applets/taskmanager/package/contents/ui/Task.qml +++ b/applets/taskmanager/package/contents/ui/Task.qml @@ -1,480 +1,462 @@ /*************************************************************************** * Copyright (C) 2012-2013 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.draganddrop 2.0 import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet import "../code/layout.js" as LayoutManager import "../code/tools.js" as TaskTools MouseArea { id: task width: groupDialog.mainItem.width height: Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.medium) + LayoutManager.verticalMargins() visible: false LayoutMirroring.enabled: (Qt.application.layoutDirection == Qt.RightToLeft) LayoutMirroring.childrenInherit: (Qt.application.layoutDirection == Qt.RightToLeft) readonly property var m: model property int itemIndex: index property bool inPopup: false property bool isWindow: model.IsWindow === true property int childCount: model.ChildCount != undefined ? model.ChildCount : 0 property int previousChildCount: 0 property alias textWidth: label.implicitWidth property bool pressed: false property int pressX: -1 property int pressY: -1 property QtObject contextMenu: null property int wheelDelta: 0 readonly property bool smartLauncherEnabled: plasmoid.configuration.smartLaunchersEnabled && !inPopup && model.IsStartup !== true property QtObject smartLauncherItem: null readonly property bool highlighted: (inPopup && activeFocus) || (!inPopup && containsMouse) + function hideToolTipTemporarily() { + toolTipArea.hideToolTip(); + } + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MidButton onIsWindowChanged: { if (isWindow) { taskInitComponent.createObject(task); } } onChildCountChanged: { if (childCount > previousChildCount) { tasksModel.requestPublishDelegateGeometry(modelIndex(), backend.globalRect(task), task); } previousChildCount = childCount; } onItemIndexChanged: { + hideToolTipTemporarily(); + if (!inPopup && !tasks.vertical && (LayoutManager.calculateStripes() > 1 || !plasmoid.configuration.separateLaunchers)) { tasks.requestLayout(); } } onContainsMouseChanged: { if (containsMouse) { if (inPopup) { - forceActiveFocus() + forceActiveFocus(); } } else { pressed = false; } if (model.IsWindow === true) { tasks.windowsHovered(model.LegacyWinIdList, containsMouse); } + } onPressed: { if (mouse.button == Qt.LeftButton || mouse.button == Qt.MidButton) { pressed = true; pressX = mouse.x; pressY = mouse.y; } else if (mouse.button == Qt.RightButton) { - if (plasmoid.configuration.showToolTips) { - toolTip.hideToolTip(); - } - - tasks.createContextMenu(task).show(); + tasks.createContextMenu(task, modelIndex()).show(); } } onReleased: { if (pressed) { if (mouse.button == Qt.MidButton) { if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.NewInstance) { tasksModel.requestNewInstance(modelIndex()); } else if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.Close) { tasksModel.requestClose(modelIndex()); } else if (plasmoid.configuration.middleClickAction == TaskManagerApplet.Backend.ToggleMinimized) { tasksModel.requestToggleMinimized(modelIndex()); } } else if (mouse.button == Qt.LeftButton) { TaskTools.activateTask(modelIndex(), model, mouse.modifiers, task); if (plasmoid.configuration.showToolTips) { - toolTip.hideToolTip(); + hideToolTipTemporarily(); } } } pressed = false; pressX = -1; pressY = -1; } onPositionChanged: { // mouse.button is always 0 here, hence checking with mouse.buttons if (pressX != -1 && mouse.buttons == Qt.LeftButton && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) { tasks.dragSource = task; dragHelper.startDrag(task, model.MimeType, model.MimeData, model.LauncherUrlWithoutIcon, model.decoration); pressX = -1; pressY = -1; return; } } onWheel: { if (plasmoid.configuration.wheelEnabled) { wheelDelta = TaskTools.wheelActivateNextPrevTask(task, wheelDelta, wheel.angleDelta.y); } else { wheel.accepted = false; } } onSmartLauncherEnabledChanged: { if (smartLauncherEnabled && !smartLauncherItem) { var smartLauncher = Qt.createQmlObject(" import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet; TaskManagerApplet.SmartLauncherItem { }", task); smartLauncher.launcherUrl = Qt.binding(function() { return model.LauncherUrlWithoutIcon; }); smartLauncherItem = smartLauncher; } } Keys.onReturnPressed: TaskTools.activateTask(modelIndex(), model, event.modifiers, task) Keys.onEnterPressed: Keys.onReturnPressed(event); function modelIndex() { return (inPopup ? tasksModel.makeModelIndex(groupDialog.visualParent.itemIndex, index) : tasksModel.makeModelIndex(index)); } Component { id: taskInitComponent Timer { id: timer interval: units.longDuration * 2 repeat: false onTriggered: { parent.hoverEnabled = true; if (parent.isWindow) { tasksModel.requestPublishDelegateGeometry(parent.modelIndex(), backend.globalRect(parent), parent); } timer.destroy(); } Component.onCompleted: timer.start() } } PlasmaCore.FrameSvgItem { id: frame anchors { fill: parent topMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0 bottomMargin: (!tasks.vertical && taskList.rows > 1) ? units.smallSpacing / 4 : 0 leftMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0 rightMargin: ((inPopup || tasks.vertical) && taskList.columns > 1) ? units.smallSpacing / 4 : 0 } imagePath: "widgets/tasks" property string basePrefix: "normal" prefix: TaskTools.taskPrefix(basePrefix) onRepaintNeeded: updatePrefix() function updatePrefix() { prefix = Qt.binding(function() { return TaskTools.taskPrefix(basePrefix); }); } + PlasmaCore.ToolTipArea { - id: toolTip + id: toolTipArea anchors.fill: parent + location: plasmoid.location active: !inPopup && !groupDialog.visible && plasmoid.configuration.showToolTips interactive: true - location: plasmoid.location mainItem: toolTipDelegate onContainsMouseChanged: { if (containsMouse) { + toolTipDelegate.parentTask = task; toolTipDelegate.parentIndex = itemIndex; + toolTipDelegate.appName = Qt.binding(function() { + return model.AppName; + }); + toolTipDelegate.pidParent = Qt.binding(function() { + return model.AppPid; + }); toolTipDelegate.windows = Qt.binding(function() { return model.LegacyWinIdList; }); - toolTipDelegate.mainText = Qt.binding(function() { - return model.display; + toolTipDelegate.isGroup = Qt.binding(function() { + return model.IsGroupParent == true; }); toolTipDelegate.icon = Qt.binding(function() { return model.decoration; }); - toolTipDelegate.subText = Qt.binding(function() { - return model.IsLauncher === true ? model.GenericName : toolTip.generateSubText(model); - }); toolTipDelegate.launcherUrl = Qt.binding(function() { return model.LauncherUrlWithoutIcon; }); + toolTipDelegate.isLauncher = Qt.binding(function() { + return model.IsLauncher == true; + }); + toolTipDelegate.isMinimizedParent = Qt.binding(function() { + return model.IsMinimized == true; + }); + toolTipDelegate.displayParent = Qt.binding(function() { + return model.display; + }); + toolTipDelegate.genericName = Qt.binding(function() { + return model.GenericName; + }); + toolTipDelegate.virtualDesktopParent = Qt.binding(function() { + return model.VirtualDesktop != undefined ? model.VirtualDesktop : 0; + }); + toolTipDelegate.isOnAllVirtualDesktopsParent = Qt.binding(function() { + return model.IsOnAllVirtualDesktops == true; + }); + toolTipDelegate.activitiesParent = Qt.binding(function() { + return model.Activities; + }); } } - - function generateSubText(task) { - var subTextEntries = new Array(); - - if (!plasmoid.configuration.showOnlyCurrentDesktop - && virtualDesktopInfo.numberOfDesktops > 1 - && model.IsOnAllVirtualDesktops !== true - && model.VirtualDesktop != -1 - && model.VirtualDesktop != undefined) { - subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[model.VirtualDesktop - 1])); - } - - if (model.Activities == undefined) { - return subTextEntries.join("\n"); - } - - if (model.Activities.length == 0 && activityInfo.numberOfRunningActivities > 1) { - subTextEntries.push(i18nc("Which virtual desktop a window is currently on", - "Available on all activities")); - } else if (model.Activities.length > 0) { - var activityNames = new Array(); - - for (var i = 0; i < model.Activities.length; i++) { - var activity = model.Activities[i]; - - if (plasmoid.configuration.showOnlyCurrentActivity) { - if (activity != activityInfo.currentActivity) { - activityNames.push(activityInfo.activityName(model.Activities[i])); - } - } else if (activity != activityInfo.currentActivity) { - activityNames.push(activityInfo.activityName(model.Activities[i])); - } - } - - if (plasmoid.configuration.showOnlyCurrentActivity) { - if (activityNames.length > 0) { - subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", - "Also available on %1", activityNames.join(", "))); - } - } else if (activityNames.length > 0) { - subTextEntries.push(i18nc("Which activities a window is currently on", - "Available on %1", activityNames.join(", "))); - } - } - - return subTextEntries.join("\n"); - } } } Loader { anchors.fill: frame asynchronous: true source: "TaskProgressOverlay.qml" active: plasmoid.configuration.smartLaunchersEnabled && task.smartLauncherItem && task.smartLauncherItem.progressVisible } Item { id: iconBox anchors { left: parent.left leftMargin: adjustMargin(true, parent.width, taskFrame.margins.left) top: parent.top topMargin: adjustMargin(false, parent.height, taskFrame.margins.top) } width: (label.visible ? height : parent.width - adjustMargin(true, parent.width, taskFrame.margins.left) - adjustMargin(true, parent.width, taskFrame.margins.right)) height: (parent.height - adjustMargin(false, parent.height, taskFrame.margins.top) - adjustMargin(false, parent.height, taskFrame.margins.bottom)) function adjustMargin(vert, size, margin) { if (!size) { return margin; } var margins = vert ? LayoutManager.horizontalMargins() : LayoutManager.verticalMargins(); if ((size - margins) < units.iconSizes.small) { return Math.ceil((margin * (units.iconSizes.small / size)) / 2); } return margin; } //width: inPopup ? units.iconSizes.small : Math.min(height, parent.width - LayoutManager.horizontalMargins()) PlasmaCore.IconItem { id: icon anchors.fill: parent active: task.highlighted || (task.contextMenu && task.contextMenu.status == PlasmaComponents.DialogStatus.Open) enabled: true usesPlasmaTheme: false source: model.decoration } Loader { // QTBUG anchors.fill in conjunction with the Loader doesn't reliably work on creation: // have a window with a badge, move it from one screen to another, the new task item on the // other screen will now have a glitched out badge mask. width: parent.width height: parent.height asynchronous: true source: "TaskBadgeOverlay.qml" active: plasmoid.configuration.smartLaunchersEnabled && height >= units.iconSizes.small && task.smartLauncherItem && task.smartLauncherItem.countVisible } states: [ // Using a state transition avoids a binding loop between label.visible and // the text label margin, which derives from the icon width. State { name: "standalone" when: !label.visible AnchorChanges { target: iconBox anchors.left: undefined anchors.horizontalCenter: parent.horizontalCenter } PropertyChanges { target: iconBox anchors.leftMargin: 0 } } ] Loader { anchors.fill: parent active: model.IsStartup === true sourceComponent: busyIndicator } Component { id: busyIndicator PlasmaComponents.BusyIndicator { anchors.fill: parent } } } PlasmaComponents.Label { id: label visible: (inPopup || !iconsOnly && model.IsLauncher !== true && (parent.width - iconBox.height - units.smallSpacing) >= (theme.mSize(theme.defaultFont).width * 7)) anchors { fill: parent leftMargin: taskFrame.margins.left + iconBox.width + units.smallSpacing topMargin: taskFrame.margins.top rightMargin: taskFrame.margins.right bottomMargin: taskFrame.margins.bottom } text: model.display wrapMode: Text.Wrap elide: Text.ElideRight textFormat: Text.PlainText verticalAlignment: Text.AlignVCenter } states: [ State { name: "launcher" when: model.IsLauncher === true PropertyChanges { target: frame basePrefix: "" } }, State { name: "hovered" when: task.highlighted || (contextMenu.status == PlasmaComponents.DialogStatus.Open && contextMenu.visualParent == task) PropertyChanges { target: frame basePrefix: "hover" } }, State { name: "attention" when: model.IsDemandingAttention === true || (task.smartLauncherItem && task.smartLauncherItem.urgent) PropertyChanges { target: frame basePrefix: "attention" } }, State { name: "minimized" when: model.IsMinimized === true && !(groupDialog.visible && groupDialog.visualParent == task) PropertyChanges { target: frame basePrefix: "minimized" } }, State { name: "active" when: model.IsActive === true || groupDialog.visible && groupDialog.visualParent == task PropertyChanges { target: frame basePrefix: "focus" } } ] Component.onCompleted: { if (!inPopup && model.IsWindow === true) { var component = Qt.createComponent("GroupExpanderOverlay.qml"); component.createObject(task); } if (!inPopup && model.IsWindow !== true) { taskInitComponent.createObject(task); } } } diff --git a/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml b/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml index 2ee49948b..c4ad679cf 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipDelegate.qml @@ -1,354 +1,107 @@ /* * Copyright 2013 by Sebastian Kügler * Copyright 2014 by Martin Gräßlin * Copyright 2016 by Kai Uwe Broulik +* Copyright 2017 by Roman Gilg * * 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 2.010-1301, USA. */ -import QtQuick 2.0 +import QtQuick 2.7 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 +import QtQml.Models 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons -Column { - id: tooltipContentItem +import org.kde.taskmanager 0.1 as TaskManager - property Item toolTip - property var parentIndex - property var windows - property string mainText - property string subText - property variant icon - property url launcherUrl - property bool group: (windows !== undefined && windows.length > 1) - - readonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(launcherUrl) - readonly property bool hasPlayer: !!mprisSourceName && !!playerData - - readonly property var playerData: mpris2Source.data[mprisSourceName] - readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" - readonly property bool canControl: hasPlayer && playerData.CanControl - readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious - readonly property bool canGoNext: hasPlayer && playerData.CanGoNext - readonly property bool canRaise: hasPlayer && playerData.CanRaise - readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) - - readonly property string track: { - var xesamTitle = currentMetadata["xesam:title"] - if (xesamTitle) { - return xesamTitle - } - // if no track title is given, print out the file name - var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" - if (!xesamUrl) { - return "" - } - var lastSlashPos = xesamUrl.lastIndexOf('/') - if (lastSlashPos < 0) { - return "" - } - var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) - return decodeURIComponent(lastUrlPart) - } - readonly property string artist: currentMetadata["xesam:artist"] || "" - readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" +PlasmaExtras.ScrollArea { + property Item parentTask + property int parentIndex - readonly property int thumbnailWidth: units.gridUnit * 15 - readonly property int thumbnailHeight: units.gridUnit * 10 + property string appName + property int pidParent + property bool isGroup - property int preferredTextWidth: theme.mSize(theme.defaultFont).width * 30 + property var windows + readonly property bool isWin: windows !== undefined - Layout.minimumWidth: Math.max(thumbnailWidth, windowRow.width, appLabelRow.width) + units.largeSpacing / 2 - Layout.minimumHeight: childrenRect.height + units.largeSpacing + property variant icon + property url launcherUrl + property bool isLauncher + property bool isMinimizedParent + + // Needed for generateSubtext() + property string displayParent + property string genericName + property int virtualDesktopParent + property bool isOnAllVirtualDesktopsParent + property var activitiesParent + // + readonly property bool isVerticalPanel: plasmoid.formFactor == PlasmaCore.Types.Vertical + + Layout.minimumWidth: contentItem.width Layout.maximumWidth: Layout.minimumWidth + + Layout.minimumHeight: contentItem.height Layout.maximumHeight: Layout.minimumHeight LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true - spacing: units.largeSpacing / 2 + property int textWidth: theme.mSize(theme.defaultFont).width * 20 - states: State { - when: tooltipContentItem.hasPlayer + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - PropertyChanges { - target: thumbnailSourceItem - opacity: 0 // cannot set visible to false or else WindowThumbnail won't provide thumbnail - } - PropertyChanges { - target: playerControlsOpacityMask - visible: true - source: thumbnailSourceItem - maskSource: playerControlsShadowMask - } - PropertyChanges { - target: playerControlsRow - visible: true - } + Component.onCompleted: { + flickableItem.interactive = Qt.binding(function() { + return isVerticalPanel ? contentItem.height > viewport.height : contentItem.width > viewport.width + }); } Item { - id: thumbnailContainer - width: Math.max(parent.width, windowRow.width) - height: albumArtImage.available ? albumArtImage.height : - raisePlayerArea.visible ? raisePlayerArea.height : - windowRow.height - - Item { - id: thumbnailSourceItem - anchors.fill: parent - - PlasmaExtras.ScrollArea { - id: scrollArea - anchors.horizontalCenter: parent.horizontalCenter - width: Math.max(windowRow.width, thumbnailWidth) - height: parent.height - - visible: !albumArtImage.available - - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Component.onCompleted: { - flickableItem.interactive = Qt.binding(function() { - return contentItem.width > viewport.width; - }); - } - - Row { - id: windowRow - spacing: units.largeSpacing - - Repeater { - model: plasmoid.configuration.showToolTips && !albumArtImage.available ? windows : null - - PlasmaCore.WindowThumbnail { - id: windowThumbnail - - width: thumbnailWidth - height: thumbnailHeight - - winId: modelData - - ToolTipWindowMouseArea { - anchors.fill: parent - modelIndex: tasksModel.makeModelIndex(parentIndex, group ? index : -1) - winId: modelData - thumbnailItem: parent - } - } - } - } - } - - Image { - id: albumArtImage - // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load - readonly property bool available: status === Image.Ready || status === Image.Loading - - anchors.centerIn: parent - width: parent.width - height: thumbnailHeight - sourceSize: Qt.size(thumbnailWidth, thumbnailHeight) - asynchronous: true - source: albumArt - fillMode: Image.PreserveAspectCrop - visible: available - - ToolTipWindowMouseArea { - anchors.fill: parent - modelIndex: tasksModel.makeModelIndex(parentIndex, group ? index : -1) - winId: windows != undefined ? (windows[0] || 0) : 0 - } - } + id: contentItem + width: childrenRect.width + height: childrenRect.height - MouseArea { - id: raisePlayerArea - anchors.centerIn: parent - width: thumbnailWidth - height: thumbnailHeight - - // if there's no window associated with this task, we might still be able to raise the player - visible: windows == undefined || !windows[0] && canRaise - onClicked: mpris2Source.raise(mprisSourceName) - - PlasmaCore.IconItem { - anchors.fill: parent - source: icon - animated: false - usesPlasmaTheme: false - visible: !albumArtImage.available - } - } - } - - Item { - id: playerControlsShadowMask - anchors.fill: thumbnailSourceItem - visible: false // OpacityMask would render it - - Rectangle { - width: parent.width - height: parent.height - playerControlsRow.height - } - - Rectangle { - anchors.bottom: parent.bottom - width: parent.width - height: playerControlsRow.height - opacity: 0.2 - } - } - - OpacityMask { - id: playerControlsOpacityMask - anchors.fill: thumbnailSourceItem - visible: false - } - - // prevent accidental click-through when a control is disabled - MouseArea { - anchors.fill: playerControlsRow - enabled: playerControlsRow.visible - } - - RowLayout { - id: playerControlsRow - anchors { - horizontalCenter: parent.horizontalCenter - bottom: thumbnailSourceItem.bottom - } - width: thumbnailWidth - spacing: 0 - enabled: canControl - visible: false - - ColumnLayout { - Layout.fillWidth: true - spacing: 0 - - PlasmaExtras.Heading { - Layout.fillWidth: true - level: 4 - wrapMode: Text.NoWrap - elide: Text.ElideRight - text: track || "" - } - - PlasmaExtras.Heading { - Layout.fillWidth: true - level: 5 - wrapMode: Text.NoWrap - elide: Text.ElideRight - text: artist || "" - } - } - - PlasmaComponents.ToolButton { - enabled: canGoBack - iconName: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" - tooltip: i18nc("Go to previous song", "Previous") - Accessible.name: tooltip - onClicked: mpris2Source.goPrevious(mprisSourceName) - } - - PlasmaComponents.ToolButton { - Layout.fillHeight: true - Layout.preferredWidth: height // make this button bigger - iconName: playing ? "media-playback-pause" : "media-playback-start" - tooltip: playing ? i18nc("Pause player", "Pause") : i18nc("Start player", "Play") - Accessible.name: tooltip - onClicked: mpris2Source.playPause(mprisSourceName) - } - - PlasmaComponents.ToolButton { - enabled: canGoNext - iconName: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" - tooltip: i18nc("Go to next song", "Next") - Accessible.name: tooltip - onClicked: mpris2Source.goNext(mprisSourceName) - } + ToolTipInstance { + visible: !isGroup } - } - Row { - id: appLabelRow - anchors.left: parent.left - spacing: units.largeSpacing + Grid { + rows: !isVerticalPanel + columns: isVerticalPanel + flow: isVerticalPanel ? Grid.TopToBottom : Grid.LeftToRight + spacing: units.largeSpacing - Item { - id: imageContainer - width: tooltipIcon.width - height: tooltipIcon.height + visible: isGroup - PlasmaCore.IconItem { - id: tooltipIcon - anchors { - left: parent.left - leftMargin: units.largeSpacing / 2 + Repeater { + id: groupRepeater + model: DelegateModel { + model: tasksModel + rootIndex: tasksModel.makeModelIndex(parentIndex, -1) + delegate: ToolTipInstance {} } - width: units.iconSizes.desktop - height: width - animated: false - usesPlasmaTheme: false - source: icon - } - } - - Column { - id: mainColumn - spacing: units.smallSpacing - - //This instance is purely for metrics - PlasmaExtras.Heading { - id: tooltipMaintextPlaceholder - visible: false - level: 3 - text: mainText - textFormat: Text.PlainText - } - PlasmaExtras.Heading { - id: tooltipMaintext - level: 3 - width: Math.min(tooltipMaintextPlaceholder.width, preferredTextWidth) - height: undefined // unset stupid PlasmaComponents.Label default height - //width: 400 - elide: Text.ElideRight - wrapMode: Text.WordWrap - // if there's no subtext allow two lines of window title - maximumLineCount: tooltipSubtext.visible ? 1 : 2 - lineHeight: 0.95 - text: mainText - textFormat: Text.PlainText - } - PlasmaComponents.Label { - id: tooltipSubtext - width: tooltipContentItem.preferredTextWidth - height: Math.min(theme.mSize(theme.defaultFont), contentHeight) - wrapMode: Text.WordWrap - text: subText - textFormat: Text.PlainText - opacity: 0.5 - visible: text !== "" } } } } diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml new file mode 100644 index 000000000..0496e0f21 --- /dev/null +++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml @@ -0,0 +1,465 @@ +/* +* Copyright 2013 by Sebastian Kügler +* Copyright 2014 by Martin Gräßlin +* Copyright 2016 by Kai Uwe Broulik +* Copyright 2017 by Roman Gilg +* +* 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 2.010-1301, USA. +*/ + +import QtQuick 2.7 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 +import QtQml.Models 2.2 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddons + +import org.kde.taskmanager 0.1 as TaskManager + +Column { + property var submodelIndex: tasksModel.makeModelIndex(parentIndex, isGroup ? index : -1) + property int flatIndex: isGroup && index != undefined ? index : 0 + + spacing: units.smallSpacing + + property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent) + property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0 + property bool hasPlayer: !!mprisSourceName && !!playerData + property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" + property bool canControl: hasPlayer && playerData.CanControl + property bool canGoBack: hasPlayer && playerData.CanGoPrevious + property bool canGoNext: hasPlayer && playerData.CanGoNext + property bool canRaise: hasPlayer && playerData.CanRaise + property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) + + readonly property string track: { + var xesamTitle = currentMetadata["xesam:title"] + if (xesamTitle) { + return xesamTitle; + } + // if no track title is given, print out the file name + var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" + if (!xesamUrl) { + return ""; + } + var lastSlashPos = xesamUrl.lastIndexOf('/') + if (lastSlashPos < 0) { + return ""; + } + var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) + return decodeURIComponent(lastUrlPart); + } + readonly property string artist: currentMetadata["xesam:artist"] || "" + readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" + + // + // launcher icon + text labels + close button + RowLayout { + id: header + Layout.minimumWidth: childrenRect.width + Layout.maximumWidth: Layout.minimumWidth + + Layout.minimumHeight: childrenRect.height + Layout.maximumHeight: Layout.minimumHeight + + anchors.horizontalCenter: parent.horizontalCenter + + // launcher icon + PlasmaCore.IconItem { + source: icon + animated: false + usesPlasmaTheme: false + visible: !isWin + } + // all textlabels + Column { + spacing: 0.75 * units.smallSpacing + PlasmaComponents.Label { + width: isWin ? textWidth : undefined + height: theme.mSize(theme.defaultFont).height + font.pointSize: -1 + font.pixelSize: height + elide: Text.ElideRight + text: appName + opacity: flatIndex == 0 + textFormat: Text.PlainText + } + // window title + PlasmaComponents.Label { + width: isWin ? textWidth : undefined + height: 0.75 * theme.mSize(theme.defaultFont).height + font.pointSize: -1 + font.pixelSize: height + elide: Text.ElideRight + text: generateTitle() + textFormat: Text.PlainText + opacity: 0.75 + } + // subtext + PlasmaComponents.Label { + width: isWin ? textWidth : undefined + height: 0.6 * theme.mSize(theme.defaultFont).height + font.pointSize: -1 + font.pixelSize: height + elide: Text.ElideRight + text: isWin ? generateSubText() : "" + textFormat: Text.PlainText + opacity: 0.6 + visible: text !== "" + } + } + // close button + MouseArea { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + + height: units.iconSizes.smallMedium + width: height + + visible: isWin + + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: { + backend.cancelHighlightWindows(); + tasksModel.requestClose(submodelIndex); + } + + PlasmaCore.IconItem { + anchors.fill: parent + active: parent.containsMouse + + source: "window-close" + animated: false + } + } + } + + // thumbnail container + Item { + id: thumbnail + width: header.width + // similar to 0.5625 = 1 / (16:9) as most screens are + // round necessary, otherwise shadow mask for players has gap! + height: Math.round(0.5 * width) + anchors.horizontalCenter: parent.horizontalCenter + + visible: isWin + + Item { + id: thumbnailSourceItem + anchors.fill: parent + + readonly property bool isMinimized: isGroup ? IsMinimized == true : isMinimizedParent + // TODO: this causes XCB error message when being visible the first time + property int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0 + + PlasmaCore.WindowThumbnail { + anchors.fill: parent + + visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized + winId: thumbnailSourceItem.winId + + ToolTipWindowMouseArea { + anchors.fill: parent + rootTask: parentTask + modelIndex: submodelIndex + winId: thumbnailSourceItem.winId + } + } + + Image { + id: albumArtImage + // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load + readonly property bool available: status === Image.Ready || status === Image.Loading + + anchors.fill: parent + sourceSize: Qt.size(parent.width, parent.height) + asynchronous: true + source: albumArt + fillMode: Image.PreserveAspectCrop + visible: available + + ToolTipWindowMouseArea { + anchors.fill: parent + rootTask: parentTask + modelIndex: submodelIndex + winId: thumbnailSourceItem.winId + } + } + + // when minimized, we don't have a preview, so show the icon + PlasmaCore.IconItem { + anchors.fill: parent + source: icon + animated: false + usesPlasmaTheme: false + visible: thumbnailSourceItem.isMinimized && !albumArtImage.visible + + ToolTipWindowMouseArea { + anchors.fill: parent + rootTask: parentTask + modelIndex: submodelIndex + winId: thumbnailSourceItem.winId + } + } + } + + + Loader { + anchors.fill: thumbnail + sourceComponent: hasPlayer ? playerControlsComp : undefined + } + + Component { + id: playerControlsComp + + Item { + anchors.fill: parent + + // TODO: When could this really be the case? A not-launcher-task always has a window!? + // if there's no window associated with this task, we might still be able to raise the player +// MouseArea { +// id: raisePlayerArea +// anchors.fill: parent + +// visible: !isWin || !windows[0] && canRaise +// onClicked: mpris2Source.raise(mprisSourceName) +// } + + Item { + id: playerControlsFrostedGlass + anchors.fill: parent + visible: false // OpacityMask would render it + + Rectangle { + width: parent.width + height: parent.height - playerControlsRow.height + opacity: 0 + } + + Rectangle { + anchors.bottom: parent.bottom + width: parent.width + height: playerControlsRow.height + opacity: 0.8 + } + } + + OpacityMask { + id: playerControlsOpacityMask + anchors.fill: parent + source: playerControlsFrostedGlass + maskSource: thumbnailSourceItem + } + + // prevent accidental click-through when a control is disabled + MouseArea { + anchors.fill: playerControlsRow + } + + RowLayout { + id: playerControlsRow + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + } + width: parent.width + spacing: 0 + enabled: canControl + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 4 + wrapMode: Text.NoWrap + elide: Text.ElideRight + text: track || "" + } + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 5 + wrapMode: Text.NoWrap + elide: Text.ElideRight + text: artist || "" + } + } + + MouseArea { + height: units.iconSizes.smallMedium + width: height + enabled: canGoBack + + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: mpris2Source.goPrevious(mprisSourceName) + + PlasmaCore.IconItem { + anchors.fill: parent + enabled: canGoBack + active: parent.containsMouse + + source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" + animated: false + } + } + + MouseArea { + height: units.iconSizes.medium + width: height + + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: mpris2Source.playPause(mprisSourceName) + + PlasmaCore.IconItem { + anchors.fill: parent + active: parent.containsMouse + + source: playing ? "media-playback-pause" : "media-playback-start" + animated: false + } + } + + MouseArea { + height: units.iconSizes.smallMedium + width: height + enabled: canGoNext + + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onClicked: mpris2Source.goNext(mprisSourceName) + + PlasmaCore.IconItem { + anchors.fill: parent + enabled: canGoNext + active: parent.containsMouse + + source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" + animated: false + } + } + } + } + } + } + + function generateTitle() { + if (!isWin) { + return genericName != undefined ? genericName : ""; + } + + var text; + if (isGroup) { + if (model.display == undefined) { + return ""; + } + text = model.display.toString(); + } else { + text = displayParent; + } + + // KWin appends increasing integers in between pointy brackets to otherwise equal window titles. + // In this case save <#number> as counter and delete it at the end of text. + var counter = text.match(/<\d+>\W*$/); + text = text.replace(/\s*<\d+>\W*$/, ""); + + // Remove appName from the end of text. + var appNameRegex = new RegExp(appName + "$", "i"); + text = text.replace(appNameRegex, ""); + text = text.replace(/\s*(?:-|—)*\s*$/, ""); + + // Add counter back at the end. + if (counter != null) { + if (text == "") { + text = counter; + } else { + text = text + " " + counter; + } + } + + // In case the window title had only redundant informations (i.e. appName), text is now empty. + // Add a hyphen to indicate that and avoid empty space. + if (text == "") { + text = "—"; + } + return text.toString(); + } + + function generateSubText() { + if (activitiesParent == undefined) { + return ""; + } + + var subTextEntries = []; + + var vd = isGroup ? VirtualDesktop : virtualDesktopParent; + + if (!plasmoid.configuration.showOnlyCurrentDesktop + && virtualDesktopInfo.numberOfDesktops > 1 + && (isGroup ? IsOnAllVirtualDesktops : isOnAllVirtualDesktopsParent) !== true + && vd != -1 + && vd != undefined + && virtualDesktopInfo.desktopNames[vd - 1] != undefined) { + subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[vd - 1])); + } + + var act = isGroup ? Activities : activitiesParent; + if (act == undefined) { + return subTextEntries.join("\n"); + } + + if (act.length == 0 && activityInfo.numberOfRunningActivities > 1) { + subTextEntries.push(i18nc("Which virtual desktop a window is currently on", + "Available on all activities")); + } else if (act.length > 0) { + var activityNames = []; + + for (var i = 0; i < act.length; i++) { + var activity = act[i]; + var activityName = activityInfo.activityName(act[i]); + if (activityName == "") { + continue; + } + if (plasmoid.configuration.showOnlyCurrentActivity) { + if (activity != activityInfo.currentActivity) { + activityNames.push(activityName); + } + } else if (activity != activityInfo.currentActivity) { + activityNames.push(activityName); + } + } + + if (plasmoid.configuration.showOnlyCurrentActivity) { + if (activityNames.length > 0) { + subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", + "Also available on %1", activityNames.join(", "))); + } + } else if (activityNames.length > 0) { + subTextEntries.push(i18nc("Which activities a window is currently on", + "Available on %1", activityNames.join(", "))); + } + } + + return subTextEntries.join("\n"); + } +} diff --git a/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml index 57ca17fc3..e97e0103d 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml @@ -1,61 +1,47 @@ /* * Copyright 2013 by Sebastian Kügler * Copyright 2014 by Martin Gräßlin * Copyright 2016 by Kai Uwe Broulik * * 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 2.010-1301, USA. */ import QtQuick 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents MouseArea { property var modelIndex property int winId // FIXME Legacy - property Item thumbnailItem + property Item rootTask - acceptedButtons: Qt.LeftButton + acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true enabled: winId != 0 onClicked: { - tasksModel.requestActivate(modelIndex); - toolTip.hideToolTip(); + if (mouse.button == Qt.LeftButton) { + tasksModel.requestActivate(modelIndex); + } else { + tasks.createContextMenu(rootTask, modelIndex).show(); + } + rootTask.hideToolTipTemporarily(); } onContainsMouseChanged: { tasks.windowsHovered([winId], containsMouse); } - - PlasmaComponents.ToolButton { - anchors { - top: parent.top - topMargin: thumbnailItem ? (thumbnailItem.height - thumbnailItem.paintedHeight) / 2 : 0 - right: parent.right - rightMargin: thumbnailItem ? (thumbnailItem.width - thumbnailItem.paintedWidth) / 2 : 0 - } - - iconSource: "window-close" - visible: parent.containsMouse && winId != 0 - tooltip: i18nc("close this window", "Close") - - onClicked: { - backend.cancelHighlightWindows(); - tasksModel.requestClose(modelIndex); - } - } } diff --git a/applets/taskmanager/package/contents/ui/main.qml b/applets/taskmanager/package/contents/ui/main.qml index ed700c61d..85b3563ab 100644 --- a/applets/taskmanager/package/contents/ui/main.qml +++ b/applets/taskmanager/package/contents/ui/main.qml @@ -1,421 +1,424 @@ /*************************************************************************** * Copyright (C) 2012-2016 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 . * ***************************************************************************/ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.taskmanager 0.1 as TaskManager import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet import "../code/layout.js" as LayoutManager import "../code/tools.js" as TaskTools Item { id: tasks anchors.fill: parent property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) property bool iconsOnly: (plasmoid.pluginName == "org.kde.plasma.icontasks") property QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml"); Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Layout.fillWidth: true Layout.fillHeight:true Layout.minimumWidth: tasks.vertical ? 0 : LayoutManager.preferredMinWidth() Layout.minimumHeight: !tasks.vertical ? 0 : LayoutManager.preferredMinHeight() //BEGIN TODO: this is not precise enough: launchers are smaller than full tasks Layout.preferredWidth: tasks.vertical ? units.gridUnit * 10 : ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes()); Layout.preferredHeight: tasks.vertical ? ((LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes()) : units.gridUnit * 2; //END TODO property Item dragSource: null signal requestLayout signal windowsHovered(variant winIds, bool hovered) signal presentWindows(variant winIds) onWidthChanged: { taskList.width = LayoutManager.layoutWidth(); if (plasmoid.configuration.forceStripes) { taskList.height = LayoutManager.layoutHeight(); } } onHeightChanged: { if (plasmoid.configuration.forceStripes) { taskList.width = LayoutManager.layoutWidth(); } taskList.height = LayoutManager.layoutHeight(); } onDragSourceChanged: { if (dragSource == null) { tasksModel.syncLaunchers(); } } TaskManager.TasksModel { id: tasksModel virtualDesktop: virtualDesktopInfo.currentDesktop screenGeometry: plasmoid.screenGeometry activity: activityInfo.currentActivity filterByVirtualDesktop: plasmoid.configuration.showOnlyCurrentDesktop filterByScreen: plasmoid.configuration.showOnlyCurrentScreen filterByActivity: plasmoid.configuration.showOnlyCurrentActivity filterNotMinimized: plasmoid.configuration.showOnlyMinimized sortMode: iconsOnly ? TaskManager.TasksModel.SortManual : sortModeEnumValue(plasmoid.configuration.sortingStrategy) launchInPlace: iconsOnly separateLaunchers: { if (!iconsOnly && !plasmoid.configuration.separateLaunchers && plasmoid.configuration.sortingStrategy == 1) { return false; } return true; } groupMode: iconsOnly ? TaskManager.TasksModel.GroupApplication : sortModeEnumValue(plasmoid.configuration.groupingStrategy) groupInline: !plasmoid.configuration.groupPopups groupingWindowTasksThreshold: (plasmoid.configuration.onlyGroupWhenFull && !iconsOnly ? LayoutManager.optimumCapacity(width, height) + 1 : -1) onLauncherListChanged: { layoutTimer.restart(); plasmoid.configuration.launchers = launcherList; } onGroupingAppIdBlacklistChanged: { plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist; } onGroupingLauncherUrlBlacklistChanged: { plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist; } function sortModeEnumValue(index) { switch (index) { case 0: return TaskManager.TasksModel.SortDisabled; case 1: return TaskManager.TasksModel.SortManual; case 2: return TaskManager.TasksModel.SortAlpha; case 3: return TaskManager.TasksModel.SortVirtualDesktop; case 4: return TaskManager.TasksModel.SortActivity; default: return TaskManager.TasksModel.SortDisabled; } } function groupModeEnumValue(index) { switch (index) { case 0: return TaskManager.TasksModel.GroupDisabled; case 1: return TaskManager.TasksModel.GroupApplications; } } Component.onCompleted: { launcherList = plasmoid.configuration.launchers; groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; // Only hook up view only after the above churn is done. taskRepeater.model = tasksModel; } } TaskManager.VirtualDesktopInfo { id: virtualDesktopInfo } TaskManager.ActivityInfo { id: activityInfo } TaskManagerApplet.Backend { id: backend taskManagerItem: groupDialog.visible ? null : tasks toolTipItem: toolTipDelegate highlightWindows: plasmoid.configuration.highlightWindows onAddLauncher: { tasks.addLauncher(url); } } PlasmaCore.DataSource { id: mpris2Source engine: "mpris2" connectedSources: sources - - function sourceNameForLauncherUrl(launcherUrl) { - if (!launcherUrl) { + function sourceNameForLauncherUrl(launcherUrl, pid) { + if (!launcherUrl || launcherUrl == "") { return ""; } // MPRIS spec explicitly mentions that "DesktopEntry" is with .desktop extension trimmed // Moreover, remove URL parameters, like wmClass (part after the question mark) var desktopFileName = launcherUrl.toString().split('/').pop().split('?')[0].replace(".desktop", "") - for (var i = 0, length = sources.length; i < length; ++i) { - var source = sources[i]; + for (var i = 0, length = connectedSources.length; i < length; ++i) { + var source = connectedSources[i]; + // we intend to connect directly, otherwise the multiplexer steals the connection away + if (source === "@multiplex") { + continue; + } var sourceData = data[source]; - if (sourceData && sourceData.DesktopEntry === desktopFileName) { - return source + if (sourceData && sourceData.DesktopEntry === desktopFileName && sourceData.InstancePid === pid) { + return source; } } return "" } function startOperation(source, op) { var service = serviceForSource(source) var operation = service.operationDescription(op) return service.startOperationCall(operation) } function goPrevious(source) { startOperation(source, "Previous"); } function goNext(source) { startOperation(source, "Next"); } function playPause(source) { startOperation(source, "PlayPause"); } function stop(source) { startOperation(source, "Stop"); } function raise(source) { startOperation(source, "Raise"); } function quit(source) { startOperation(source, "Quit"); } } Timer { id: iconGeometryTimer interval: 500 repeat: false onTriggered: { TaskTools.publishIconGeometries(taskList.children); } } Binding { target: plasmoid property: "status" value: (tasksModel.anyTaskDemandsAttention ? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus) } Connections { target: plasmoid onLocationChanged: { // This is on a timer because the panel may not have // settled into position yet when the location prop- // erty updates. iconGeometryTimer.start(); } } Connections { target: plasmoid.configuration onLaunchersChanged: tasksModel.launcherList = plasmoid.configuration.launchers onGroupingAppIdBlacklistChanged: tasksModel.groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist; onGroupingLauncherUrlBlacklistChanged: tasksModel.groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist; } TaskManagerApplet.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium } PlasmaCore.FrameSvgItem { id: taskFrame visible: false; imagePath: "widgets/tasks"; prefix: "normal" } PlasmaCore.Svg { id: taskSvg imagePath: "widgets/tasks" } MouseHandler { id: mouseHandler anchors.fill: parent target: taskList onUrlsDropped: { // If all dropped URLs point to application desktop files, we'll add a launcher for each of them. var createLaunchers = urls.every(function (item) { return backend.isApplication(item) }); if (createLaunchers) { urls.forEach(function (item) { addLauncher(item); }); return; } if (!hoveredItem) { return; } // DeclarativeMimeData urls is a QJsonArray but requestOpenUrls expects a proper QList. var urlsList = backend.jsonArrayToUrlList(urls); // Otherwise we'll just start a new instance of the application with the URLs as argument, // as you probably don't expect some of your files to open in the app and others to spawn launchers. tasksModel.requestOpenUrls(hoveredItem.modelIndex(), urlsList); } } ToolTipDelegate { id: toolTipDelegate visible: false } TaskList { id: taskList anchors { left: parent.left top: parent.top } onWidthChanged: LayoutManager.layout(taskRepeater) onHeightChanged: LayoutManager.layout(taskRepeater) flow: { if (tasks.vertical) { return plasmoid.configuration.forceStripes ? Flow.LeftToRight : Flow.TopToBottom } return plasmoid.configuration.forceStripes ? Flow.TopToBottom : Flow.LeftToRight } onAnimatingChanged: { if (!animating) { TaskTools.publishIconGeometries(children); } } function layout() { taskList.width = LayoutManager.layoutWidth(); taskList.height = LayoutManager.layoutHeight(); LayoutManager.layout(taskRepeater); } Timer { id: layoutTimer interval: 0 repeat: false onTriggered: taskList.layout() } Repeater { id: taskRepeater delegate: Task {} - onItemAdded: taskList.layout() onItemRemoved: taskList.layout() } } GroupDialog { id: groupDialog } function hasLauncher(url) { return tasksModel.launcherPosition(url) != -1; } function addLauncher(url) { if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { tasksModel.requestAddLauncher(url); } } // This is called by plasmashell in response to a Meta+number shortcut. function activateTaskAtIndex(index) { if (typeof index !== "number") { return; } var task = taskRepeater.itemAt(index); if (task) { TaskTools.activateTask(task.modelIndex(), task.m, null, task); } } function resetDragSource() { dragSource = null; } - function createContextMenu(task) { - var menu = tasks.contextMenuComponent.createObject(task); - menu.visualParent = task; + function createContextMenu(rootTask, modelIndex) { + var menu = tasks.contextMenuComponent.createObject(rootTask); + menu.visualParent = rootTask; + menu.modelIndex = modelIndex; menu.mpris2Source = mpris2Source; return menu; } Component.onCompleted: { tasks.requestLayout.connect(layoutTimer.restart); tasks.requestLayout.connect(iconGeometryTimer.restart); tasks.windowsHovered.connect(backend.windowsHovered); tasks.presentWindows.connect(backend.presentWindows); dragHelper.dropped.connect(resetDragSource); } }