Warning, /plasma/plasma-desktop/containments/panel/contents/ui/main.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org> 0003 SPDX-FileCopyrightText: 2022 Niccolò Venerandi <niccolo@venerandi.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 import QtQuick 2.1 0009 import QtQuick.Layouts 1.1 0010 import org.kde.plasma.plasmoid 2.0 0011 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 PC3 0015 import org.kde.kquickcontrolsaddons 2.0 0016 import org.kde.draganddrop 2.0 as DragDrop 0017 import org.kde.kirigami 2.20 as Kirigami 0018 0019 import "LayoutManager.js" as LayoutManager 0020 0021 ContainmentItem { 0022 id: root 0023 width: 640 0024 height: 48 0025 0026 //BEGIN properties 0027 Layout.preferredWidth: fixedWidth || currentLayout.implicitWidth + currentLayout.horizontalDisplacement 0028 Layout.preferredHeight: fixedHeight || currentLayout.implicitHeight + currentLayout.verticalDisplacement 0029 0030 property Item toolBox 0031 property var layoutManager: LayoutManager 0032 0033 property Item configOverlay 0034 0035 property bool isHorizontal: Plasmoid.formFactor !== PlasmaCore.Types.Vertical 0036 property int fixedWidth: 0 0037 property int fixedHeight: 0 0038 property bool hasSpacer 0039 // True when a widget is being drag and dropped within the panel. 0040 property bool dragAndDropping: false 0041 // True when e.g. the task manager is drag and dropping tasks. 0042 property bool appletRequestsInhibitDnD: false 0043 0044 //END properties 0045 0046 //BEGIN functions 0047 function checkLastSpacer() { 0048 for (var i = 0; i < appletsModel.count; ++i) { 0049 const applet = appletsModel.get(i).applet; 0050 if (!applet || !applet.visible || !applet.Layout) { 0051 continue; 0052 } 0053 if ((isHorizontal && applet.Layout.fillWidth) || 0054 (!isHorizontal && applet.Layout.fillHeight)) { 0055 hasSpacer = true; 0056 return; 0057 } 0058 } 0059 hasSpacer = false; 0060 } 0061 0062 function plasmoidLocationString(): string { 0063 switch (Plasmoid.location) { 0064 case PlasmaCore.Types.LeftEdge: 0065 return "west"; 0066 case PlasmaCore.Types.TopEdge: 0067 return "north"; 0068 case PlasmaCore.Types.RightEdge: 0069 return "east"; 0070 case PlasmaCore.Types.BottomEdge: 0071 return "south"; 0072 } 0073 return ""; 0074 } 0075 //END functions 0076 0077 //BEGIN connections 0078 Containment.onAppletAdded: (applet, geometry) => { 0079 LayoutManager.addApplet(applet, geometry.x, geometry.y); 0080 root.checkLastSpacer(); 0081 // When a new preset panel is added, avoid calling save() multiple times 0082 Qt.callLater(LayoutManager.save); 0083 } 0084 0085 Containment.onAppletRemoved: (applet) => { 0086 let plasmoidItem = root.itemFor(applet); 0087 if (plasmoidItem) { 0088 appletsModel.remove(plasmoidItem.parent.index); 0089 } 0090 checkLastSpacer(); 0091 LayoutManager.save(); 0092 } 0093 0094 Plasmoid.onUserConfiguringChanged: { 0095 if (!Plasmoid.userConfiguring) { 0096 if (root.configOverlay) { 0097 root.configOverlay.destroy(); 0098 root.configOverlay = null; 0099 } 0100 return; 0101 } 0102 0103 if (Plasmoid.immutable) { 0104 return; 0105 } 0106 0107 Plasmoid.applets.forEach(applet => applet.expanded = false); 0108 const component = Qt.createComponent("ConfigOverlay.qml"); 0109 root.configOverlay = component.createObject(root, { 0110 "anchors.fill": dropArea, 0111 }); 0112 component.destroy(); 0113 } 0114 //END connections 0115 0116 DragDrop.DropArea { 0117 id: dropArea 0118 anchors.fill: parent 0119 0120 // These are invisible and only used to read panel margins 0121 // Both will fallback to "standard" panel margins if the theme does not 0122 // define a normal or a thick margin. 0123 KSvg.FrameSvgItem { 0124 id: panelSvg 0125 visible: false 0126 imagePath: "widgets/panel-background" 0127 prefix: [root.plasmoidLocationString(), ""] 0128 } 0129 KSvg.FrameSvgItem { 0130 id: thickPanelSvg 0131 visible: false 0132 prefix: ['thick'].concat(panelSvg.prefix) 0133 imagePath: "widgets/panel-background" 0134 } 0135 property bool marginAreasEnabled: panelSvg.margins != thickPanelSvg.margins 0136 property var marginHighlightSvg: KSvg.Svg{imagePath: "widgets/margins-highlight"} 0137 //Margins are either the size of the margins in the SVG, unless that prevents the panel from being at least half a smallMedium icon) tall at which point we set the margin to whatever allows it to be that...or if it still won't fit, 1. 0138 //the size a margin should be to force a panel to be the required size above 0139 readonly property real spacingAtMinSize: Math.floor(Math.max(1, (isHorizontal ? root.height : root.width) - Kirigami.Units.iconSizes.smallMedium)/2) 0140 0141 Component.onCompleted: { 0142 LayoutManager.plasmoid = root.Plasmoid; 0143 LayoutManager.root = root; 0144 LayoutManager.layout = currentLayout; 0145 LayoutManager.marginHighlights = []; 0146 LayoutManager.appletsModel = appletsModel; 0147 LayoutManager.restore(); 0148 0149 root.Plasmoid.internalAction("configure").visible = Qt.binding(function() { 0150 return !root.Plasmoid.immutable; 0151 }); 0152 root.Plasmoid.internalAction("configure").enabled = Qt.binding(function() { 0153 return !root.Plasmoid.immutable; 0154 }); 0155 } 0156 0157 onDragEnter: event => { 0158 if (Plasmoid.immutable || root.appletRequestsInhibitDnD) { 0159 event.ignore(); 0160 return; 0161 } 0162 //during drag operations we disable panel auto resize 0163 root.fixedWidth = root.Layout.preferredWidth 0164 root.fixedHeight = root.Layout.preferredHeight 0165 appletsModel.insert(LayoutManager.indexAtCoordinates(event.x, event.y), {applet: dndSpacer}) 0166 } 0167 0168 onDragMove: event => { 0169 LayoutManager.move(dndSpacer.parent, LayoutManager.indexAtCoordinates(event.x, event.y)); 0170 } 0171 0172 onDragLeave: event => { 0173 /* 0174 * When reordering task items, dragLeave signal will be emitted directly 0175 * without dragEnter, and in this case parent.index is undefined, so also 0176 * check if dndSpacer is in appletsModel. 0177 */ 0178 if (typeof(dndSpacer.parent.index) === "number") { 0179 appletsModel.remove(dndSpacer.parent.index); 0180 root.fixedWidth = root.fixedHeight = 0; 0181 } 0182 } 0183 0184 onDrop: event => { 0185 appletsModel.remove(dndSpacer.parent.index); 0186 root.processMimeData(event.mimeData, event.x, event.y); 0187 event.accept(event.proposedAction); 0188 root.fixedWidth = root.fixedHeight = 0; 0189 } 0190 0191 //BEGIN components 0192 Component { 0193 id: appletContainerComponent 0194 // This loader conditionally manages the BusyIndicator, it's not 0195 // loading the applet. The applet becomes a regular child item. 0196 Loader { 0197 id: container 0198 required property Item applet 0199 required property int index 0200 property Item dragging 0201 property bool isAppletContainer: true 0202 property bool isMarginSeparator: ((applet.plasmoid?.constraintHints & Plasmoid.MarginAreasSeparator) == Plasmoid.MarginAreasSeparator) 0203 property int appletIndex: index // To make sure it's always readable even inside other models 0204 property bool inThickArea: false 0205 visible: applet.plasmoid?.status !== PlasmaCore.Types.HiddenStatus || (!Plasmoid.immutable && Plasmoid.userConfiguring); 0206 0207 //when the applet moves caused by its resize, don't animate. 0208 //this is completely heuristic, but looks way less "jumpy" 0209 property bool movingForResize: false 0210 0211 function getMargins(side, returnAllMargins = false, overrideFillArea = null, overrideThickArea = null): real { 0212 if (!applet || !applet.plasmoid) { 0213 return 0; 0214 } 0215 //Margins are either the size of the margins in the SVG, unless that prevents the panel from being at least half a smallMedium icon + smallSpace) tall at which point we set the margin to whatever allows it to be that...or if it still won't fit, 1. 0216 let fillArea = overrideFillArea === null ? applet && (applet.plasmoid.constraintHints & Plasmoid.CanFillArea) : overrideFillArea 0217 let inThickArea = overrideThickArea === null ? container.inThickArea : overrideThickArea 0218 var layout = { 0219 top: isHorizontal, bottom: isHorizontal, 0220 right: !isHorizontal, left: !isHorizontal 0221 }; 0222 return ((layout[side] || returnAllMargins) && !fillArea) ? Math.round(Math.min(dropArea.spacingAtMinSize, (inThickArea ? thickPanelSvg.fixedMargins[side] : panelSvg.fixedMargins[side]))) : 0; 0223 } 0224 0225 Layout.topMargin: getMargins('top') 0226 Layout.bottomMargin: getMargins('bottom') 0227 Layout.leftMargin: getMargins('left') 0228 Layout.rightMargin: getMargins('right') 0229 0230 // Always fill width/height, in order to still shrink the applet when there is not enough space. 0231 // When the applet doesn't want to expand set a Layout.maximumWidth accordingly 0232 // https://bugs.kde.org/show_bug.cgi?id=473420 0233 Layout.fillWidth: true 0234 Layout.fillHeight: true 0235 property bool wantsToFillWidth: applet?.Layout.fillWidth 0236 property bool wantsToFillHeight: applet?.Layout.fillHeight 0237 onWantsToFillWidthChanged: checkLastSpacer() 0238 onWantsToFillHeightChanged: checkLastSpacer() 0239 0240 property int availWidth: root.width - Layout.leftMargin - Layout.rightMargin 0241 property int availHeight: root.height - Layout.topMargin - Layout.bottomMargin 0242 function findPositive(first, second) {return first > 0 ? first : second} 0243 0244 // BEGIN BUG 454095: do not combine these expressions to a function or the bindings won't work 0245 Layout.minimumWidth: root.isHorizontal ? findPositive(applet?.Layout.minimumWidth, availHeight) : availWidth 0246 Layout.minimumHeight: !root.isHorizontal ? findPositive(applet?.Layout.minimumHeight, availWidth) : availHeight 0247 0248 Layout.preferredWidth: root.isHorizontal ? findPositive(applet?.Layout.preferredWidth, Layout.minimumWidth) : availWidth 0249 Layout.preferredHeight: !root.isHorizontal ? findPositive(applet?.Layout.preferredHeight, Layout.minimumHeight) : availHeight 0250 0251 Layout.maximumWidth: root.isHorizontal ? (wantsToFillWidth ? findPositive(applet?.Layout.maximumWidth, root.width) : Math.min(applet?.Layout.maximumWidth, Layout.preferredWidth)) : availWidth 0252 Layout.maximumHeight: !root.isHorizontal ? (wantsToFillHeight ? findPositive(applet?.Layout.maximumHeight, root.height) : Math.min(applet?.Layout.maximumHeight, Layout.preferredHeight)) : availHeight 0253 // END BUG 454095 0254 0255 Item { 0256 id: marginHighlightElements 0257 anchors.fill: parent 0258 // index -1 is for floating applets, which do not need a margin highlight 0259 opacity: Plasmoid.containment.corona.editMode && dropArea.marginAreasEnabled && !root.dragAndDropping && index != -1 ? 1 : 0 0260 Behavior on opacity { 0261 NumberAnimation { 0262 duration: Kirigami.Units.longDuration 0263 easing.type: Easing.InOutQuad 0264 } 0265 } 0266 0267 component SideMargin: KSvg.SvgItem { 0268 property string side; property bool fill: true 0269 property int inset; property int padding 0270 property var west: ({'left': 'top', 'top': 'left', 'right': 'top', 'bottom': 'left'}) 0271 property var mirror: ({'left': 'right', 'top': 'bottom', 'right': 'left', 'bottom': 'top'}) 0272 property var onComponentCompleted: { 0273 let left = west[side] 0274 let right = mirror[left] 0275 let up = mirror[side] 0276 anchors[up] = undefined 0277 if (root.isHorizontal) { 0278 height = padding; 0279 } else { 0280 width = padding; 0281 } 0282 anchors[left+'Margin'] = - currentLayout.rowSpacing/2 - (appletIndex == 0 ? dropArea.anchors[left + 'Margin'] + currentLayout.x : 0) 0283 anchors[right+'Margin'] = - currentLayout.rowSpacing/2 - (appletIndex == appletsModel.count-1 ? dropArea.anchors[right + 'Margin'] + currentLayout.toolBoxSize : 0) 0284 anchors[side+'Margin'] = - inset 0285 } 0286 elementId: fill ? 'fill' : (root.isHorizontal ? side + (inThickArea ? 'left' : 'right') : (inThickArea ? 'top' : 'bottom') + side) 0287 svg: dropArea.marginHighlightSvg 0288 anchors {top: parent.top; left: parent.left; right: parent.right; bottom: parent.bottom} 0289 } 0290 Repeater { 0291 model: ['top', 'bottom', 'right', 'left'] 0292 SideMargin { 0293 side: modelData 0294 inset: container.getMargins(side) 0295 visible: (modelData === 'top' || modelData === 'bottom') === root.isHorizontal 0296 padding: container.getMargins(side, false, false, isMarginSeparator ? false : inThickArea) 0297 } 0298 } 0299 Repeater { 0300 model: ['top', 'bottom', 'right', 'left'] 0301 SideMargin { 0302 side: modelData 0303 inset: -container.getMargins(side, false, false, false) 0304 padding: container.getMargins(side, false, false, true) + inset 0305 visible: isMarginSeparator && (modelData === 'top' || modelData === 'bottom') === root.isHorizontal 0306 fill: false 0307 } 0308 } 0309 } 0310 0311 onAppletChanged: { 0312 if (applet) { 0313 applet.parent = container 0314 applet.anchors.fill = container 0315 } else { 0316 appletsModel.remove(index) 0317 } 0318 } 0319 0320 active: applet && applet.Plasmoid.busy 0321 sourceComponent: PC3.BusyIndicator { 0322 z: 999 0323 } 0324 0325 property int oldX: 0 0326 property int oldY: 0 0327 onXChanged: if (oldX) animateFrom(oldX, y) 0328 onYChanged: if (oldY) animateFrom(x, oldY) 0329 transform: Translate{id: translation} 0330 function animateFrom(xa, ya) { 0331 if (isHorizontal) translation.x = xa - x 0332 else translation.y = ya - y 0333 oldX = oldY = 0 0334 translAnim.running = true 0335 } 0336 NumberAnimation { 0337 id: translAnim 0338 duration: Kirigami.Units.shortDuration 0339 easing.type: Easing.OutCubic 0340 target: translation 0341 properties: "x,y" 0342 to: 0 0343 } 0344 } 0345 } 0346 //END components 0347 0348 //BEGIN UI elements 0349 0350 anchors { 0351 leftMargin: isHorizontal ? Math.min(dropArea.spacingAtMinSize, panelSvg.fixedMargins.left + currentLayout.rowSpacing) : 0 0352 rightMargin: isHorizontal ? Math.min(dropArea.spacingAtMinSize, panelSvg.fixedMargins.right + currentLayout.rowSpacing) : 0 0353 topMargin: isHorizontal ? 0 : Math.min(dropArea.spacingAtMinSize, panelSvg.fixedMargins.top + currentLayout.rowSpacing) 0354 bottomMargin: isHorizontal ? 0 : Math.min(dropArea.spacingAtMinSize, panelSvg.fixedMargins.bottom + currentLayout.rowSpacing) 0355 } 0356 0357 Item { 0358 id: dndSpacer 0359 property bool busy: false 0360 Layout.preferredWidth: width 0361 Layout.preferredHeight: height 0362 width: isHorizontal ? Kirigami.Units.iconSizes.sizeForLabels * 5 : currentLayout.width 0363 height: isHorizontal ? currentLayout.height : Kirigami.Units.iconSizes.sizeForLabels * 5 0364 } 0365 0366 ListModel { 0367 id: appletsModel 0368 } 0369 0370 GridLayout { 0371 id: currentLayout 0372 0373 Repeater { 0374 model: appletsModel 0375 delegate: appletContainerComponent 0376 } 0377 0378 rowSpacing: Kirigami.Units.smallSpacing 0379 columnSpacing: Kirigami.Units.smallSpacing 0380 0381 x: Qt.application.layoutDirection === Qt.RightToLeft && isHorizontal ? toolBoxSize : 0; 0382 readonly property int toolBoxSize: !toolBox || !Plasmoid.containment.corona.editMode || Qt.application.layoutDirection === Qt.RightToLeft ? 0 : (isHorizontal ? toolBox.width : toolBox.height) 0383 0384 PC3.ToolButton { 0385 id: addWidgetsButton 0386 Layout.preferredWidth: width 0387 Layout.preferredHeight: height 0388 Layout.alignment: Qt.AlignHCenter 0389 visible: appletsModel.count === 0 0390 text: isHorizontal ? i18nd("plasma_shell_org.kde.plasma.desktop", "Add Widgets…") : undefined 0391 icon.name: "list-add-symbolic" 0392 onClicked: Plasmoid.internalAction("add widgets").trigger() 0393 } 0394 0395 property int horizontalDisplacement: dropArea.anchors.leftMargin + dropArea.anchors.rightMargin + (isHorizontal ? currentLayout.toolBoxSize : 0) 0396 property int verticalDisplacement: dropArea.anchors.topMargin + dropArea.anchors.bottomMargin + (isHorizontal ? 0 : currentLayout.toolBoxSize) 0397 0398 // BEGIN BUG 454095: use lastSpacer to left align applets, as implicitWidth is updated too late 0399 width: root.width - horizontalDisplacement 0400 height: root.height - verticalDisplacement 0401 0402 Item { 0403 id: lastSpacer 0404 visible: !root.hasSpacer 0405 Layout.fillWidth: true 0406 Layout.fillHeight: true 0407 0408 /** 0409 * This index will be used when adding a new panel. 0410 * 0411 * @see LayoutManager.indexAtCoordinates 0412 */ 0413 readonly property alias index: appletsModel.count 0414 } 0415 // END BUG 454095 0416 0417 rows: isHorizontal ? 1 : currentLayout.children.length 0418 columns: isHorizontal ? currentLayout.children.length : 1 0419 flow: isHorizontal ? GridLayout.LeftToRight : GridLayout.TopToBottom 0420 layoutDirection: Qt.application.layoutDirection 0421 } 0422 } 0423 //END UI elements 0424 }