/*SPDX-FileCopyrightText: 2012-2016 Eike Hein <hein@kde.org>SPDX-License-Identifier: GPL-2.0-or-later*/pragma ComponentBehavior: Boundimport QtQuickimport QtQuick.Layoutsimport org.kde.plasma.plasmoidimport org.kde.plasma.components as PlasmaComponents3import org.kde.plasma.core as PlasmaCoreimport org.kde.ksvg as KSvgimport org.kde.plasma.private.mpris as Mprisimport org.kde.kirigami as Kirigamiimport org.kde.plasma.workspace.trianglemousefilterimport org.kde.taskmanager as TaskManagerimport plasma.applet.org.kde.plasma.taskmanager as TaskManagerAppletimport org.kde.plasma.workspace.dbus as DBusPlasmoidItem {id: tasks// For making a bottom to top layout since qml flow can't do that.// We just hang the task manager upside down to achieve that.// This mirrors the tasks and group dialog as well, so we un-rotate them// to fix that (see Task.qml and GroupDialog.qml).rotation: Plasmoid.configuration.reverseMode && Plasmoid.formFactor === PlasmaCore.Types.Vertical ? 180 : 0readonly property bool shouldShrinkToZero: tasksModel.count === 0readonly property bool vertical: Plasmoid.formFactor === PlasmaCore.Types.Verticalreadonly property bool iconsOnly: Plasmoid.pluginName === "org.kde.plasma.icontasks"property Task toolTipOpenedByClickproperty Task toolTipAreaItemreadonly property Component pulseAudioComponent: Qt.createComponent("PulseAudio.qml")property alias taskList: taskListpreferredRepresentation: fullRepresentationPlasmoid.constraintHints: Plasmoid.CanFillAreaPlasmoid.onUserConfiguringChanged: {if (Plasmoid.userConfiguring && groupDialog !== null) {groupDialog.visible = false;}}Layout.fillWidth: vertical ? true : Plasmoid.configuration.fillLayout.fillHeight: !vertical ? true : Plasmoid.configuration.fillLayout.minimumWidth: {if (shouldShrinkToZero) {return Kirigami.Units.gridUnit; // For edit mode}return vertical ? 0 : TaskManagerApplet.LayoutMetrics.preferredMinWidth();}Layout.minimumHeight: {if (shouldShrinkToZero) {return Kirigami.Units.gridUnit; // For edit mode}return !vertical ? 0 : TaskManagerApplet.LayoutMetrics.preferredMinHeight();}//BEGIN TODO: this is not precise enough: launchers are smaller than full tasksLayout.preferredWidth: {if (shouldShrinkToZero) {return 0.01;}if (vertical) {return Kirigami.Units.gridUnit * 10;}return taskList.Layout.maximumWidth}Layout.preferredHeight: {if (shouldShrinkToZero) {return 0.01;}if (vertical) {return taskList.Layout.maximumHeight}return Kirigami.Units.gridUnit * 2;}//END TODOproperty Item dragSourceproperty var hoveredWinIdssignal requestLayoutonDragSourceChanged: {if (dragSource === null) {tasksModel.syncLaunchers();}}function windowsHovered(winIds: var, hovered: bool): DBus.DBusPendingReply {if (!Plasmoid.configuration.highlightWindows) {return;}// If the hover leave event comes after something else was already hovered, don't cancel the new highlightif (!hovered && !(winIds.every(winId => hoveredWinIds.includes(winId)) &&hoveredWinIds.every(winId => winIds.includes(winId)))) {return}hoveredWinIds = winIdsreturn DBus.SessionBus.asyncCall({service: "org.kde.KWin.HighlightWindow", path: "/org/kde/KWin/HighlightWindow", iface: "org.kde.KWin.HighlightWindow", member: "highlightWindows", arguments: [hovered ? winIds : []], signature: "(as)"});}function cancelHighlightWindows(): DBus.DBusPendingReply {return DBus.SessionBus.asyncCall({service: "org.kde.KWin.HighlightWindow", path: "/org/kde/KWin/HighlightWindow", iface: "org.kde.KWin.HighlightWindow", member: "highlightWindows", arguments: [[]], signature: "(as)"});}function activateWindowView(winIds: var): DBus.DBusPendingReply {if (!effectWatcher.registered) {return;}cancelHighlightWindows();return DBus.SessionBus.asyncCall({service: "org.kde.KWin.Effect.WindowView1", path: "/org/kde/KWin/Effect/WindowView1", iface: "org.kde.KWin.Effect.WindowView1", member: "activate", arguments: [winIds.map(s => String(s))], signature: "(as)"});}function publishIconGeometries(taskItems: /*list<Item>*/var): void {if (TaskManagerApplet.TaskTools.taskManagerInstanceCount >= 2) {return;}for (let i = 0; i < taskItems.length - 1; ++i) {const task = taskItems[i];if (!task.model.IsLauncher && !task.model.IsStartup) {tasksModel.requestPublishDelegateGeometry(tasksModel.makeModelIndex(task.index),backend.globalRect(task), task);}}}readonly property TaskManager.TasksModel tasksModel: TaskManager.TasksModel {id: tasksModelreadonly property int logicalLauncherCount: {if (Plasmoid.configuration.separateLaunchers) {return launcherCount;}let startupsWithLaunchers = 0;for (let i = 0; i < taskRepeater.count; ++i) {const item = taskRepeater.itemAt(i) as Task;// During destruction required properties such as item.model can go null for a while,// so in paths that can trigger on those moments, they need to be guardedif (item?.model?.IsStartup && item.model.HasLauncher) {++startupsWithLaunchers;}}return launcherCount + startupsWithLaunchers;}screenGeometry: Plasmoid.containment.screenGeometryactivity: activityInfo.currentActivityfilterByCurrentVirtualDesktop: Plasmoid.configuration.showOnlyCurrentDesktopfilterByScreen: Plasmoid.configuration.showOnlyCurrentScreenfilterByActivity: Plasmoid.configuration.showOnlyCurrentActivityfilterNotMinimized: Plasmoid.configuration.showOnlyMinimizedhideActivatedLaunchers: tasks.iconsOnly || Plasmoid.configuration.hideLauncherOnStartsortMode: sortModeEnumValue(Plasmoid.configuration.sortingStrategy)launchInPlace: tasks.iconsOnly && Plasmoid.configuration.sortingStrategy === 1separateLaunchers: {if (!tasks.iconsOnly && !Plasmoid.configuration.separateLaunchers&& Plasmoid.configuration.sortingStrategy === 1) {return false;}return true;}groupMode: groupModeEnumValue(Plasmoid.configuration.groupingStrategy)groupInline: !Plasmoid.configuration.groupPopups && !tasks.iconsOnlygroupingWindowTasksThreshold: (Plasmoid.configuration.onlyGroupWhenFull && !tasks.iconsOnly? TaskManagerApplet.LayoutMetrics.optimumCapacity(tasks.width, tasks.height) + 1 : -1)onLauncherListChanged: {Plasmoid.configuration.launchers = launcherList;}onGroupingAppIdBlacklistChanged: {Plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist;}onGroupingLauncherUrlBlacklistChanged: {Plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist;}function sortModeEnumValue(index: int): /*TaskManager.TasksModel.SortMode*/ int {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;// 5 is SortLastActivated, skippedcase 6:return TaskManager.TasksModel.SortWindowPositionHorizontal;default:return TaskManager.TasksModel.SortDisabled;}}function groupModeEnumValue(index: int): /*TaskManager.TasksModel.GroupMode*/ int {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;}}readonly property TaskManagerApplet.Backend backend: TaskManagerApplet.Backend {id: backendscreen: Plasmoid.screen ?? -1hasActiveTask: tasksModel.count > 0 && tasksModel.activeTask.validshowsOnlyCurrentScreen: Plasmoid.configuration.showOnlyCurrentScreenonAddLauncher: url => {tasks.addLauncher(url);}onActivateTaskAtIndexRequested: index => {tasks.activateTaskAtIndex(index);}onActivatePreviousTaskRequested: () => {tasks.activatePreviousTask();}onActivateNextTaskRequested: () => {tasks.activateNextTask();}onMoveActiveTaskBackwardRequested: () => {tasks.moveActiveTaskBackward();}onMoveActiveTaskForwardRequested: () => {tasks.moveActiveTaskForward();}}DBus.DBusServiceWatcher {id: effectWatcherbusType: DBus.BusType.SessionwatchedService: "org.kde.KWin.Effect.WindowView1"}readonly property Component taskInitComponent: Component {Timer {interval: 200running: trueonTriggered: {const task = parent as Task;if (task) {tasks.tasksModel.requestPublishDelegateGeometry(task.modelIndex(), tasks.backend.globalRect(task), task);}destroy();}}}Connections {target: Plasmoidfunction onLocationChanged(): void {if (TaskManagerApplet.TaskTools.taskManagerInstanceCount >= 2) {return;}// 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.containmentfunction onScreenGeometryChanged(): void {iconGeometryTimer.start();}}Mpris.Mpris2Model {id: mpris2Source}Component {id: contextMenuComponentContextMenu {}}Item {anchors.fill: parentTaskManager.VirtualDesktopInfo {id: virtualDesktopInfo}TaskManager.ActivityInfo {id: activityInforeadonly property string nullUuid: "00000000-0000-0000-0000-000000000000"}Loader {id: pulseAudiosourceComponent: tasks.pulseAudioComponentactive: tasks.pulseAudioComponent.status === Component.Ready}Timer {id: iconGeometryTimerinterval: 500repeat: falseonTriggered: {tasks.publishIconGeometries(taskList.children, tasks);}}Binding {target: Plasmoidproperty: "status"value: (tasksModel.anyTaskDemandsAttention && Plasmoid.configuration.unhideOnAttention? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus)restoreMode: Binding.RestoreBinding}Connections {target: Plasmoid.configurationfunction onLaunchersChanged(): void {tasksModel.launcherList = Plasmoid.configuration.launchers}function onGroupingAppIdBlacklistChanged(): void {tasksModel.groupingAppIdBlacklist = Plasmoid.configuration.groupingAppIdBlacklist;}function onGroupingLauncherUrlBlacklistChanged(): void {tasksModel.groupingLauncherUrlBlacklist = Plasmoid.configuration.groupingLauncherUrlBlacklist;}}Component {id: busyIndicatorPlasmaComponents3.BusyIndicator {}}// Save drag dataItem {id: dragHelperDrag.dragType: Drag.AutomaticDrag.supportedActions: Qt.CopyAction | Qt.MoveAction | Qt.LinkActionDrag.onDragFinished: dropAction => {tasks.dragSource = null;}}KSvg.FrameSvgItem {id: taskFramevisible: falseimagePath: "widgets/tasks"prefix: TaskManagerApplet.TaskTools.taskPrefix("normal", Plasmoid.location)}MouseHandler {id: mouseHandleranchors.fill: parenttarget: taskListonUrlsDropped: urls => {// If all dropped URLs point to application desktop files, we'll add a launcher for each of them.const createLaunchers = urls.every(item => tasks.backend.isApplication(item));if (createLaunchers) {urls.forEach(item => addLauncher(item));return;}if (!hoveredItem) {return;}// 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 as Task).modelIndex(), urls);}}ToolTipDelegate {id: openWindowToolTipDelegatevisible: false}ToolTipDelegate {id: pinnedAppToolTipDelegatevisible: false}TriangleMouseFilter {id: tmffilterTimeOut: 300active: tasks.toolTipAreaItem && tasks.toolTipAreaItem.toolTipOpenblockFirstEnter: falseedge: {switch (Plasmoid.location) {case PlasmaCore.Types.BottomEdge:return Qt.TopEdge;case PlasmaCore.Types.TopEdge:return Qt.BottomEdge;case PlasmaCore.Types.LeftEdge:return Qt.RightEdge;case PlasmaCore.Types.RightEdge:return Qt.LeftEdge;default:return Qt.TopEdge;}}LayoutMirroring.enabled: tasks.shouldBeMirrored(Plasmoid.configuration.reverseMode, Application.layoutDirection, tasks.vertical)anchors {left: parent.lefttop: parent.top}height: taskList.heightwidth: taskList.widthTaskList {id: taskListLayoutMirroring.enabled: tasks.shouldBeMirrored(Plasmoid.configuration.reverseMode, Application.layoutDirection, tasks.vertical)anchors {left: parent.lefttop: parent.top}count: tasksModel.countreadonly property real widthOccupation: taskRepeater.count / columnsreadonly property real heightOccupation: taskRepeater.count / rowsLayout.maximumWidth: {const totalMaxWidth = children.reduce((accumulator, child) => {if (!isFinite(child.Layout.maximumWidth)) {return accumulator;}return accumulator + child.Layout.maximumWidth}, 0);return Math.round(totalMaxWidth / widthOccupation);}Layout.maximumHeight: {const totalMaxHeight = children.reduce((accumulator, child) => {if (!isFinite(child.Layout.maximumHeight)) {return accumulator;}return accumulator + child.Layout.maximumHeight}, 0);return Math.round(totalMaxHeight / heightOccupation);}width: {if (tasks.shouldShrinkToZero) {return 0;}if (tasks.vertical) {return tasks.width * Math.min(1, widthOccupation);} else {return Math.min(tasks.width, Layout.maximumWidth);}}height: {if (tasks.shouldShrinkToZero) {return 0;}if (tasks.vertical) {return Math.min(tasks.height, Layout.maximumHeight);} else {return tasks.height * Math.min(1, heightOccupation);}}flow: {if (tasks.vertical) {return Plasmoid.configuration.forceStripes ? Grid.LeftToRight : Grid.TopToBottom}return Plasmoid.configuration.forceStripes ? Grid.TopToBottom : Grid.LeftToRight}onAnimatingChanged: {if (!animating) {tasks.publishIconGeometries(children, tasks);}}Repeater {id: taskRepeaterdelegate: Task {tasksRoot: tasks}}}}}readonly property Component groupDialogComponent: Qt.createComponent("GroupDialog.qml")property GroupDialog groupDialogreadonly property bool supportsLaunchers: truefunction hasLauncher(url: url): bool {return tasksModel.launcherPosition(url) !== -1;}function addLauncher(url: url): void {if (Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) {tasksModel.requestAddLauncher(url);}}function removeLauncher(url: url): void {if (Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) {tasksModel.requestRemoveLauncher(url);}}function activateTaskAtIndex(index: int): void {const task = taskRepeater.itemAt(index) as Task;if (task) {TaskManagerApplet.TaskTools.activateTask(task.modelIndex(), task.model, null, task, Plasmoid, this, effectWatcher.registered);}}// Shared helpers for activatePreviousTask/activateNextTask.function buildFlatTaskList() {const count = taskRepeater.count;if (count === 0) return [];let taskIndexList = [];for (let i = 0; i < count; ++i) {const item = taskRepeater.itemAt(i);if (!item || item.model.IsLauncher || item.model.IsStartup) continue;if (item.model.IsGroupParent) {for (let j = 0; j < tasksModel.rowCount(item.modelIndex()); ++j) {taskIndexList.push(tasksModel.makeModelIndex(i, j));}} else if (item.model.IsWindow) {taskIndexList.push(item.modelIndex());}}return taskIndexList;}function findActivePosition(taskIndexList): int {for (let i = 0; i < taskIndexList.length; ++i) {if (tasksModel.data(taskIndexList[i], TaskManager.AbstractTasksModel.IsActive)) {return i;}}return -1;}function activatePreviousTask(): void {const taskIndexList = buildFlatTaskList();if (taskIndexList.length === 0) return;const activePos = findActivePosition(taskIndexList);if (activePos < 0) return;const prevPos = (activePos - 1 + taskIndexList.length) % taskIndexList.length;tasksModel.requestActivate(taskIndexList[prevPos]);}function activateNextTask(): void {const taskIndexList = buildFlatTaskList();if (taskIndexList.length === 0) return;const activePos = findActivePosition(taskIndexList);if (activePos < 0) return;const nextPos = (activePos + 1) % taskIndexList.length;tasksModel.requestActivate(taskIndexList[nextPos]);}// Shared helper for moveActiveTaskBackward/moveActiveTaskForward.function getActiveRow(): int {if (tasksModel.count === 0) return -1;// Switch to manual sorting via the config so the binding and the// settings UI stay in sync.Plasmoid.configuration.sortingStrategy = 1; // Manual// Find the active task's top-level row from the now-resorted model.const activeModelIndex = tasksModel.activeTask;if (!activeModelIndex.valid) return -1;// If the active task is a group child, find its parent's row.const parentIndex = activeModelIndex.parent;return parentIndex.valid ? parentIndex.row : activeModelIndex.row;}function moveActiveTaskBackward(): void {const activeRow = getActiveRow();if (activeRow <= 0) return; // Already leftmosttasksModel.move(activeRow, activeRow - 1);}function moveActiveTaskForward(): void {const activeRow = getActiveRow();if (activeRow < 0 || activeRow >= tasksModel.count - 1) return; // Already rightmosttasksModel.move(activeRow, activeRow + 1);}function createContextMenu(rootTask, modelIndex, args = {}) {const initialArgs = Object.assign(args, {visualParent: rootTask,modelIndex,mpris2Source,backend,});return contextMenuComponent.createObject(rootTask, initialArgs);}function shouldBeMirrored(reverseMode, layoutDirection, vertical): bool {// LayoutMirroring is only horizontalif (vertical) {return layoutDirection === Qt.RightToLeft;}if (layoutDirection === Qt.LeftToRight) {return reverseMode;}return !reverseMode;}Component.onCompleted: {TaskManagerApplet.TaskTools.taskManagerInstanceCount += 1;requestLayout.connect(iconGeometryTimer.restart);}Component.onDestruction: {TaskManagerApplet.TaskTools.taskManagerInstanceCount -= 1;}}