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 }