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 }