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 }