/*SPDX-FileCopyrightText: 2012-2016 Eike Hein <hein@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuick 2.15import QtQuick.Layouts 1.15import QtQml 2.15import org.kde.plasma.plasmoid 2.0import org.kde.plasma.core 2.0 as PlasmaCoreimport org.kde.taskmanager 0.1 as TaskManagerimport org.kde.plasma.private.taskmanager 0.1 as TaskManagerAppletimport "code/layout.js" as LayoutManagerimport "code/tools.js" as TaskToolsMouseArea {id: tasksanchors.fill: parenthoverEnabled: truereadonly property bool shouldShirnkToZero: !LayoutManager.logicalTaskCount()property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Verticalproperty bool iconsOnly: plasmoid.pluginName === "org.kde.plasma.icontasks"property var toolTipOpenedByClick: nullproperty QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml")property QtObject pulseAudioComponent: Qt.createComponent("PulseAudio.qml")property bool needLayoutRefresh: false;property variant taskClosedWithMouseMiddleButton: []Plasmoid.preferredRepresentation: Plasmoid.fullRepresentationPlasmoid.constraintHints: PlasmaCore.Types.CanFillAreaPlasmoid.onUserConfiguringChanged: {if (plasmoid.userConfiguring) {groupDialog.visible = false;}}Layout.fillWidth: tasks.vertical ? true : plasmoid.configuration.fillLayout.fillHeight: !tasks.vertical ? true : plasmoid.configuration.fillLayout.minimumWidth: {if (shouldShirnkToZero) {return PlasmaCore.Units.gridUnit; // For edit mode}return tasks.vertical ? 0 : LayoutManager.preferredMinWidth();}Layout.minimumHeight: {if (shouldShirnkToZero) {return PlasmaCore.Units.gridUnit; // For edit mode}return !tasks.vertical ? 0 : LayoutManager.preferredMinHeight();}//BEGIN TODO: this is not precise enough: launchers are smaller than full tasksLayout.preferredWidth: {if (shouldShirnkToZero) {return 0.01;}if (tasks.vertical) {return PlasmaCore.Units.gridUnit * 10;}return (LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes();}Layout.preferredHeight: {if (shouldShirnkToZero) {return 0.01;}if (tasks.vertical) {return (LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes();}return PlasmaCore.Units.gridUnit * 2;}//END TODOproperty Item dragSource: nullsignal requestLayoutsignal windowsHovered(variant winIds, bool hovered)signal activateWindowView(variant winIds)onDragSourceChanged: {if (dragSource == null) {tasksModel.syncLaunchers();}}onExited: {if (needLayoutRefresh) {LayoutManager.layout(taskRepeater)needLayoutRefresh = false;}}TaskManager.TasksModel {id: tasksModelreadonly property int logicalLauncherCount: {if (plasmoid.configuration.separateLaunchers) {return launcherCount;}var startupsWithLaunchers = 0;for (var i = 0; i < taskRepeater.count; ++i) {var item = taskRepeater.itemAt(i);if (item && item.m.IsStartup === true && item.m.HasLauncher === true) {++startupsWithLaunchers;}}return launcherCount + startupsWithLaunchers;}virtualDesktop: virtualDesktopInfo.currentDesktopscreenGeometry: plasmoid.screenGeometryactivity: activityInfo.currentActivityfilterByVirtualDesktop: plasmoid.configuration.showOnlyCurrentDesktopfilterByScreen: plasmoid.configuration.showOnlyCurrentScreenfilterByActivity: plasmoid.configuration.showOnlyCurrentActivityfilterNotMinimized: plasmoid.configuration.showOnlyMinimizedsortMode: sortModeEnumValue(plasmoid.configuration.sortingStrategy)launchInPlace: iconsOnly && plasmoid.configuration.sortingStrategy === 1separateLaunchers: {if (!iconsOnly && !plasmoid.configuration.separateLaunchers&& plasmoid.configuration.sortingStrategy === 1) {return false;}return true;}groupMode: groupModeEnumValue(plasmoid.configuration.groupingStrategy)groupInline: !plasmoid.configuration.groupPopupsgroupingWindowTasksThreshold: (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: activityInforeadonly property string nullUuid: "00000000-0000-0000-0000-000000000000"}TaskManagerApplet.Backend {id: backendtaskManagerItem: taskshighlightWindows: plasmoid.configuration.highlightWindowsonAddLauncher: {tasks.addLauncher(url);}}PlasmaCore.DataSource {id: mpris2Sourceengine: "mpris2"connectedSources: sourcesonSourceAdded: {connectSource(source);}onSourceRemoved: {disconnectSource(source);}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", "")if (desktopFileName.indexOf("applications:") === 0) {desktopFileName = desktopFileName.substr(13)}let fallbackSource = "";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 awayif (source === "@multiplex") {continue;}var sourceData = data[source];if (!sourceData) {continue;}/*** If the task is in a group, we can't use desktopFileName to match the task.* but in case PID match fails, use the match result from desktopFileName.*/if (pid && sourceData.InstancePid === pid) {return source;}if (sourceData.DesktopEntry === desktopFileName) {fallbackSource = source;}var metadata = sourceData.Metadata;if (metadata) {var kdePid = metadata["kde:pid"];if (kdePid && pid === kdePid) {return source;}}}// If PID match fails, return fallbackSource.return fallbackSource;}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 play(source) {startOperation(source, "Play");}function pause(source) {startOperation(source, "Pause");}function playPause(source) {startOperation(source, "PlayPause");}function stop(source) {startOperation(source, "Stop");}function raise(source) {startOperation(source, "Raise");}function quit(source) {startOperation(source, "Quit");}}Loader {id: pulseAudiosourceComponent: pulseAudioComponentactive: pulseAudioComponent.status === Component.Ready}Timer {id: iconGeometryTimerinterval: 500repeat: falseonTriggered: {TaskTools.publishIconGeometries(taskList.children);}}Binding {target: plasmoidproperty: "status"value: (tasksModel.anyTaskDemandsAttention && plasmoid.configuration.unhideOnAttention? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus)restoreMode: Binding.RestoreBinding}Connections {target: plasmoidfunction 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.configurationfunction onLaunchersChanged() {tasksModel.launcherList = plasmoid.configuration.launchers}function onGroupingAppIdBlacklistChanged() {tasksModel.groupingAppIdBlacklist = plasmoid.configuration.groupingAppIdBlacklist;}function onGroupingLauncherUrlBlacklistChanged() {tasksModel.groupingLauncherUrlBlacklist = plasmoid.configuration.groupingLauncherUrlBlacklist;}function onIconSpacingChanged() {taskList.layout();}}TaskManagerApplet.DragHelper {id: dragHelperdragIconSize: PlasmaCore.Units.iconSizes.medium}PlasmaCore.FrameSvgItem {id: taskFramevisible: false;imagePath: "widgets/tasks";prefix: "normal"}PlasmaCore.Svg {id: taskSvgimagePath: "widgets/tasks"}MouseHandler {id: mouseHandleranchors.fill: parenttarget: taskListonUrlsDropped: {// 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<QUrl>.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: openWindowToolTipDelegatevisible: false}ToolTipDelegate {id: pinnedAppToolTipDelegatevisible: false}TaskList {id: taskListanchors {left: parent.lefttop: parent.top}width: tasks.shouldShirnkToZero ? 0 : LayoutManager.layoutWidth()height: tasks.shouldShirnkToZero ? 0 : LayoutManager.layoutHeight()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);}}onWidthChanged: layoutTimer.restart()onHeightChanged: layoutTimer.restart()function layout() {LayoutManager.layout(taskRepeater);}Timer {id: layoutTimerinterval: 0repeat: falseonTriggered: taskList.layout()}Repeater {id: taskRepeaterdelegate: Task {}onItemAdded: taskList.layout()onItemRemoved: {if (tasks.containsMouse && index != taskRepeater.count &&item.winIdList && item.winIdList.length > 0 &&taskClosedWithMouseMiddleButton.indexOf(item.winIdList[0]) > -1) {needLayoutRefresh = true;} else {taskList.layout();}taskClosedWithMouseMiddleButton = [];}}}readonly property Component groupDialogComponent: Qt.createComponent("GroupDialog.qml")property GroupDialog groupDialog: nullfunction 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) {/*** BUG 452187: when activating a task from keyboard, there is no* containsMouse changed signal, so we need to update the tooltip* properties here.*/if (plasmoid.configuration.showToolTips&& plasmoid.configuration.groupedTaskVisualization === 1) {task.toolTipAreaItem.updateMainItemBindings();}TaskTools.activateTask(task.modelIndex(), task.m, null, task);}}function resetDragSource() {dragSource = null;}function createContextMenu(rootTask, modelIndex, args = {}) {const initialArgs = Object.assign(args, {visualParent: rootTask,modelIndex,mpris2Source,backend,});return contextMenuComponent.createObject(rootTask, initialArgs);}Component.onCompleted: {tasks.requestLayout.connect(layoutTimer.restart);tasks.requestLayout.connect(iconGeometryTimer.restart);tasks.windowsHovered.connect(backend.windowsHovered);tasks.activateWindowView.connect(backend.activateWindowView);dragHelper.dropped.connect(resetDragSource);}}