Warning, /plasma/plasma-desktop/applets/pager/package/contents/ui/main.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2012 Luís Gabriel Lima <lampih@gmail.com> 0003 SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de> 0004 SPDX-FileCopyrightText: 2016 Eike Hein <hein@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 import QtQuick 2.15 0010 import QtQuick.Layouts 1.1 0011 import org.kde.plasma.plasmoid 2.0 0012 import org.kde.plasma.core as PlasmaCore 0013 import org.kde.ksvg 1.0 as KSvg 0014 import org.kde.plasma.components 3.0 as PlasmaComponents3 0015 import org.kde.draganddrop 2.0 as DnD 0016 import org.kde.plasma.private.pager 2.0 0017 import org.kde.plasma.activityswitcher as ActivitySwitcher 0018 import org.kde.kirigami 2.20 as Kirigami 0019 0020 import org.kde.kcmutils as KCM 0021 import org.kde.config as KConfig 0022 0023 PlasmoidItem { 0024 id: root 0025 0026 readonly property bool isActivityPager: Plasmoid.pluginName === "org.kde.plasma.activitypager" 0027 readonly property bool vertical: Plasmoid.formFactor === PlasmaCore.Types.Vertical 0028 0029 readonly property real aspectRatio: (((pagerModel.pagerItemSize.width * pagerItemGrid.effectiveColumns) 0030 + ((pagerItemGrid.effectiveColumns * pagerItemGrid.spacing) - pagerItemGrid.spacing)) 0031 / ((pagerModel.pagerItemSize.height * pagerItemGrid.effectiveRows) 0032 + ((pagerItemGrid.effectiveRows * pagerItemGrid.spacing) - pagerItemGrid.spacing))) 0033 0034 Layout.minimumWidth: !root.vertical ? Math.floor(height * aspectRatio) : 1 0035 Layout.minimumHeight: root.vertical ? Math.floor(width / aspectRatio) : 1 0036 0037 Layout.maximumWidth: !root.vertical ? Math.floor(height * aspectRatio) : Infinity 0038 Layout.maximumHeight: root.vertical ? Math.floor(width / aspectRatio) : Infinity 0039 0040 Plasmoid.status: pagerModel.shouldShowPager ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.HiddenStatus 0041 0042 Layout.fillWidth: root.vertical 0043 Layout.fillHeight: !root.vertical 0044 0045 property bool dragging: false 0046 property string dragId 0047 0048 property int dragSwitchDesktopIndex: -1 0049 0050 property int wheelDelta: 0 0051 0052 function colorWithAlpha(color: color, alpha: real): color { 0053 return Qt.rgba(color.r, color.g, color.b, alpha) 0054 } 0055 0056 readonly property color windowActiveOnActiveDesktopColor: colorWithAlpha(Kirigami.Theme.textColor, 0.6) 0057 readonly property color windowInactiveOnActiveDesktopColor: colorWithAlpha(Kirigami.Theme.textColor, 0.35) 0058 readonly property color windowActiveColor: colorWithAlpha(Kirigami.Theme.textColor, 0.5) 0059 readonly property color windowActiveBorderColor: Kirigami.Theme.textColor 0060 readonly property color windowInactiveColor: colorWithAlpha(Kirigami.Theme.textColor, 0.17) 0061 readonly property color windowInactiveBorderColor: colorWithAlpha(Kirigami.Theme.textColor, 0.5) 0062 0063 function sanitize(input: string): string { 0064 // Based on QQuickStyledTextPrivate::parseEntity 0065 const table = { 0066 '>': '>', 0067 '<': '<', 0068 '&': '&', 0069 "'": ''', 0070 '"': '"', 0071 '\u00a0': ' ', 0072 }; 0073 return input.replace(/[<>&'"\u00a0]/g, c => table[c]); 0074 } 0075 0076 function generateWindowList(windows) { 0077 // if we have 5 windows, we would show "4 and another one" with the 0078 // hint that there's 1 more taking the same amount of space than just showing it 0079 const maximum = windows.length === 5 ? 5 : 4 0080 0081 let text = "<ul><li>" 0082 + windows.slice(0, maximum).map(sanitize).join("</li><li>") 0083 + "</li></ul>"; 0084 0085 if (windows.length > maximum) { 0086 text += i18np("…and %1 other window", "…and %1 other windows", windows.length - maximum) 0087 } 0088 0089 return text 0090 } 0091 0092 MouseArea { 0093 id: rootMouseArea 0094 anchors.fill: parent 0095 0096 acceptedButtons: Qt.NoButton 0097 hoverEnabled: true 0098 0099 onContainsMouseChanged: { 0100 if (!containsMouse && dragging) { 0101 // Somewhat heavy-handed way to clean up after a window delegate drag 0102 // exits the window. 0103 pagerModel.refresh(); 0104 dragging = false; 0105 } 0106 } 0107 0108 onWheel: wheel => { 0109 // Magic number 120 for common "one click, see: 0110 // https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop 0111 wheelDelta += wheel.angleDelta.y || wheel.angleDelta.x; 0112 0113 let increment = 0; 0114 0115 while (wheelDelta >= 120) { 0116 wheelDelta -= 120; 0117 increment++; 0118 } 0119 0120 while (wheelDelta <= -120) { 0121 wheelDelta += 120; 0122 increment--; 0123 } 0124 0125 while (increment !== 0) { 0126 if (increment < 0) { 0127 const nextPage = Plasmoid.configuration.wrapPage? 0128 (pagerModel.currentPage + 1) % repeater.count : 0129 Math.min(pagerModel.currentPage + 1, repeater.count - 1); 0130 pagerModel.changePage(nextPage); 0131 } else { 0132 const previousPage = Plasmoid.configuration.wrapPage ? 0133 (repeater.count + pagerModel.currentPage - 1) % repeater.count : 0134 Math.max(pagerModel.currentPage - 1, 0); 0135 pagerModel.changePage(previousPage); 0136 } 0137 0138 increment += (increment < 0) ? 1 : -1; 0139 wheelDelta = 0; 0140 } 0141 } 0142 } 0143 0144 PagerModel { 0145 id: pagerModel 0146 0147 enabled: root.visible 0148 0149 showDesktop: (Plasmoid.configuration.currentDesktopSelected === 1) 0150 0151 showOnlyCurrentScreen: Plasmoid.configuration.showOnlyCurrentScreen 0152 screenGeometry: Plasmoid.containment.screenGeometry 0153 0154 pagerType: isActivityPager ? PagerModel.Activities : PagerModel.VirtualDesktops 0155 } 0156 0157 Connections { 0158 target: Plasmoid.configuration 0159 0160 function onShowWindowIconsChanged() { 0161 // Causes the model to reset; Component.onCompleted in the 0162 // window delegate now gets a chance to create the icon item, 0163 // which it otherwise will not do. 0164 pagerModel.refresh(); 0165 } 0166 0167 function onDisplayedTextChanged() { 0168 // Causes the model to reset; Component.onCompleted in the 0169 // desktop delegate now gets a chance to create the label item, 0170 // which it otherwise will not do. 0171 pagerModel.refresh(); 0172 } 0173 } 0174 0175 Component { 0176 id: desktopLabelComponent 0177 0178 PlasmaComponents3.Label { 0179 required property int index 0180 required property var model 0181 required property KSvg.FrameSvgItem desktopFrame 0182 0183 anchors { 0184 fill: parent 0185 topMargin: desktopFrame.margins.top 0186 leftMargin: desktopFrame.margins.left 0187 rightMargin: desktopFrame.margins.right 0188 bottomMargin: desktopFrame.margins.bottom 0189 } 0190 0191 text: Plasmoid.configuration.displayedText ? model.display : index + 1 0192 textFormat: Text.PlainText 0193 0194 wrapMode: Text.NoWrap 0195 elide: Text.ElideRight 0196 0197 horizontalAlignment: Text.AlignHCenter 0198 verticalAlignment: Text.AlignVCenter 0199 0200 font.pixelSize: Math.min(height, Kirigami.Theme.defaultFont.pixelSize) 0201 0202 z: 9999 // The label goes above everything 0203 } 0204 } 0205 0206 Component { 0207 id: windowIconComponent 0208 0209 Kirigami.Icon { 0210 anchors.centerIn: parent 0211 0212 height: Math.min(Kirigami.Units.iconSizes.small, 0213 parent.height, 0214 Math.max(parent.height - (Kirigami.Units.smallSpacing * 2), 0215 Kirigami.Units.smallSpacing * 2)) 0216 width: Math.min(Kirigami.Units.iconSizes.small, 0217 parent.width, 0218 Math.max(parent.width - (Kirigami.Units.smallSpacing * 2), 0219 Kirigami.Units.smallSpacing * 2)) 0220 0221 property var model: null 0222 0223 source: model ? model.decoration : undefined 0224 roundToIconSize: false 0225 animated: false 0226 } 0227 } 0228 0229 Timer { 0230 id: dragTimer 0231 interval: 1000 0232 onTriggered: { 0233 if (root.dragSwitchDesktopIndex !== -1 && root.dragSwitchDesktopIndex !== pagerModel.currentPage) { 0234 pagerModel.changePage(root.dragSwitchDesktopIndex); 0235 } 0236 } 0237 } 0238 onDragSwitchDesktopIndexChanged: if (root.dragSwitchDesktopIndex === -1) { 0239 dragTimer.stop(); 0240 } else { 0241 dragTimer.restart(); 0242 } 0243 0244 Grid { 0245 id: pagerItemGrid 0246 0247 anchors.centerIn: parent 0248 spacing: 1 0249 rows: effectiveRows 0250 columns: effectiveColumns 0251 0252 z: 1 0253 0254 readonly property int effectiveRows: { 0255 if (!pagerModel.count) { 0256 return 1; 0257 } 0258 0259 let rows = 1; 0260 0261 if (isActivityPager && Plasmoid.configuration.pagerLayout !== 0 /*No Default*/) { 0262 if (Plasmoid.configuration.pagerLayout === 1 /*Horizontal*/) { 0263 rows = 1; 0264 } else if (Plasmoid.configuration.pagerLayout === 2 /*Vertical*/) { 0265 rows = pagerModel.count; 0266 } 0267 } else { 0268 let columns = Math.floor(pagerModel.count / pagerModel.layoutRows); 0269 0270 if (pagerModel.count % pagerModel.layoutRows > 0) { 0271 columns += 1; 0272 } 0273 0274 rows = Math.floor(pagerModel.count / columns); 0275 0276 if (pagerModel.count % columns > 0) { 0277 rows += 1; 0278 } 0279 } 0280 0281 return rows; 0282 } 0283 0284 readonly property int effectiveColumns: { 0285 if (!pagerModel.count) { 0286 return 1; 0287 } 0288 0289 return Math.ceil(pagerModel.count / effectiveRows); 0290 } 0291 0292 readonly property real pagerItemSizeRatio: pagerModel.pagerItemSize.width / pagerModel.pagerItemSize.height 0293 readonly property real widthScaleFactor: columnWidth / pagerModel.pagerItemSize.width 0294 readonly property real heightScaleFactor: rowHeight / pagerModel.pagerItemSize.height 0295 0296 states: [ 0297 State { 0298 name: "vertical" 0299 when: root.vertical 0300 PropertyChanges { 0301 target: pagerItemGrid 0302 innerSpacing: effectiveColumns 0303 rowHeight: Math.floor(columnWidth / pagerItemSizeRatio) 0304 columnWidth: Math.floor((root.width - innerSpacing) / effectiveColumns) 0305 } 0306 } 0307 ] 0308 0309 property int innerSpacing: (effectiveRows - 1) * spacing 0310 property int rowHeight: Math.floor((root.height - innerSpacing) / effectiveRows) 0311 property int columnWidth: Math.floor(rowHeight * pagerItemSizeRatio) 0312 0313 Repeater { 0314 id: repeater 0315 0316 model: pagerModel 0317 0318 PlasmaCore.ToolTipArea { 0319 id: desktop 0320 0321 readonly property string desktopId: isActivityPager ? model.TasksModel.activity : model.TasksModel.virtualDesktop 0322 readonly property bool active: (index === pagerModel.currentPage) 0323 0324 mainText: model.display 0325 // our ToolTip has maximumLineCount of 8 which doesn't fit but QML doesn't 0326 // respect that in RichText so we effectively can put in as much as we like :) 0327 // it also gives us more flexibility when it comes to styling the <li> 0328 textFormat: Text.RichText 0329 0330 function updateSubTextIfNeeded() { 0331 if (!containsMouse) { 0332 return; 0333 } 0334 0335 let text = "" 0336 let visibleWindows = [] 0337 let minimizedWindows = [] 0338 0339 for (let i = 0, length = windowRectRepeater.count; i < length; ++i) { 0340 const window = windowRectRepeater.itemAt(i) 0341 if (window) { 0342 if (window.minimized) { 0343 minimizedWindows.push(window.visibleName) 0344 } else { 0345 visibleWindows.push(window.visibleName) 0346 } 0347 } 0348 } 0349 0350 if (visibleWindows.length === 1) { 0351 text += visibleWindows[0] 0352 } else if (visibleWindows.length > 1) { 0353 text += i18np("%1 Window:", "%1 Windows:", visibleWindows.length) 0354 + generateWindowList(visibleWindows) 0355 } 0356 0357 if (visibleWindows.length && minimizedWindows.length) { 0358 if (visibleWindows.length === 1) { 0359 text += "<br>" 0360 } 0361 text += "<br>" 0362 } 0363 0364 if (minimizedWindows.length > 0) { 0365 text += i18np("%1 Minimized Window:", "%1 Minimized Windows:", minimizedWindows.length) 0366 + generateWindowList(minimizedWindows) 0367 } 0368 0369 if (text.length) { 0370 // Get rid of the spacing <ul> would cause 0371 text = "<style>ul { margin: 0; }</style>" + text 0372 } 0373 0374 subText = text 0375 } 0376 0377 width: pagerItemGrid.columnWidth 0378 height: pagerItemGrid.rowHeight 0379 0380 // These states match the set of SVG prefixes for the "widgets/pager" below. 0381 state: { 0382 if ((desktopMouseArea.enabled && (desktopMouseArea.containsMouse || desktopMouseArea.activeFocus)) 0383 || (root.dragging && root.dragId === desktopId)) { 0384 return "hover"; 0385 } else if (active) { 0386 return "active"; 0387 } else { 0388 return "normal"; 0389 } 0390 } 0391 0392 component PagerFrame : KSvg.FrameSvgItem { 0393 anchors.fill: parent 0394 imagePath: "widgets/pager" 0395 opacity: desktop.state === usedPrefix ? 1 : 0 0396 Behavior on opacity { OpacityAnimator { duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic } } 0397 } 0398 0399 PagerFrame { 0400 id: desktopFrame 0401 z: 2 // Above window outlines, but below label 0402 prefix: "hover" 0403 } 0404 PagerFrame { 0405 z: 3 0406 prefix: "active" 0407 } 0408 PagerFrame { 0409 z: 4 0410 prefix: "normal" 0411 } 0412 0413 DnD.DropArea { 0414 id: droparea 0415 anchors.fill: parent 0416 preventStealing: true 0417 0418 onDragEnter: event => { 0419 root.dragSwitchDesktopIndex = index; 0420 } 0421 onDragLeave: event => { 0422 // new onDragEnter may happen before an old onDragLeave 0423 if (root.dragSwitchDesktopIndex === index) { 0424 root.dragSwitchDesktopIndex = -1; 0425 } 0426 } 0427 onDrop: event => { 0428 pagerModel.drop(event.mimeData, event.modifiers, desktop.desktopId); 0429 root.dragSwitchDesktopIndex = -1; 0430 } 0431 } 0432 0433 MouseArea { 0434 id: desktopMouseArea 0435 0436 anchors.fill: parent 0437 hoverEnabled: true 0438 activeFocusOnTab: true 0439 onClicked: mouse => { 0440 pagerModel.changePage(index); 0441 } 0442 Accessible.name: Plasmoid.configuration.displayedText ? model.display : i18n("Desktop %1", (index + 1)) 0443 Accessible.description: Plasmoid.configuration.displayedText ? i18nc("@info:tooltip %1 is the name of a virtual desktop or an activity", "Switch to %1", model.display) : i18nc("@info:tooltip %1 is the name of a virtual desktop or an activity", "Switch to %1", (index + 1)) 0444 Accessible.role: Accessible.Button 0445 Keys.onPressed: event => { 0446 switch (event.key) { 0447 case Qt.Key_Space: 0448 case Qt.Key_Enter: 0449 case Qt.Key_Return: 0450 case Qt.Key_Select: 0451 pagerModel.changePage(index); 0452 break; 0453 } 0454 } 0455 } 0456 0457 Item { 0458 id: clipRect 0459 0460 x: 1 0461 y: 1 0462 z: 1 // Below FrameSvg 0463 width: desktop.width - 2 * x 0464 height: desktop.height - 2 * y 0465 0466 Repeater { 0467 id: windowRectRepeater 0468 0469 model: TasksModel 0470 0471 onCountChanged: desktop.updateSubTextIfNeeded() 0472 0473 Rectangle { 0474 id: windowRect 0475 0476 z: 1 + model.StackingOrder 0477 0478 property rect geometry: model.Geometry 0479 property string visibleName: model.display 0480 property bool minimized: (model.IsMinimized === true) 0481 onMinimizedChanged: desktop.updateSubTextIfNeeded() 0482 onVisibleNameChanged: desktop.updateSubTextIfNeeded() 0483 0484 /* since we move clipRect with 1, move it back */ 0485 x: Math.round(geometry.x * pagerItemGrid.widthScaleFactor) - 1 0486 y: Math.round(geometry.y * pagerItemGrid.heightScaleFactor) - 1 0487 width: Math.round(geometry.width * pagerItemGrid.widthScaleFactor) 0488 height: Math.round(geometry.height * pagerItemGrid.heightScaleFactor) 0489 visible: model.IsMinimized !== true 0490 color: { 0491 if (desktop.active) { 0492 if (model.IsActive === true) 0493 return windowActiveOnActiveDesktopColor; 0494 else 0495 return windowInactiveOnActiveDesktopColor; 0496 } else { 0497 if (model.IsActive === true) 0498 return windowActiveColor; 0499 else 0500 return windowInactiveColor; 0501 } 0502 } 0503 0504 border.width: 1 0505 border.color: (model.IsActive === true) ? windowActiveBorderColor 0506 : windowInactiveBorderColor 0507 0508 Behavior on width { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic } } 0509 Behavior on height { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic } } 0510 Behavior on color { ColorAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic } } 0511 Behavior on border.color { ColorAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic } } 0512 0513 MouseArea { 0514 id: windowMouseArea 0515 anchors.fill: parent 0516 0517 drag.target: windowRect 0518 drag.threshold: 1 0519 drag.axis: Drag.XAndYAxis 0520 drag.minimumX: 0 0521 drag.maximumX: root.width - windowRect.width 0522 drag.minimumY: 0 0523 drag.maximumY: root.height - windowRect.height 0524 0525 drag.onActiveChanged: { 0526 root.dragging = drag.active; 0527 root.dragId = desktop.desktopId; 0528 desktopMouseArea.enabled = !drag.active; 0529 0530 if (drag.active) { 0531 // Reparent to allow drags outside of this desktop. 0532 const value = root.mapFromItem(clipRect, windowRect.x, windowRect.y); 0533 windowRect.parent = root; 0534 windowRect.x = value.x; 0535 windowRect.y = value.y; 0536 } 0537 } 0538 0539 onReleased: mouse => { 0540 if (root.dragging) { 0541 windowRect.visible = false; 0542 const windowCenter = Qt.point(windowRect.x + windowRect.width / 2, windowRect.y + windowRect.height / 2); 0543 const pagerItem = pagerItemGrid.childAt(windowCenter.x, windowCenter.y); 0544 0545 if (pagerItem) { 0546 const relativeTopLeft = root.mapToItem(pagerItem, windowRect.x, windowRect.y); 0547 0548 const modelIndex = windowRectRepeater.model.index(index, 0) 0549 pagerModel.moveWindow(modelIndex, relativeTopLeft.x, relativeTopLeft.y, 0550 pagerItem.desktopId, root.dragId, 0551 pagerItemGrid.widthScaleFactor, pagerItemGrid.heightScaleFactor); 0552 } 0553 0554 // Will reset the model, destroying the reparented drag delegate that 0555 // is no longer bound to model.Geometry. 0556 root.dragging = false; 0557 pagerModel.refresh(); 0558 } else { 0559 // When there is no dragging (just a click), the event is passed 0560 // to the desktop MouseArea. 0561 desktopMouseArea.clicked(mouse); 0562 } 0563 } 0564 } 0565 0566 Component.onCompleted: { 0567 if (Plasmoid.configuration.showWindowIcons) { 0568 windowIconComponent.createObject(windowRect, { model }); 0569 } 0570 } 0571 } 0572 } 0573 } 0574 0575 Component.onCompleted: { 0576 if (Plasmoid.configuration.displayedText < 2) { 0577 desktopLabelComponent.createObject(desktop, { index, model, desktopFrame }); 0578 } 0579 } 0580 0581 onContainsMouseChanged: updateSubTextIfNeeded() 0582 } 0583 } 0584 } 0585 0586 Plasmoid.contextualActions: [ 0587 PlasmaCore.Action { 0588 text: i18n("Show Activity Manager…") 0589 icon.name: "activities" 0590 visible: root.isActivityPager 0591 onTriggered: ActivitySwitcher.Backend.toggleActivityManager() 0592 }, 0593 PlasmaCore.Action { 0594 text: i18n("Add Virtual Desktop") 0595 icon.name: "list-add" 0596 visible: !root.isActivityPager && KConfig.KAuthorized.authorize("kcm_kwin_virtualdesktops") 0597 onTriggered: pagerModel.addDesktop() 0598 }, 0599 PlasmaCore.Action { 0600 text: i18n("Remove Virtual Desktop") 0601 icon.name: "list-remove" 0602 visible: !root.isActivityPager && KConfig.KAuthorized.authorize("kcm_kwin_virtualdesktops") 0603 enabled: repeater.count > 1 0604 onTriggered: pagerModel.removeDesktop() 0605 }, 0606 PlasmaCore.Action { 0607 text: i18n("Configure Virtual Desktops…") 0608 visible: !root.isActivityPager && KConfig.KAuthorized.authorize("kcm_kwin_virtualdesktops") 0609 onTriggered: KCM.KCMLauncher.openSystemSettings("kcm_kwin_virtualdesktops") 0610 } 0611 ] 0612 }