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 }