Warning, /plasma/kwin/src/kcms/rules/ui/RulesEditor.qml is written in an unsupported language. File is not indexed.
0001 /*
0002 SPDX-FileCopyrightText: 2020 Ismael Asensio <isma.af@gmail.com>
0003
0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006
0007 import QtQuick
0008 import QtQuick.Layouts
0009 import QtQuick.Controls as QQC2
0010 import org.kde.kirigami 2.19 as Kirigami
0011 import org.kde.kcmutils as KCM
0012 import org.kde.kitemmodels
0013 import org.kde.kcms.kwinrules
0014
0015
0016 KCM.ScrollViewKCM {
0017 id: rulesEditor
0018
0019 title: kcm.rulesModel.description
0020
0021 view: ListView {
0022 id: rulesView
0023 clip: true
0024
0025 model: enabledRulesModel
0026 delegate: RuleItemDelegate {
0027 ListView.onAdd: {
0028 // Try to position the new added item into the visible view
0029 // FIXME: It only works when moving towards the end of the list
0030 ListView.view.currentIndex = index
0031 }
0032 }
0033 section {
0034 property: "section"
0035 delegate: Kirigami.ListSectionHeader {
0036 width: ListView.view.width
0037 label: section
0038 }
0039 }
0040
0041 highlightRangeMode: ListView.ApplyRange
0042
0043 add: Transition {
0044 NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.longDuration * 3 }
0045 }
0046 removeDisplaced: Transition {
0047 NumberAnimation { property: "y"; duration: Kirigami.Units.longDuration }
0048 }
0049
0050 // We need to center on the free space below contentItem, not the full
0051 // ListView. This invisible item helps make that positioning work no
0052 // matter the window height
0053 Item {
0054 anchors {
0055 left: parent.left
0056 right: parent.right
0057 top: parent.contentItem.bottom
0058 bottom: parent.bottom
0059 }
0060 visible: rulesView.count <= 4
0061
0062 Kirigami.PlaceholderMessage {
0063 id: hintArea
0064 anchors.centerIn: parent
0065 width: parent.width - (Kirigami.Units.largeSpacing * 4)
0066 text: i18n("No window properties changed")
0067 explanation: xi18nc("@info", "Click the <interface>Add Property...</interface> button below to add some window properties that will be affected by the rule")
0068 }
0069 }
0070 }
0071
0072 header: ColumnLayout {
0073 visible: warningList.count > 0
0074 Repeater {
0075 id: warningList
0076 model: kcm.rulesModel.warningMessages
0077
0078 delegate: Kirigami.InlineMessage {
0079 text: modelData
0080 visible: true
0081 Layout.fillWidth: true
0082 }
0083 }
0084 }
0085
0086 footer: RowLayout {
0087 QQC2.Button {
0088 text: checked ? i18n("Close") : i18n("Add Property...")
0089 icon.name: checked ? "dialog-close" : "list-add"
0090 checkable: true
0091 checked: propertySheet.visible
0092 onToggled: {
0093 propertySheet.visible = checked;
0094 }
0095 }
0096 Item {
0097 Layout.fillWidth: true
0098 }
0099 QQC2.Button {
0100 id: detectButton
0101 text: i18n("Detect Window Properties")
0102 icon.name: "edit-find"
0103 enabled: !propertySheet.visible && !errorDialog.visible
0104 onClicked: {
0105 overlayModel.onlySuggestions = true;
0106 kcm.rulesModel.detectWindowProperties(Math.max(delaySpin.value * 1000,
0107 Kirigami.Units.shortDuration));
0108 }
0109 }
0110 QQC2.SpinBox {
0111 id: delaySpin
0112 enabled: detectButton.enabled
0113 Layout.preferredWidth: Math.max(metricsInstant.advanceWidth, metricsAfter.advanceWidth) + Kirigami.Units.gridUnit * 4
0114 from: 0
0115 to: 30
0116 textFromValue: (value, locale) => {
0117 return (value == 0) ? i18n("Instantly")
0118 : i18np("After %1 second", "After %1 seconds", value)
0119 }
0120
0121 TextMetrics {
0122 id: metricsInstant
0123 font: delaySpin.font
0124 text: i18n("Instantly")
0125 }
0126 TextMetrics {
0127 id: metricsAfter
0128 font: delaySpin.font
0129 text: i18np("After %1 second", "After %1 seconds", 99)
0130 }
0131 }
0132
0133 }
0134
0135 Connections {
0136 target: kcm.rulesModel
0137 function onShowSuggestions() {
0138 if (errorDialog.visible) {
0139 return;
0140 }
0141 overlayModel.onlySuggestions = true;
0142 propertySheet.visible = true;
0143 }
0144 function onShowErrorMessage(title, message) {
0145 errorDialog.title = title
0146 errorDialog.message = message
0147 errorDialog.open()
0148 }
0149 }
0150
0151 Kirigami.Dialog {
0152 id: errorDialog
0153
0154 property alias message: errorLabel.text
0155
0156 preferredWidth: rulesEditor.width - Kirigami.Units.gridUnit * 6
0157 maximumWidth: Kirigami.Units.gridUnit * 35
0158 footer: null // Just use the close button on top
0159
0160 ColumnLayout {
0161 // Wrap it in a Layout so we can apply margins to the text while keeping implicit sizes
0162 Kirigami.Heading {
0163 id: errorLabel
0164 level: 4
0165 wrapMode: Text.WordWrap
0166
0167 Layout.fillWidth: true
0168 Layout.margins: Kirigami.Units.largeSpacing
0169 }
0170 }
0171 }
0172
0173 Kirigami.OverlaySheet {
0174 id: propertySheet
0175
0176 title: i18n("Add property to the rule")
0177
0178 footer: Kirigami.SearchField {
0179 id: searchField
0180 horizontalAlignment: Text.AlignLeft
0181 }
0182
0183 ListView {
0184 id: overlayView
0185 model: overlayModel
0186 Layout.preferredWidth: Kirigami.Units.gridUnit * 28
0187 clip: true
0188
0189 section {
0190 property: "section"
0191 delegate: Kirigami.ListSectionHeader {
0192 label: section
0193 height: implicitHeight
0194 }
0195 }
0196
0197 delegate: QQC2.ItemDelegate {
0198 id: propertyDelegate
0199 highlighted: false
0200 width: ListView.view.width
0201
0202 contentItem: RowLayout {
0203 Kirigami.Icon {
0204 source: model.icon
0205 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0206 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0207 Layout.alignment: Qt.AlignVCenter
0208 }
0209 QQC2.Label {
0210 id: itemNameLabel
0211 text: model.name
0212 horizontalAlignment: Qt.AlignLeft
0213 Layout.preferredWidth: implicitWidth
0214 Layout.fillWidth: true
0215 Layout.alignment: Qt.AlignVCenter
0216 }
0217 QQC2.Label {
0218 id: suggestedLabel
0219 text: formatValue(model.suggested, model.type, model.options)
0220 horizontalAlignment: Text.AlignRight
0221 elide: Text.ElideRight
0222 opacity: 0.7
0223 Layout.maximumWidth: propertyDelegate.width - itemNameLabel.implicitWidth - Kirigami.Units.gridUnit * 6
0224 Layout.alignment: Qt.AlignVCenter
0225 QQC2.ToolTip {
0226 text: suggestedLabel.text
0227 visible: hovered && suggestedLabel.truncated
0228 }
0229 }
0230
0231 KCM.ContextualHelpButton {
0232 Layout.rightMargin: Kirigami.Units.largeSpacing
0233 visible: model.description.length > 0
0234 toolTipText: model.description
0235 }
0236
0237 QQC2.ToolButton {
0238 icon.name: (model.enabled) ? "dialog-ok-apply" : "list-add"
0239 onClicked: addProperty();
0240 Layout.preferredWidth: implicitWidth
0241 Layout.leftMargin: -Kirigami.Units.smallSpacing
0242 Layout.rightMargin: -Kirigami.Units.smallSpacing
0243 Layout.alignment: Qt.AlignVCenter
0244 }
0245 }
0246
0247 onClicked: {
0248 addProperty();
0249 propertySheet.close();
0250 }
0251
0252 function addProperty() {
0253 model.enabled = true;
0254 if (model.suggested != null) {
0255 model.value = model.suggested;
0256 model.suggested = null;
0257 }
0258 }
0259 }
0260 }
0261
0262 onVisibleChanged: {
0263 searchField.text = "";
0264 if (visible) {
0265 searchField.forceActiveFocus();
0266 } else {
0267 overlayModel.onlySuggestions = false;
0268 }
0269 }
0270 }
0271
0272 function formatValue(value, type, options) {
0273 if (value == null) {
0274 return "";
0275 }
0276 switch (type) {
0277 case RuleItem.Boolean:
0278 return value ? i18n("Yes") : i18n("No");
0279 case RuleItem.Percentage:
0280 return i18n("%1 %", value);
0281 case RuleItem.Point:
0282 return i18nc("Coordinates (x, y)", "(%1, %2)", value.x, value.y);
0283 case RuleItem.Size:
0284 return i18nc("Size (width, height)", "(%1, %2)", value.width, value.height);
0285 case RuleItem.Option:
0286 return options.textOfValue(value);
0287 case RuleItem.NetTypes:
0288 var selectedValue = value.toString(2).length - 1;
0289 return options.textOfValue(selectedValue);
0290 case RuleItem.OptionList:
0291 return Array.from(value, item => options.textOfValue(item) ).join(", ");
0292 }
0293 return value;
0294 }
0295
0296 KSortFilterProxyModel {
0297 id: enabledRulesModel
0298 sourceModel: kcm.rulesModel
0299 filterRowCallback: (source_row, source_parent) => {
0300 var index = sourceModel.index(source_row, 0, source_parent);
0301 return sourceModel.data(index, RulesModel.EnabledRole);
0302 }
0303 }
0304
0305 KSortFilterProxyModel {
0306 id: overlayModel
0307 sourceModel: kcm.rulesModel
0308
0309 property bool onlySuggestions: false
0310 onOnlySuggestionsChanged: {
0311 invalidateFilter();
0312 }
0313
0314 filterString: searchField.text.trim().toLowerCase()
0315 filterRowCallback: (source_row, source_parent) => {
0316 var index = sourceModel.index(source_row, 0, source_parent);
0317
0318 var hasSuggestion = sourceModel.data(index, RulesModel.SuggestedValueRole) != null;
0319 var isOptional = sourceModel.data(index, RulesModel.SelectableRole);
0320 var isEnabled = sourceModel.data(index, RulesModel.EnabledRole);
0321
0322 var showItem = hasSuggestion || (!onlySuggestions && isOptional && !isEnabled);
0323
0324 if (!showItem) {
0325 return false;
0326 }
0327 if (filterString.length > 0) {
0328 return sourceModel.data(index, RulesModel.NameRole).toLowerCase().includes(filterString)
0329 }
0330 return true;
0331 }
0332 }
0333
0334 }