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 2.15 0010 import QtQuick.Window 2.15 0011 import QtQuick.Controls 2.15 0012 import QtQuick.Layouts 1.15 0013 import QtQml.Models 2.15 0014 0015 import org.kde.kirigami 2.12 as Kirigami 0016 import org.kde.kitemmodels 1.0 as KItemModels 0017 import org.kde.ksysguard.sensors 1.0 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 ListView { 0398 id: listView 0399 0400 // this causes us to load at least one delegate 0401 // this is essential in guessing the contentHeight 0402 // which is needed to initially resize the popup 0403 cacheBuffer: 1 0404 0405 property string searchString 0406 0407 implicitHeight: contentHeight 0408 0409 model: DelegateModel { 0410 id: delegateModel 0411 0412 model: listView.searchString ? sensorsSearchableModel : treeModel 0413 delegate: Kirigami.BasicListItem { 0414 width: listView.width 0415 text: model.display 0416 reserveSpaceForIcon: false 0417 0418 Kirigami.Icon { 0419 source: "go-next-symbolic" 0420 Layout.fillHeight: true 0421 Layout.preferredWidth: Kirigami.Units.iconSizes.small 0422 // Still visible for correct size hints calculation 0423 opacity: model.SensorId.length == 0 0424 } 0425 onClicked: { 0426 if (model.SensorId.length == 0) { 0427 delegateModel.rootIndex = delegateModel.modelIndex(index); 0428 } else { 0429 if (control.selected === undefined || control.selected === null) { 0430 control.selected = [] 0431 } 0432 const length = control.selected.push(model.SensorId) 0433 control.selectedChanged() 0434 if (control.maxAllowedSensors == length) { 0435 popup.close(); 0436 } 0437 } 0438 } 0439 } 0440 } 0441 0442 Sensors.SensorTreeModel { id: treeModel } 0443 0444 KItemModels.KSortFilterProxyModel { 0445 id: sensorsSearchableModel 0446 filterCaseSensitivity: Qt.CaseInsensitive 0447 filterString: listView.searchString 0448 sourceModel: KItemModels.KSortFilterProxyModel { 0449 filterRowCallback: function(row, parent) { 0450 var sensorId = sourceModel.data(sourceModel.index(row, 0), Sensors.SensorTreeModel.SensorId) 0451 return sensorId.length > 0 0452 } 0453 sourceModel: KItemModels.KDescendantsProxyModel { 0454 model: listView.searchString ? treeModel : null 0455 } 0456 } 0457 } 0458 0459 highlightRangeMode: ListView.ApplyRange 0460 highlightMoveDuration: 0 0461 boundsBehavior: Flickable.StopAtBounds 0462 } 0463 } 0464 } 0465 0466 background: Item { 0467 anchors { 0468 fill: parent 0469 margins: -1 0470 } 0471 0472 Kirigami.ShadowedRectangle { 0473 anchors.fill: parent 0474 anchors.margins: 1 0475 0476 Kirigami.Theme.colorSet: Kirigami.Theme.View 0477 Kirigami.Theme.inherit: false 0478 0479 radius: 2 0480 color: Kirigami.Theme.backgroundColor 0481 0482 property color borderColor: Kirigami.Theme.textColor 0483 border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3) 0484 border.width: 1 0485 0486 shadow.xOffset: 0 0487 shadow.yOffset: 2 0488 shadow.color: Qt.rgba(0, 0, 0, 0.3) 0489 shadow.size: 8 0490 } 0491 } 0492 } 0493 }