Warning, /plasma/plasma-desktop/applets/taskmanager/package/contents/ui/main.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2012-2016 Eike Hein <hein@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 import QtQuick
0008 import QtQuick.Layouts 1.15
0009 import QtQml 2.15
0010 
0011 import org.kde.plasma.plasmoid 2.0
0012 import org.kde.plasma.components 3.0 as PlasmaComponents3
0013 import org.kde.plasma.core as PlasmaCore
0014 import org.kde.ksvg 1.0 as KSvg
0015 import org.kde.plasma.private.mpris as Mpris
0016 import org.kde.kirigami 2.20 as Kirigami
0017 
0018 import org.kde.plasma.workspace.trianglemousefilter 1.0
0019 
0020 import org.kde.taskmanager 0.1 as TaskManager
0021 import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet
0022 
0023 import "code/layout.js" as LayoutManager
0024 import "code/tools.js" as TaskTools
0025 
0026 PlasmoidItem {
0027     id: tasks
0028 
0029     // For making a bottom to top layout since qml flow can't do that.
0030     // We just hang the task manager upside down to achieve that.
0031     // This mirrors the tasks as well, so we just rotate them again to fix that (see Task.qml).
0032     rotation: Plasmoid.configuration.reverseMode && Plasmoid.formFactor === PlasmaCore.Types.Vertical ? 180 : 0
0033 
0034     readonly property bool shouldShirnkToZero: !LayoutManager.logicalTaskCount()
0035     property bool vertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical
0036     property bool iconsOnly: Plasmoid.pluginName === "org.kde.plasma.icontasks"
0037 
0038     property var toolTipOpenedByClick: null
0039 
0040     property QtObject contextMenuComponent: Qt.createComponent("ContextMenu.qml")
0041     property QtObject pulseAudioComponent: Qt.createComponent("PulseAudio.qml")
0042 
0043     property var toolTipAreaItem: null
0044 
0045     property bool needLayoutRefresh: false;
0046     property variant taskClosedWithMouseMiddleButton: []
0047     property alias taskList: taskList
0048 
0049     preferredRepresentation: fullRepresentation
0050 
0051     Plasmoid.constraintHints: Plasmoid.CanFillArea
0052 
0053     Plasmoid.onUserConfiguringChanged: {
0054         if (Plasmoid.userConfiguring && !!tasks.groupDialog) {
0055             tasks.groupDialog.visible = false;
0056         }
0057     }
0058 
0059     Layout.fillWidth: tasks.vertical ? true : Plasmoid.configuration.fill
0060     Layout.fillHeight: !tasks.vertical ? true : Plasmoid.configuration.fill
0061     Layout.minimumWidth: {
0062         if (shouldShirnkToZero) {
0063             return Kirigami.Units.gridUnit; // For edit mode
0064         }
0065         return tasks.vertical ? 0 : LayoutManager.preferredMinWidth();
0066     }
0067     Layout.minimumHeight: {
0068         if (shouldShirnkToZero) {
0069             return Kirigami.Units.gridUnit; // For edit mode
0070         }
0071         return !tasks.vertical ? 0 : LayoutManager.preferredMinHeight();
0072     }
0073 
0074 //BEGIN TODO: this is not precise enough: launchers are smaller than full tasks
0075     Layout.preferredWidth: {
0076         if (shouldShirnkToZero) {
0077             return 0.01;
0078         }
0079         if (tasks.vertical) {
0080             return Kirigami.Units.gridUnit * 10;
0081         }
0082         return (LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxWidth()) / LayoutManager.calculateStripes();
0083     }
0084     Layout.preferredHeight: {
0085         if (shouldShirnkToZero) {
0086             return 0.01;
0087         }
0088         if (tasks.vertical) {
0089             return (LayoutManager.logicalTaskCount() * LayoutManager.preferredMaxHeight()) / LayoutManager.calculateStripes();
0090         }
0091         return Kirigami.Units.gridUnit * 2;
0092     }
0093 //END TODO
0094 
0095     property Item dragSource: null
0096 
0097     signal requestLayout
0098     signal windowsHovered(variant winIds, bool hovered)
0099     signal activateWindowView(variant winIds)
0100 
0101     onDragSourceChanged: {
0102         if (dragSource == null) {
0103             tasksModel.syncLaunchers();
0104         }
0105     }
0106 
0107     function publishIconGeometries(taskItems) {
0108         if (TaskTools.taskManagerInstanceCount >= 2) {
0109             return;
0110         }
0111         for (var i = 0; i < taskItems.length - 1; ++i) {
0112             var task = taskItems[i];
0113 
0114             if (!task.model.IsLauncher && !task.model.IsStartup) {
0115                 tasks.tasksModel.requestPublishDelegateGeometry(tasks.tasksModel.makeModelIndex(task.index),
0116                     backend.globalRect(task), task);
0117             }
0118         }
0119     }
0120 
0121     property TaskManager.TasksModel tasksModel: TaskManager.TasksModel {
0122         id: tasksModel
0123 
0124         readonly property int logicalLauncherCount: {
0125             if (Plasmoid.configuration.separateLaunchers) {
0126                 return launcherCount;
0127             }
0128 
0129             var startupsWithLaunchers = 0;
0130 
0131             for (var i = 0; i < taskRepeater.count; ++i) {
0132                 var item = taskRepeater.itemAt(i);
0133 
0134                 // During destruction required properties such as item.model can go null for a while,
0135                 // so in paths that can trigger on those moments, they need to be guarded
0136                 if (item?.model?.IsStartup && item.model.HasLauncher) {
0137                     ++startupsWithLaunchers;
0138                 }
0139             }
0140 
0141             return launcherCount + startupsWithLaunchers;
0142         }
0143 
0144         virtualDesktop: virtualDesktopInfo.currentDesktop
0145         screenGeometry: Plasmoid.containment.screenGeometry
0146         activity: activityInfo.currentActivity
0147 
0148         filterByVirtualDesktop: Plasmoid.configuration.showOnlyCurrentDesktop
0149         filterByScreen: Plasmoid.configuration.showOnlyCurrentScreen
0150         filterByActivity: Plasmoid.configuration.showOnlyCurrentActivity
0151         filterNotMinimized: Plasmoid.configuration.showOnlyMinimized
0152 
0153         hideActivatedLaunchers: tasks.iconsOnly || !Plasmoid.configuration.separateLaunchers
0154         sortMode: sortModeEnumValue(Plasmoid.configuration.sortingStrategy)
0155         launchInPlace: tasks.iconsOnly && Plasmoid.configuration.sortingStrategy === 1
0156         separateLaunchers: {
0157             if (!tasks.iconsOnly && !Plasmoid.configuration.separateLaunchers
0158                 && Plasmoid.configuration.sortingStrategy === 1) {
0159                 return false;
0160             }
0161 
0162             return true;
0163         }
0164 
0165         groupMode: groupModeEnumValue(Plasmoid.configuration.groupingStrategy)
0166         groupInline: !Plasmoid.configuration.groupPopups && !tasks.iconsOnly
0167         groupingWindowTasksThreshold: (Plasmoid.configuration.onlyGroupWhenFull && !tasks.iconsOnly
0168             ? LayoutManager.optimumCapacity(width, height) + 1 : -1)
0169 
0170         onLauncherListChanged: {
0171             layoutTimer.restart();
0172             Plasmoid.configuration.launchers = launcherList;
0173         }
0174 
0175         onGroupingAppIdBlacklistChanged: {
0176             Plasmoid.configuration.groupingAppIdBlacklist = groupingAppIdBlacklist;
0177         }
0178 
0179         onGroupingLauncherUrlBlacklistChanged: {
0180             Plasmoid.configuration.groupingLauncherUrlBlacklist = groupingLauncherUrlBlacklist;
0181         }
0182 
0183         function sortModeEnumValue(index) {
0184             switch (index) {
0185                 case 0:
0186                     return TaskManager.TasksModel.SortDisabled;
0187                 case 1:
0188                     return TaskManager.TasksModel.SortManual;
0189                 case 2:
0190                     return TaskManager.TasksModel.SortAlpha;
0191                 case 3:
0192                     return TaskManager.TasksModel.SortVirtualDesktop;
0193                 case 4:
0194                     return TaskManager.TasksModel.SortActivity;
0195                 default:
0196                     return TaskManager.TasksModel.SortDisabled;
0197             }
0198         }
0199 
0200         function groupModeEnumValue(index) {
0201             switch (index) {
0202                 case 0:
0203                     return TaskManager.TasksModel.GroupDisabled;
0204                 case 1:
0205                     return TaskManager.TasksModel.GroupApplications;
0206             }
0207         }
0208 
0209         Component.onCompleted: {
0210             launcherList = Plasmoid.configuration.launchers;
0211             groupingAppIdBlacklist = Plasmoid.configuration.groupingAppIdBlacklist;
0212             groupingLauncherUrlBlacklist = Plasmoid.configuration.groupingLauncherUrlBlacklist;
0213 
0214             // Only hook up view only after the above churn is done.
0215             taskRepeater.model = tasksModel;
0216         }
0217     }
0218 
0219     property TaskManagerApplet.Backend backend: TaskManagerApplet.Backend {
0220         id: backend
0221         highlightWindows: Plasmoid.configuration.highlightWindows
0222 
0223         onAddLauncher: {
0224             tasks.addLauncher(url);
0225         }
0226 
0227         onWindowViewAvailableChanged: TaskTools.windowViewAvailable = windowViewAvailable;
0228 
0229         Component.onCompleted: TaskTools.windowViewAvailable = windowViewAvailable;
0230     }
0231 
0232     property Component taskInitComponent: Component {
0233         Timer {
0234             id: timer
0235 
0236             interval: Kirigami.Units.longDuration
0237             running: true
0238 
0239             onTriggered: {
0240                 tasksModel.requestPublishDelegateGeometry(parent.modelIndex(), backend.globalRect(parent), parent);
0241                 timer.destroy();
0242             }
0243         }
0244     }
0245 
0246     Connections {
0247         target: Plasmoid
0248 
0249         function onLocationChanged() {
0250             if (TaskTools.taskManagerInstanceCount >= 2) {
0251                 return;
0252             }
0253             // This is on a timer because the panel may not have
0254             // settled into position yet when the location prop-
0255             // erty updates.
0256             iconGeometryTimer.start();
0257         }
0258     }
0259 
0260     Connections {
0261         target: Plasmoid.containment
0262 
0263         function onScreenGeometryChanged() {
0264             iconGeometryTimer.start();
0265         }
0266     }
0267 
0268     Mpris.Mpris2Model {
0269         id: mpris2Source
0270     }
0271 
0272     MouseArea {
0273         anchors.fill: parent
0274 
0275         hoverEnabled: true
0276         onExited: {
0277             if (needLayoutRefresh) {
0278                 LayoutManager.layout(taskRepeater)
0279                 needLayoutRefresh = false;
0280             }
0281         }
0282 
0283         TaskManager.VirtualDesktopInfo {
0284             id: virtualDesktopInfo
0285         }
0286 
0287         TaskManager.ActivityInfo {
0288             id: activityInfo
0289             readonly property string nullUuid: "00000000-0000-0000-0000-000000000000"
0290         }
0291 
0292         Loader {
0293             id: pulseAudio
0294             sourceComponent: pulseAudioComponent
0295             active: pulseAudioComponent.status === Component.Ready
0296         }
0297 
0298         Timer {
0299             id: iconGeometryTimer
0300 
0301             interval: 500
0302             repeat: false
0303 
0304             onTriggered: {
0305                 tasks.publishIconGeometries(taskList.children, tasks);
0306             }
0307         }
0308 
0309         Binding {
0310             target: Plasmoid
0311             property: "status"
0312             value: (tasksModel.anyTaskDemandsAttention && Plasmoid.configuration.unhideOnAttention
0313                 ? PlasmaCore.Types.NeedsAttentionStatus : PlasmaCore.Types.PassiveStatus)
0314             restoreMode: Binding.RestoreBinding
0315         }
0316 
0317         Connections {
0318             target: Plasmoid.configuration
0319 
0320             function onLaunchersChanged() {
0321                 tasksModel.launcherList = Plasmoid.configuration.launchers
0322             }
0323             function onGroupingAppIdBlacklistChanged() {
0324                 tasksModel.groupingAppIdBlacklist = Plasmoid.configuration.groupingAppIdBlacklist;
0325             }
0326             function onGroupingLauncherUrlBlacklistChanged() {
0327                 tasksModel.groupingLauncherUrlBlacklist = Plasmoid.configuration.groupingLauncherUrlBlacklist;
0328             }
0329             function onIconSpacingChanged() {
0330                 taskList.layout();
0331             }
0332         }
0333 
0334         Component {
0335             id: busyIndicator
0336             PlasmaComponents3.BusyIndicator {}
0337         }
0338 
0339         // Save drag data
0340         Item {
0341             id: dragHelper
0342 
0343             Drag.dragType: Drag.Automatic
0344             Drag.supportedActions: Qt.CopyAction | Qt.MoveAction | Qt.LinkAction
0345             Drag.onDragFinished: tasks.dragSource = null;
0346         }
0347 
0348         KSvg.FrameSvgItem {
0349             id: taskFrame
0350 
0351             visible: false;
0352 
0353             imagePath: "widgets/tasks";
0354             prefix: TaskTools.taskPrefix("normal", Plasmoid.location)
0355         }
0356 
0357         MouseHandler {
0358             id: mouseHandler
0359 
0360             anchors.fill: parent
0361 
0362             target: taskList
0363 
0364             onUrlsDropped: (urls) => {
0365                 // If all dropped URLs point to application desktop files, we'll add a launcher for each of them.
0366                 var createLaunchers = urls.every(function (item) {
0367                     return backend.isApplication(item)
0368                 });
0369 
0370                 if (createLaunchers) {
0371                     urls.forEach(function (item) {
0372                         addLauncher(item);
0373                     });
0374                     return;
0375                 }
0376 
0377                 if (!hoveredItem) {
0378                     return;
0379                 }
0380 
0381                 // Otherwise we'll just start a new instance of the application with the URLs as argument,
0382                 // as you probably don't expect some of your files to open in the app and others to spawn launchers.
0383                 tasksModel.requestOpenUrls(hoveredItem.modelIndex(), urls);
0384             }
0385         }
0386 
0387         ToolTipDelegate {
0388             id: openWindowToolTipDelegate
0389             visible: false
0390         }
0391 
0392         ToolTipDelegate {
0393             id: pinnedAppToolTipDelegate
0394             visible: false
0395         }
0396 
0397         TriangleMouseFilter {
0398             id: tmf
0399             filterTimeOut: 300
0400             active: tasks.toolTipAreaItem && tasks.toolTipAreaItem.toolTipOpen
0401             blockFirstEnter: false
0402 
0403             edge: {
0404                 switch (Plasmoid.location) {
0405                     case PlasmaCore.Types.BottomEdge:
0406                         return Qt.TopEdge;
0407                     case PlasmaCore.Types.TopEdge:
0408                         return Qt.BottomEdge;
0409                     case PlasmaCore.Types.LeftEdge:
0410                         return Qt.RightEdge;
0411                     case PlasmaCore.Types.RightEdge:
0412                         return Qt.LeftEdge;
0413                     default:
0414                         return Qt.TopEdge;
0415                 }
0416             }
0417 
0418             secondaryPoint: {
0419                 if (tasks.toolTipAreaItem === null) {
0420                     return Qt.point(0, 0);
0421                 }
0422                 const x = tasks.toolTipAreaItem.x;
0423                 const y = tasks.toolTipAreaItem.y;
0424                 const height = tasks.toolTipAreaItem.height;
0425                 const width = tasks.toolTipAreaItem.width;
0426                 return Qt.point(x+width/2, height);
0427             }
0428 
0429             anchors {
0430                 left: parent.left
0431                 top: parent.top
0432             }
0433 
0434             height: taskList.implicitHeight
0435             width: taskList.implicitWidth
0436 
0437             TaskList {
0438                 id: taskList
0439 
0440                 anchors {
0441                     left: parent.left
0442                     top: parent.top
0443                 }
0444                 width: tasks.shouldShirnkToZero ? 0 : LayoutManager.layoutWidth()
0445                 height: tasks.shouldShirnkToZero ? 0 : LayoutManager.layoutHeight()
0446 
0447                 flow: {
0448                     if (tasks.vertical) {
0449                         return Plasmoid.configuration.forceStripes ? Grid.LeftToRight : Grid.TopToBottom
0450                     }
0451                     return Plasmoid.configuration.forceStripes ? Grid.TopToBottom : Grid.LeftToRight
0452                 }
0453 
0454                 onAnimatingChanged: {
0455                     if (!animating) {
0456                         tasks.publishIconGeometries(children, tasks);
0457                     }
0458                 }
0459                 onWidthChanged: layoutTimer.restart()
0460                 onHeightChanged: layoutTimer.restart()
0461 
0462                 function layout() {
0463                     LayoutManager.layout(taskRepeater);
0464                 }
0465 
0466                 Timer {
0467                     id: layoutTimer
0468 
0469                     interval: 0
0470                     repeat: false
0471 
0472                     onTriggered: taskList.layout()
0473                 }
0474 
0475                 Repeater {
0476                     id: taskRepeater
0477 
0478                     delegate: Task {}
0479                     onItemAdded: taskList.layout()
0480                     onItemRemoved: {
0481                         if (tasks.containsMouse && index != taskRepeater.count &&
0482                             item.model.WinIdList.length > 0 &&
0483                             taskClosedWithMouseMiddleButton.indexOf(item.winIdList[0]) > -1) {
0484                             needLayoutRefresh = true;
0485                         } else {
0486                             taskList.layout();
0487                         }
0488                         taskClosedWithMouseMiddleButton = [];
0489                     }
0490                 }
0491             }
0492         }
0493     }
0494 
0495     readonly property Component groupDialogComponent: Qt.createComponent("GroupDialog.qml")
0496     property GroupDialog groupDialog: null
0497 
0498     readonly property bool supportsLaunchers: true
0499 
0500     function hasLauncher(url) {
0501         return tasksModel.launcherPosition(url) != -1;
0502     }
0503 
0504     function addLauncher(url) {
0505         if (Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) {
0506             tasksModel.requestAddLauncher(url);
0507         }
0508     }
0509 
0510     function removeLauncher(url) {
0511         if (Plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) {
0512             tasksModel.requestRemoveLauncher(url);
0513         }
0514     }
0515 
0516     // This is called by plasmashell in response to a Meta+number shortcut.
0517     function activateTaskAtIndex(index) {
0518         if (typeof index !== "number") {
0519             return;
0520         }
0521 
0522         var task = taskRepeater.itemAt(index);
0523         if (task) {
0524             TaskTools.activateTask(task.modelIndex(), task.model, null, task, Plasmoid, tasks);
0525         }
0526     }
0527 
0528     function createContextMenu(rootTask, modelIndex, args = {}) {
0529         const initialArgs = Object.assign(args, {
0530             visualParent: rootTask,
0531             modelIndex,
0532             mpris2Source,
0533             backend,
0534         });
0535         return contextMenuComponent.createObject(rootTask, initialArgs);
0536     }
0537 
0538     Component.onCompleted: {
0539         TaskTools.taskManagerInstanceCount += 1;
0540         tasks.requestLayout.connect(layoutTimer.restart);
0541         tasks.requestLayout.connect(iconGeometryTimer.restart);
0542         tasks.windowsHovered.connect(backend.windowsHovered);
0543         tasks.activateWindowView.connect(backend.activateWindowView);
0544     }
0545 
0546     Component.onDestruction: {
0547         TaskTools.taskManagerInstanceCount -= 1;
0548     }
0549 }