diff --git a/applets/taskmanager/package/contents/ui/GroupDialog.qml b/applets/taskmanager/package/contents/ui/GroupDialog.qml index 829fcf000..c87852b1e 100644 --- a/applets/taskmanager/package/contents/ui/GroupDialog.qml +++ b/applets/taskmanager/package/contents/ui/GroupDialog.qml @@ -1,157 +1,164 @@ /*************************************************************************** * 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 QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.draganddrop 2.0 import "../code/layout.js" as LayoutManager PlasmaCore.Dialog { id: groupDialog visible: false type: PlasmaCore.Dialog.PopupMenu flags: Qt.WindowStaysOnTopHint hideOnWindowDeactivate: true location: plasmoid.location + property int preferredWidth: Screen.width / (3 * Screen.devicePixelRatio) + property int preferredHeight: Screen.height / (2 * Screen.devicePixelRatio) + mainItem: Item { MouseHandler { id: mouseHandler anchors.fill: parent target: taskList } TaskList { id: taskList anchors.fill: parent Repeater { id: groupRepeater function currentIndex() { for (var i = 0; i < count; ++i) { if (itemAt(i).activeFocus) { return i; } } return -1; } onCountChanged: updateSize() } } Keys.onUpPressed: { var currentIndex = groupRepeater.currentIndex(); // In doubt focus the first item if (currentIndex === -1) { groupRepeater.itemAt(0).forceActiveFocus(); return; } var previousIndex = currentIndex - 1; if (previousIndex < 0) { previousIndex = groupRepeater.count - 1; } groupRepeater.itemAt(previousIndex).forceActiveFocus() } Keys.onDownPressed: { var currentIndex = groupRepeater.currentIndex(); // In doubt focus the first item, also wrap around. if (currentIndex === -1 || currentIndex + 1 >= groupRepeater.count) { groupRepeater.itemAt(0).forceActiveFocus(); return; } groupRepeater.itemAt(currentIndex + 1).forceActiveFocus(); } Keys.onEscapePressed: groupDialog.visible = false; } data: [ VisualDataModel { id: groupFilter delegate: Task { visible: true inPopup: true } } ] onVisualParentChanged: { if (!visualParent) { visible = false; } } onVisibleChanged: { if (visible && visualParent) { groupFilter.model = tasksModel; groupFilter.rootIndex = groupFilter.modelIndex(visualParent.itemIndex); groupRepeater.model = groupFilter; mainItem.forceActiveFocus(); } else { visualParent = null; groupRepeater.model = undefined; groupFilter.model = undefined; groupFilter.rootIndex = undefined; } } function updateSize() { if (!visible || !visualParent) { return; } if (!groupRepeater.count) { visible = false; } else { var task; var maxWidth = 0; + var maxHeight = 0; + + backend.cancelHighlightWindows(); for (var i = 0; i < taskList.children.length - 1; ++i) { task = taskList.children[i]; if (task.textWidth > maxWidth) { maxWidth = task.textWidth; } task.textWidthChanged.connect(updateSize); } - maxWidth += LayoutManager.horizontalMargins() + units.iconSizes.small + 6; + maxWidth += LayoutManager.horizontalMargins() + units.iconSizes.medium + 2 * units.smallSpacing; + maxHeight = groupRepeater.count * (LayoutManager.verticalMargins() + Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.medium)); - // TODO: Properly derive limits from work area size (screen size sans struts). - mainItem.width = Math.min(maxWidth, (tasks.vertical ? 640 - tasks.width : Math.max(tasks.width, 640)) - 20); - mainItem.height = groupRepeater.count * (LayoutManager.verticalMargins() + Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.small)); + mainItem.height = Math.min(preferredHeight, maxHeight); + mainItem.width = Math.min(preferredWidth, (tasks.vertical ? Math.max(maxWidth, tasks.width) : Math.min(maxWidth, tasks.width))); } } } diff --git a/applets/taskmanager/package/contents/ui/Task.qml b/applets/taskmanager/package/contents/ui/Task.qml index 0a59d533d..404d74254 100644 --- a/applets/taskmanager/package/contents/ui/Task.qml +++ b/applets/taskmanager/package/contents/ui/Task.qml @@ -1,477 +1,477 @@ /*************************************************************************** * 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.small) + LayoutManager.verticalMargins() + 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) 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: { if (!inPopup && !tasks.vertical && (LayoutManager.calculateStripes() > 1 || !plasmoid.configuration.separateLaunchers)) { tasks.requestLayout(); } } onContainsMouseChanged: { if (containsMouse) { if (inPopup) { 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(); } } 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); } } 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) 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 anchors.fill: parent active: !inPopup && !groupDialog.visible && plasmoid.configuration.showToolTips interactive: true location: plasmoid.location mainItem: toolTipDelegate onContainsMouseChanged: { if (containsMouse) { toolTipDelegate.parentIndex = itemIndex; toolTipDelegate.windows = Qt.binding(function() { return model.LegacyWinIdList; }); toolTipDelegate.mainText = Qt.binding(function() { return model.display; }); 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; }); } } 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/ToolTipWindowMouseArea.qml b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml index e3551df32..57ca17fc3 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipWindowMouseArea.qml @@ -1,58 +1,61 @@ /* * 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 acceptedButtons: Qt.LeftButton hoverEnabled: true enabled: winId != 0 onClicked: { tasksModel.requestActivate(modelIndex); toolTip.hideToolTip(); } 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: tasksModel.requestClose(modelIndex); + 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 e53b19458..973a1f6b5 100644 --- a/applets/taskmanager/package/contents/ui/main.qml +++ b/applets/taskmanager/package/contents/ui/main.qml @@ -1,409 +1,409 @@ /*************************************************************************** * 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: tasks + 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) { 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]; var sourceData = data[source]; if (sourceData && sourceData.DesktopEntry === desktopFileName) { 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); } } function resetDragSource() { dragSource = null; } function createContextMenu(task) { var menu = tasks.contextMenuComponent.createObject(task); menu.visualParent = task; 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); } } diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp index 4ef5b88ba..16f1f571a 100644 --- a/applets/taskmanager/plugin/backend.cpp +++ b/applets/taskmanager/plugin/backend.cpp @@ -1,425 +1,431 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; Backend::Backend(QObject* parent) : QObject(parent) , m_taskManagerItem(0) , m_toolTipItem(0) , m_panelWinId(0) , m_highlightWindows(false) , m_actionGroup(new QActionGroup(this)) { } Backend::~Backend() { } QQuickItem *Backend::taskManagerItem() const { return m_taskManagerItem; } void Backend::setTaskManagerItem(QQuickItem* item) { if (item != m_taskManagerItem) { m_taskManagerItem = item; emit taskManagerItemChanged(); } } QQuickItem *Backend::toolTipItem() const { return m_toolTipItem; } void Backend::setToolTipItem(QQuickItem *item) { if (item != m_toolTipItem) { m_toolTipItem = item; connect(item, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(toolTipWindowChanged(QQuickWindow*))); emit toolTipItemChanged(); } } bool Backend::highlightWindows() const { return m_highlightWindows; } void Backend::setHighlightWindows(bool highlight) { if (highlight != m_highlightWindows) { m_highlightWindows = highlight; updateWindowHighlight(); emit highlightWindowsChanged(); } } QVariantList Backend::jumpListActions(const QUrl &launcherUrl, QObject *parent) { QVariantList actions; if (!parent || !launcherUrl.isValid() || !launcherUrl.isLocalFile() || !KDesktopFile::isDesktopFile(launcherUrl.toLocalFile())) { return actions; } KDesktopFile desktopFile(launcherUrl.toLocalFile()); const QStringList &jumpListActions = desktopFile.readActions(); const QLatin1String kde("KDE"); foreach (const QString &actionName, jumpListActions) { const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); if (!actionGroup.isValid() || !actionGroup.exists()) { continue; } const QStringList ¬ShowIn = actionGroup.readXdgListEntry(QStringLiteral("NotShowIn")); if (notShowIn.contains(kde)) { continue; } const QStringList &onlyShowIn = actionGroup.readXdgListEntry(QStringLiteral("OnlyShowIn")); if (!onlyShowIn.isEmpty() && !onlyShowIn.contains(kde)) { continue; } const QString &name = actionGroup.readEntry(QStringLiteral("Name")); const QString &exec = actionGroup.readEntry(QStringLiteral("Exec")); if (name.isEmpty() || exec.isEmpty()) { continue; } QAction *action = new QAction(this); action->setText(name); action->setIcon(QIcon::fromTheme(actionGroup.readEntry("Icon"))); action->setProperty("exec", exec); // so we can show the proper application name and icon when it launches action->setProperty("applicationName", desktopFile.readName()); action->setProperty("applicationIcon", desktopFile.readIcon()); connect(action, &QAction::triggered, this, &Backend::handleJumpListAction); actions << QVariant::fromValue(action); } return actions; } QVariantList Backend::recentDocumentActions(const QUrl &launcherUrl, QObject *parent) { QVariantList actions; if (!parent || !launcherUrl.isValid() || !launcherUrl.isLocalFile() || !KDesktopFile::isDesktopFile(launcherUrl.toLocalFile())) { return actions; } QString desktopName = launcherUrl.fileName(); QString storageId = desktopName; if (storageId.startsWith(QLatin1String("org.kde."))) { storageId = storageId.right(storageId.length() - 8); } if (storageId.endsWith(QLatin1String(".desktop"))) { storageId = storageId.left(storageId.length() - 8); } auto query = UsedResources | RecentlyUsedFirst | Agent(storageId) | Type::any() | Activity::current() | Url::file(); ResultSet results(query); ResultSet::const_iterator resultIt = results.begin(); int actionCount = 0; while (actionCount < 5 && resultIt != results.end()) { const QString resource = (*resultIt).resource(); const QUrl url(resource); if (!url.isValid()) { continue; } const KFileItem fileItem(url); if (!fileItem.isFile()) { continue; } QAction *action = new QAction(this); action->setText(url.fileName()); action->setIcon(QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme("unknown"))); action->setProperty("agent", storageId); action->setProperty("entryPath", launcherUrl); action->setData(resource); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); ++resultIt; ++actionCount; } if (actionCount > 0) { QAction *action = new QAction(this); action->setText(i18n("Forget Recent Documents")); action->setProperty("agent", storageId); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); } return actions; } void Backend::toolTipWindowChanged(QQuickWindow *window) { Q_UNUSED(window) updateWindowHighlight(); } void Backend::handleJumpListAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } KRun::run(action->property("exec").toString(), {}, nullptr, action->property("applicationName").toString(), action->property("applicationIcon").toString()); } void Backend::handleRecentDocumentAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } const QString agent = action->property("agent").toString(); if (agent.isEmpty()) { return; } const QString desktopPath = action->property("entryPath").toUrl().toLocalFile(); const QString resource = action->data().toString(); if (desktopPath.isEmpty() || resource.isEmpty()) { auto query = UsedResources | Agent(agent) | Type::any() | Activity::current() | Url::file(); KAStats::forgetResources(query); return; } KService::Ptr service = KService::serviceByDesktopPath(desktopPath); qDebug() << service; if (!service) { return; } KRun::runService(*service, QList() << QUrl(resource), QApplication::activeWindow()); } void Backend::setActionGroup(QAction *action) const { if (action) { action->setActionGroup(m_actionGroup); } } QRect Backend::globalRect(QQuickItem *item) const { if (!item || !item->window()) { return QRect(); } QRect iconRect(item->x(), item->y(), item->width(), item->height()); iconRect.moveTopLeft(item->parentItem()->mapToScene(iconRect.topLeft()).toPoint()); iconRect.moveTopLeft(item->window()->mapToGlobal(iconRect.topLeft())); return iconRect; } void Backend::ungrabMouse(QQuickItem *item) const { if (item && item->window() && item->window()->mouseGrabberItem()) { item->window()->mouseGrabberItem()->ungrabMouse(); } } bool Backend::canPresentWindows() const { return (KWindowSystem::compositingActive() && KWindowEffects::isEffectAvailable(KWindowEffects::PresentWindowsGroup)); } void Backend::presentWindows(const QVariant &_winIds) { QList winIds; const QVariantList &_winIdsList = _winIds.toList(); foreach(const QVariant &_winId, _winIdsList) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { winIds.append(winId); } } if (!winIds.count()) { return; } if (m_windowsToHighlight.count()) { m_windowsToHighlight.clear(); updateWindowHighlight(); } KWindowEffects::presentWindows(m_taskManagerItem->window()->winId(), winIds); } bool Backend::isApplication(const QUrl &url) const { if (!url.isValid() || !url.isLocalFile()) { return false; } const QString &localPath = url.toLocalFile(); if (!KDesktopFile::isDesktopFile(localPath)) { return false; } KDesktopFile desktopFile(localPath); return desktopFile.hasApplicationType(); } QList Backend::jsonArrayToUrlList(const QJsonArray &array) const { QList urls; urls.reserve(array.count()); for (auto it = array.constBegin(), end = array.constEnd(); it != end; ++it) { urls << QUrl(it->toString()); } return urls; } +void Backend::cancelHighlightWindows() +{ + m_windowsToHighlight.clear(); + updateWindowHighlight(); +} + void Backend::windowsHovered(const QVariant &_winIds, bool hovered) { m_windowsToHighlight.clear(); if (hovered) { const QVariantList &winIds = _winIds.toList(); foreach(const QVariant &_winId, winIds) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { m_windowsToHighlight.append(winId); } } } updateWindowHighlight(); } void Backend::updateWindowHighlight() { if (!m_highlightWindows) { if (m_panelWinId) { KWindowEffects::highlightWindows(m_panelWinId, QList()); m_panelWinId = 0; } return; } if (m_taskManagerItem && m_taskManagerItem->window()) { m_panelWinId = m_taskManagerItem->window()->winId(); } else { return; } QList windows = m_windowsToHighlight; if (windows.count() && m_toolTipItem && m_toolTipItem->window()) { windows.append(m_toolTipItem->window()->winId()); } KWindowEffects::highlightWindows(m_panelWinId, windows); } diff --git a/applets/taskmanager/plugin/backend.h b/applets/taskmanager/plugin/backend.h index a3788b091..a75013aa1 100644 --- a/applets/taskmanager/plugin/backend.h +++ b/applets/taskmanager/plugin/backend.h @@ -1,109 +1,111 @@ /*************************************************************************** * Copyright (C) 2013-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 . * ***************************************************************************/ #ifndef BACKEND_H #define BACKEND_H #include #include #include class QAction; class QActionGroup; class QQuickItem; class QQuickWindow; class QJsonArray; namespace KActivities { class Consumer; } class Backend : public QObject { Q_OBJECT Q_PROPERTY(QQuickItem* taskManagerItem READ taskManagerItem WRITE setTaskManagerItem NOTIFY taskManagerItemChanged) Q_PROPERTY(QQuickItem* toolTipItem READ toolTipItem WRITE setToolTipItem NOTIFY toolTipItemChanged) Q_PROPERTY(bool highlightWindows READ highlightWindows WRITE setHighlightWindows NOTIFY highlightWindowsChanged) public: enum MiddleClickAction { None = 0, Close, NewInstance, ToggleMinimized }; Q_ENUM(MiddleClickAction); Backend(QObject *parent = 0); ~Backend(); QQuickItem *taskManagerItem() const; void setTaskManagerItem(QQuickItem *item); QQuickItem *toolTipItem() const; void setToolTipItem(QQuickItem *item); bool highlightWindows() const; void setHighlightWindows(bool highlight); Q_INVOKABLE QVariantList jumpListActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE QVariantList recentDocumentActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE void setActionGroup(QAction *action) const; Q_INVOKABLE QRect globalRect(QQuickItem *item) const; Q_INVOKABLE void ungrabMouse(QQuickItem *item) const; Q_INVOKABLE bool canPresentWindows() const; Q_INVOKABLE bool isApplication(const QUrl &url) const; Q_INVOKABLE QList jsonArrayToUrlList(const QJsonArray &array) const; + Q_INVOKABLE void cancelHighlightWindows(); + public Q_SLOTS: void presentWindows(const QVariant &winIds); void windowsHovered(const QVariant &winIds, bool hovered); Q_SIGNALS: void taskManagerItemChanged() const; void toolTipItemChanged() const; void highlightWindowsChanged() const; void addLauncher(const QUrl &url) const; private Q_SLOTS: void toolTipWindowChanged(QQuickWindow *window); void handleJumpListAction() const; void handleRecentDocumentAction() const; private: void updateWindowHighlight(); QQuickItem *m_taskManagerItem; QQuickItem *m_toolTipItem; WId m_panelWinId; bool m_highlightWindows; QList m_windowsToHighlight; QActionGroup *m_actionGroup; KActivities::Consumer *m_activitiesConsumer; }; #endif