Warning, /plasma/libksysguard/faces/import/Choices.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0004     SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 import QtQuick
0010 import QtQuick.Window
0011 import QtQuick.Controls
0012 import QtQuick.Layouts
0013 import QtQml.Models
0014 
0015 import org.kde.kirigami as Kirigami
0016 import org.kde.kitemmodels as KItemModels
0017 import org.kde.ksysguard.sensors as Sensors
0018 
0019 Control {
0020     id: control
0021 
0022     property bool supportsColors: true
0023     property bool labelsEditable: true
0024     property int maxAllowedSensors: -1
0025     property var selected: []
0026     property var colors: {}
0027     property var labels: {}
0028 
0029     signal selectColor(string sensorId)
0030     signal colorForSensorGenerated(string sensorId, color color)
0031     signal sensorLabelChanged(string sensorId, string label)
0032 
0033     onSelectedChanged: {
0034         if (!control.selected) {
0035             return;
0036         }
0037         for (let i = 0; i < Math.min(control.selected.length, selectedModel.count); ++i) {
0038             selectedModel.set(i, {"sensor": control.selected[i]});
0039         }
0040         if (selectedModel.count > control.selected.length) {
0041             selectedModel.remove(control.selected.length, selectedModel.count - control.selected.length);
0042         } else if (selectedModel.count < control.selected.length) {
0043             for (let i = selectedModel.count; i < control.selected.length; ++i) {
0044                 selectedModel.append({"sensor": control.selected[i]});
0045             }
0046         }
0047     }
0048 
0049     background: TextField {
0050         readOnly: true
0051         hoverEnabled: false
0052 
0053         placeholderText: control.selected.length == 0 ? i18ndc("KSysGuardSensorFaces", "@label", "Click to select a sensor…") : ""
0054 
0055         onFocusChanged: {
0056             if (focus && (maxAllowedSensors <= 0 || repeater.count < maxAllowedSensors)) {
0057                 popup.open()
0058             } else {
0059                 popup.close()
0060             }
0061         }
0062         onReleased: {
0063             if (focus && (maxAllowedSensors <= 0 || repeater.count < maxAllowedSensors)) {
0064                 popup.open()
0065             }
0066         }
0067     }
0068 
0069     contentItem: Flow {
0070         spacing: Kirigami.Units.smallSpacing
0071 
0072         move: Transition {
0073             NumberAnimation {
0074                 properties: "x,y"
0075                 duration: Kirigami.Units.shortDuration
0076                 easing.type: Easing.InOutQuad
0077             }
0078         }
0079         Repeater {
0080             id: repeater
0081             model: ListModel {
0082                 id: selectedModel
0083                 function writeSelectedSensors() {
0084                     let newSelected = [];
0085                     for (let i = 0; i < count; ++i) {
0086                         newSelected.push(get(i).sensor);
0087                     }
0088                     control.selected = newSelected;
0089                     control.selectedChanged();
0090                 }
0091             }
0092 
0093             delegate: Item {
0094                 id: delegate
0095                 implicitHeight: layout.implicitHeight + Kirigami.Units.smallSpacing * 2
0096                 implicitWidth: Math.min(layout.implicitWidth + Kirigami.Units.smallSpacing * 2,
0097                                         control.width - control.leftPadding - control.rightPadding)
0098                 readonly property int position: index
0099                 Rectangle {
0100                     id: delegateContents
0101                     z: 10
0102                     color: Qt.rgba(
0103                                 Kirigami.Theme.highlightColor.r,
0104                                 Kirigami.Theme.highlightColor.g,
0105                                 Kirigami.Theme.highlightColor.b,
0106                                 0.25)
0107                     radius: Kirigami.Units.smallSpacing
0108                     border.color: Kirigami.Theme.highlightColor
0109                     border.width: 1
0110                     opacity: (control.maxAllowedSensors <= 0 || index < control.maxAllowedSensors) ? 1 : 0.4
0111                     parent: drag.active ? control : delegate
0112 
0113                     width: delegate.width
0114                     height: delegate.height
0115                     DragHandler {
0116                         id: drag
0117                         //TODO: uncomment as soon as we can depend from 5.15
0118                         cursorShape: active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
0119                         enabled: selectedModel.count > 1
0120                         onActiveChanged: {
0121                             if (active) {
0122                                 let pos = delegateContents.mapFromItem(control.contentItem, 0, 0);
0123                                 delegateContents.x = pos.x;
0124                                 delegateContents.y = pos.y;
0125                             } else {
0126                                 let pos = delegate.mapFromItem(delegateContents, 0, 0);
0127                                 delegateContents.x = pos.x;
0128                                 delegateContents.y = pos.y;
0129                                 dropAnim.restart();
0130                                 selectedModel.writeSelectedSensors();
0131                             }
0132                         }
0133                         xAxis {
0134                             minimum: 0
0135                             maximum: control.width - delegateContents.width
0136                         }
0137                         yAxis {
0138                             minimum: 0
0139                             maximum: control.height - delegateContents.height
0140                         }
0141                         onCentroidChanged: {
0142                             if (!active || control.contentItem.move.running) {
0143                                 return;
0144                             }
0145                             let pos = control.contentItem.mapFromItem(null, drag.centroid.scenePosition.x, drag.centroid.scenePosition.y);
0146                             pos.x = Math.max(0, Math.min(control.contentItem.width - 1, pos.x));
0147                             pos.y = Math.max(0, Math.min(control.contentItem.height - 1, pos.y));
0148 
0149                             let child = control.contentItem.childAt(pos.x, pos.y);
0150                             if (child === delegate) {
0151                                 return;
0152                             } else if (child) {
0153                                 let newIndex = -1;
0154                                 if (pos.x > child.x + child.width/2) {
0155                                     newIndex = Math.min(child.position + 1, selectedModel.count - 1);
0156                                 } else {
0157                                     newIndex = child.position;
0158                                 }
0159                                 selectedModel.move(index, newIndex, 1);
0160                             }
0161                         }
0162                     }
0163                     ParallelAnimation {
0164                         id: dropAnim
0165                         XAnimator {
0166                             target: delegateContents
0167                             from: delegateContents.x
0168                             to: 0
0169                             duration: Kirigami.Units.shortDuration
0170                             easing.type: Easing.InOutQuad
0171                         }
0172                         YAnimator {
0173                             target: delegateContents
0174                             from: delegateContents.y
0175                             to: 0
0176                             duration: Kirigami.Units.shortDuration
0177                             easing.type: Easing.InOutQuad
0178                         }
0179                     }
0180 
0181                     Sensors.Sensor { id: sensor; sensorId: model.sensor }
0182 
0183                     Component.onCompleted: {
0184                         if (typeof control.colors === "undefined" ||
0185                             typeof control.colors[sensor.sensorId] === "undefined") {
0186                             let color = Qt.hsva(Math.random(), Kirigami.Theme.highlightColor.hsvSaturation, Kirigami.Theme.highlightColor.hsvValue, 1);
0187                             control.colorForSensorGenerated(sensor.sensorId, color)
0188                         }
0189                     }
0190 
0191                     RowLayout {
0192                         id: layout
0193 
0194                         anchors.fill: parent
0195                         anchors.margins: Kirigami.Units.smallSpacing
0196 
0197                         ToolButton {
0198                             visible: control.supportsColors
0199                             Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0200                             Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0201 
0202                             padding: Kirigami.Units.smallSpacing
0203                             flat: false
0204 
0205                             contentItem: Rectangle {
0206                                 color: typeof control.colors === "undefined"  ? "black" : control.colors[sensor.sensorId]
0207                             }
0208 
0209                             onClicked: control.selectColor(sensor.sensorId)
0210                         }
0211 
0212                         RowLayout {
0213                             id: normalLayout
0214                             Label {
0215                                 id: label
0216                                 Layout.fillWidth: true
0217                                 text: {
0218                                     if (!control.labels || !control.labels[sensor.sensorId]) {
0219                                         return sensor.name
0220                                     }
0221                                     return control.labels[sensor.sensorId]
0222                                 }
0223                                 elide: Text.ElideRight
0224 
0225                                 HoverHandler { id: handler }
0226 
0227                                 ToolTip.text: sensor.name
0228                                 ToolTip.visible: handler.hovered && label.truncated
0229                                 ToolTip.delay: Kirigami.Units.toolTipDelay
0230                             }
0231                             ToolButton {
0232                                 id: editButton
0233                                 visible: control.labelsEditable
0234                                 icon.name: "document-edit"
0235                                 icon.width: Kirigami.Units.iconSizes.small
0236                                 icon.height: Kirigami.Units.iconSizes.small
0237                                 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0238                                 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0239                                 onClicked: layout.state = "editing"
0240                             }
0241                             ToolButton {
0242                                 id: removeButton
0243                                 icon.name: "edit-delete-remove"
0244                                 icon.width: Kirigami.Units.iconSizes.small
0245                                 icon.height: Kirigami.Units.iconSizes.small
0246                                 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0247                                 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0248 
0249                                 onClicked: {
0250                                     if (control.selected === undefined || control.selected === null) {
0251                                         control.selected = []
0252                                     }
0253                                     control.selected.splice(control.selected.indexOf(sensor.sensorId), 1)
0254                                     control.selectedChanged()
0255                                 }
0256                             }
0257                         }
0258 
0259                         Loader {
0260                             id: editLoader
0261                             active: false
0262                             visible: active
0263                             focus: active
0264                             Layout.fillWidth: true
0265                             sourceComponent: RowLayout {
0266                                 id: editLayout
0267                                 TextField {
0268                                     id: textField
0269                                     Layout.fillWidth: true
0270                                     text: label.text
0271                                     cursorPosition: 0
0272                                     focus: true
0273                                     onAccepted: {
0274                                         if (text == sensor.name) {
0275                                             text = ""
0276                                         }
0277                                         sensorLabelChanged(sensor.sensorId, text)
0278                                         layout.state = ""
0279                                     }
0280                                 }
0281                                 ToolButton {
0282                                     icon.name: "checkmark"
0283                                     width: Kirigami.Units.iconSizes.smallMedium
0284                                     Layout.preferredHeight: textField.implicitHeight
0285                                     Layout.preferredWidth: Layout.preferredHeight
0286                                     onClicked: textField.accepted()
0287                                 }
0288                             }
0289                         }
0290 
0291                         states: State {
0292                             name: "editing"
0293                             PropertyChanges {
0294                                 target: normalLayout
0295                                 visible: false
0296                             }
0297                             PropertyChanges {
0298                                 target: editLoader
0299                                 active: true
0300                             }
0301                             PropertyChanges {
0302                                 target: delegate
0303                                 implicitWidth: control.availableWidth
0304                             }
0305                         }
0306                         transitions: Transition {
0307                             PropertyAnimation {
0308                                 target: delegate
0309                                 properties: "implicitWidth"
0310                                 duration: Kirigami.Units.shortDuration
0311                                 easing.type: Easing.InOutQuad
0312                             }
0313                         }
0314                     }
0315                 }
0316             }
0317         }
0318 
0319         Item {
0320             width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
0321             height: width
0322             visible: control.maxAllowedSensors <= 0 || control.selected.length < control.maxAllowedSensors
0323         }
0324     }
0325 
0326     Popup {
0327         id: popup
0328 
0329         // Those bindings will be immediately broken on show, but they're needed to not show the popup at a wrong position for an instant
0330         y: (control.Kirigami.ScenePosition.y + control.height + height > control.Window.height)
0331             ? - height
0332             : control.height
0333         implicitHeight: Math.min(contentItem.implicitHeight + 2, Kirigami.Units.gridUnit * 20)
0334         width: control.width + 2
0335         topMargin: 6
0336         bottomMargin: 6
0337         Kirigami.Theme.colorSet: Kirigami.Theme.View
0338         Kirigami.Theme.inherit: false
0339         modal: true
0340         dim: false
0341         closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
0342 
0343         padding: 1
0344 
0345         onOpened: {
0346             if (control.Kirigami.ScenePosition.y + control.height + height > control.Window.height) {
0347                 y = - height;
0348             } else {
0349                 y = control.height
0350             }
0351 
0352             searchField.forceActiveFocus();
0353         }
0354         onClosed: delegateModel.rootIndex = delegateModel.parentModelIndex()
0355 
0356         contentItem: ColumnLayout {
0357             spacing: 0
0358             ToolBar {
0359                 Layout.fillWidth: true
0360                 Layout.minimumHeight: implicitHeight
0361                 Layout.maximumHeight: implicitHeight
0362                 contentItem: ColumnLayout {
0363 
0364                     Kirigami.SearchField {
0365                         id: searchField
0366                         Layout.fillWidth: true
0367                         Layout.fillHeight: true
0368                         placeholderText: i18nd("KSysGuardSensorFaces", "Search...")
0369                         onTextEdited: listView.searchString = text
0370                         onAccepted: listView.searchString = text
0371                         KeyNavigation.down: listView
0372                     }
0373 
0374                     RowLayout {
0375                         visible: delegateModel.rootIndex.valid
0376                         Layout.maximumHeight: visible ? implicitHeight : 0
0377                         ToolButton {
0378                             Layout.fillHeight: true
0379                             Layout.preferredWidth: height
0380                             icon.name: "go-previous"
0381                             text: i18ndc("KSysGuardSensorFaces", "@action:button", "Back")
0382                             display: Button.IconOnly
0383                             onClicked: delegateModel.rootIndex = delegateModel.parentModelIndex()
0384                         }
0385                         Kirigami.Heading {
0386                             level: 2
0387                             text: delegateModel.rootIndex.model ? delegateModel.rootIndex.model.data(delegateModel.rootIndex) : ""
0388                         }
0389                     }
0390                 }
0391             }
0392 
0393             ScrollView {
0394                 Layout.fillWidth: true
0395                 Layout.fillHeight: true
0396                 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
0397                 clip: true
0398 
0399                 ListView {
0400                     id: listView
0401 
0402                     // this causes us to load at least one delegate
0403                     // this is essential in guessing the contentHeight
0404                     // which is needed to initially resize the popup
0405                     cacheBuffer: 1
0406 
0407                     property string searchString
0408 
0409                     implicitHeight: contentHeight
0410 
0411                     model: DelegateModel {
0412                         id: delegateModel
0413 
0414                         model: listView.searchString ? sensorsSearchableModel : treeModel
0415                         delegate: ItemDelegate {
0416                             id: listItem
0417 
0418                             width: listView.width
0419 
0420                             text: model.display
0421 
0422                             leftPadding: mirrored ? indicator.implicitWidth + Kirigami.Units.largeSpacing * 2 : Kirigami.Units.largeSpacing
0423                             rightPadding: !mirrored ? indicator.implicitWidth + Kirigami.Units.largeSpacing * 2 : Kirigami.Units.largeSpacing
0424 
0425                             indicator: Kirigami.Icon {
0426                                 anchors.right: parent.right
0427                                 anchors.rightMargin: Kirigami.Units.largeSpacing
0428                                 anchors.verticalCenter: parent.verticalCenter
0429 
0430                                 width: Kirigami.Units.iconSizes.small
0431                                 height: width
0432                                 source: "go-next-symbolic"
0433                                 opacity: model.SensorId.length == 0
0434                             }
0435 
0436                             onClicked: {
0437                                 if (model.SensorId.length == 0) {
0438                                     delegateModel.rootIndex = delegateModel.modelIndex(index);
0439                                 } else {
0440                                     if (control.selected === undefined || control.selected === null) {
0441                                         control.selected = []
0442                                     }
0443                                     const length = control.selected.push(model.SensorId)
0444                                     control.selectedChanged()
0445                                     if (control.maxAllowedSensors == length) {
0446                                         popup.close();
0447                                     }
0448                                 }
0449                             }
0450                         }
0451                     }
0452 
0453                     Sensors.SensorTreeModel { id: treeModel }
0454 
0455                     KItemModels.KSortFilterProxyModel {
0456                         id: sensorsSearchableModel
0457                         filterCaseSensitivity: Qt.CaseInsensitive
0458                         filterString: listView.searchString
0459                         sourceModel: KItemModels.KSortFilterProxyModel {
0460                             filterRowCallback: function(row, parent) {
0461                                 var sensorId = sourceModel.data(sourceModel.index(row, 0), Sensors.SensorTreeModel.SensorId)
0462                                 return sensorId.length > 0
0463                             }
0464                             sourceModel: KItemModels.KDescendantsProxyModel {
0465                                 model: listView.searchString ? treeModel : null
0466                             }
0467                         }
0468                     }
0469 
0470                     highlightRangeMode: ListView.ApplyRange
0471                     highlightMoveDuration: 0
0472                     boundsBehavior: Flickable.StopAtBounds
0473                 }
0474             }
0475         }
0476 
0477         background: Item {
0478             anchors {
0479                 fill: parent
0480                 margins: -1
0481             }
0482 
0483             Kirigami.ShadowedRectangle {
0484                 anchors.fill: parent
0485                 anchors.margins: 1
0486 
0487                 Kirigami.Theme.colorSet: Kirigami.Theme.View
0488                 Kirigami.Theme.inherit: false
0489 
0490                 radius: 2
0491                 color: Kirigami.Theme.backgroundColor
0492 
0493                 property color borderColor: Kirigami.Theme.textColor
0494                 border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
0495                 border.width: 1
0496 
0497                 shadow.xOffset: 0
0498                 shadow.yOffset: 2
0499                 shadow.color: Qt.rgba(0, 0, 0, 0.3)
0500                 shadow.size: 8
0501             }
0502         }
0503     }
0504 }