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 }