From 4684971b2ad65a04ca73020c99da3dceb9d3a9fb Mon Sep 17 00:00:00 2001 From: Anton Kharuzhy <publicantroids@gmail.com> Date: Wed, 10 Jul 2024 17:50:12 +0200 Subject: [PATCH] #44: sortable replacements, replacements listeners decoupled to avoid dead-locking --- .../contents/ui/config/TitleReplacements.qml | 177 ++++++++++++++---- package/contents/ui/main.qml | 72 ++++--- package/contents/ui/utils.js | 15 +- 3 files changed, 197 insertions(+), 67 deletions(-) diff --git a/package/contents/ui/config/TitleReplacements.qml b/package/contents/ui/config/TitleReplacements.qml index d1df1bc..777dcb0 100644 --- a/package/contents/ui/config/TitleReplacements.qml +++ b/package/contents/ui/config/TitleReplacements.qml @@ -26,12 +26,85 @@ KCM.SimpleKCM { Regex } + ListModel { + id: replacementsModel + + function updateModelFromConfig() { + clear(); + for (let i = 0; i < cfg_titleReplacementsPatterns.length; i++) { + append({ + "pattern": cfg_titleReplacementsPatterns[i], + "template": cfg_titleReplacementsTemplates[i], + "type": cfg_titleReplacementsTypes[i] + }); + } + } + + function updateConfigFromModel() { + Qt.callLater(_updateConfigFromModel); + } + + function _updateConfigFromModel() { + const length = count; + cfg_titleReplacementsPatterns.length = length; + cfg_titleReplacementsTemplates.length = length; + cfg_titleReplacementsTypes.length = length; + for (let i = 0; i < length; i++) { + const rowValue = get(i); + cfg_titleReplacementsPatterns[i] = rowValue.pattern; + cfg_titleReplacementsTemplates[i] = rowValue.template; + cfg_titleReplacementsTypes[i] = rowValue.type; + } + } + + function pushNewReplacement() { + append({ + "pattern": "", + "template": "", + "type": TitleReplacements.Type.String + }); + updateConfigFromModel(); + } + + function deleteReplacement(index) { + remove(index); + updateConfigFromModel(); + } + + function setType(index, type) { + set(index, { + "type": type + }); + updateConfigFromModel(); + } + + function setPattern(index, pattern) { + set(index, { + "pattern": pattern + }); + updateConfigFromModel(); + } + + function setTemplate(index, template) { + set(index, { + "template": template + }); + updateConfigFromModel(); + } + + function moveReplacement(from, to) { + move(from, to, 1); + updateConfigFromModel(); + } + + Component.onCompleted: updateModelFromConfig() + } + ColumnLayout { RowLayout { - visible: replacementsRepeater.count > 5 Button { text: i18n("Add Title Replacement") - onClicked: pushNewReplacement() + onClicked: replacementsModel.pushNewReplacement() } Label { text: i18n("<a href=\"https://www.w3schools.com/jsref/jsref_obj_regexp.asp\">JavaScript RegExp Reference</a>") @@ -44,34 +117,72 @@ KCM.SimpleKCM { Repeater { id: replacementsRepeater - function updateModel() { - model = cfg_titleReplacementsPatterns.length; - } - - Component.onCompleted: updateModel() - + model: replacementsModel delegate: RowLayout { id: replacement required property int index + required property var modelData + + Drag.source: dragArea + Drag.active: dragArea.drag.active + Drag.hotSpot.y: height / 2 + + Kirigami.Icon { + Layout.maximumWidth: Kirigami.Units.gridUnit + + source: "transform-move-vertical" + MouseArea { + id: dragArea + anchors.fill: parent + drag.axis: Drag.YAxis + drag.target: replacement + cursorShape: Qt.DragMoveCursor + + drag { + onActiveChanged: function () { + if (!drag.active) { + replacementsModel.updateModelFromConfig(); + } + } + } + + states: State { + when: dragArea.drag.active + + PropertyChanges { + target: replacement + z: 1 + } + } + + DropArea { + anchors.fill: parent + onEntered: drag => { + replacementsModel.moveReplacement(drag.source.DelegateModel.itemsIndex, dragArea.DelegateModel.itemsIndex); + } + } + } + } ComboBox { id: titleReplacementsType + Layout.maximumWidth: Kirigami.Units.gridUnit * 5 model: [i18n("String"), i18n("RegExp")] onActivated: function () { - cfg_titleReplacementsTypes[replacement.index] = currentIndex; + replacementsModel.setType(index, currentIndex); } - Component.onCompleted: currentIndex = cfg_titleReplacementsTypes[replacement.index] + currentIndex: modelData.type } TextField { id: titleReplacementsPattern onTextEdited: function () { - cfg_titleReplacementsPatterns[replacement.index] = text; + replacementsModel.setPattern(index, text); } Layout.alignment: Qt.AlignLeft - Component.onCompleted: text = cfg_titleReplacementsPatterns[replacement.index] + text: modelData.pattern } Label { @@ -82,24 +193,25 @@ KCM.SimpleKCM { id: titleReplacementsTemplate onTextEdited: function () { - cfg_titleReplacementsTemplates[replacement.index] = text; + replacementsModel.setTemplate(index, text); } Layout.alignment: Qt.AlignLeft - Component.onCompleted: text = cfg_titleReplacementsTemplates[replacement.index] + text: modelData.template } Button { icon.name: "delete" onClicked: function () { - deleteReplacement(replacement.index); + replacementsModel.deleteReplacement(replacement.index); } } } } RowLayout { + visible: replacementsRepeater.count > 5 Button { text: i18n("Add Title Replacement") - onClicked: pushNewReplacement() + onClicked: replacementsModel.pushNewReplacement() } Label { text: i18n("<a href=\"https://www.w3schools.com/jsref/jsref_obj_regexp.asp\">JavaScript RegExp Reference</a>") @@ -143,41 +255,32 @@ KCM.SimpleKCM { } Connections { - target: page + target: replacementsModel - function onCfg_titleReplacementsTypesChanged() { + function onDataChanged() { testOutput.updateTestOutput(); } - function onCfg_titleReplacementsPatternsChanged() { + function onRowsRemoved() { testOutput.updateTestOutput(); } + } - function onCfg_titleReplacementsTemplatesChanged() { - testOutput.updateTestOutput(); + function _updateTestOutput() { + let outputText = testInput.text; + for (let i = 0; i < replacementsModel.count; i++) { + const rowValue = replacementsModel.get(i); + const replacement = Utils.Replacement.createReplacement(rowValue.type, rowValue.pattern, rowValue.template); + outputText = replacement.replace(outputText); } + testOutput.text = outputText; } function updateTestOutput() { - const replacements = Utils.Replacement.createReplacementList(cfg_titleReplacementsTypes, cfg_titleReplacementsPatterns, cfg_titleReplacementsTemplates); - testOutput.text = Utils.Replacement.applyReplacementList(testInput.text, replacements); + Qt.callLater(_updateTestOutput); } Component.onCompleted: updateTestOutput() } } - - function pushNewReplacement() { - cfg_titleReplacementsTypes.push(TitleReplacements.Type.String); - cfg_titleReplacementsTemplates.push(""); - cfg_titleReplacementsPatterns.push(""); - replacementsRepeater.updateModel(); - } - - function deleteReplacement(index) { - cfg_titleReplacementsPatterns.splice(index, 1); - cfg_titleReplacementsTypes.splice(index, 1); - cfg_titleReplacementsTemplates.splice(index, 1); - replacementsRepeater.updateModel(); - } } diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml index 1942651..afde222 100644 --- a/package/contents/ui/main.qml +++ b/package/contents/ui/main.qml @@ -183,32 +183,10 @@ PlasmoidItem { property bool empty: text === undefined || text === "" property bool hideEmpty: empty && plasmoid.configuration.windowTitleHideEmpty property int windowTitleSource: plasmoid.configuration.overrideElementsMaximized && tasksModel.activeWindow.maximized ? plasmoid.configuration.windowTitleSourceMaximized : plasmoid.configuration.windowTitleSource - property var titleTextReplacements: Utils.Replacement.createReplacementList(plasmoid.configuration.titleReplacementsTypes, plasmoid.configuration.titleReplacementsPatterns, plasmoid.configuration.titleReplacementsTemplates) - - function titleText(windowTitleSource) { - let titleTextResult = ""; - switch (windowTitleSource) { - case 0: - titleTextResult = tasksModel.activeWindow.appName; - break; - case 1: - titleTextResult = tasksModel.activeWindow.decoration; - break; - case 2: - titleTextResult = tasksModel.activeWindow.genericAppName; - break; - case 3: - titleTextResult = plasmoid.configuration.windowTitleUndefined; - break; - } - if (titleTextResult) { - titleTextResult = Utils.Replacement.applyReplacementList(titleTextResult, titleTextReplacements); - } - return titleTextResult; - } + property var titleTextReplacements: [] Layout.leftMargin: !hideEmpty ? plasmoid.configuration.windowTitleMarginsLeft : 0 - Layout.topMargin: !hideEmpty ? plasmoid.configuration.windowTitleMarginsTop : 0 + Layout.topMargin: !hideEmpty ? plasmoid.configuration.windowTitleMarginsTotitleReplacementsTypesp : 0 Layout.bottomMargin: !hideEmpty ? plasmoid.configuration.windowTitleMarginsBottom : 0 Layout.rightMargin: !hideEmpty ? plasmoid.configuration.windowTitleMarginsRight : 0 Layout.minimumWidth: plasmoid.configuration.windowTitleMinimumWidth @@ -224,6 +202,22 @@ PlasmoidItem { wrapMode: Text.WrapAnywhere enabled: tasksModel.hasActiveWindow + Connections { + target: plasmoid.configuration + + function onTitleReplacementsTypesChanged() { + updateTitleTextReplacements(); + } + + function onTitleReplacementsPatternsChanged() { + updateTitleTextReplacements(); + } + + function onTitleReplacementsTemplatesChanged() { + updateTitleTextReplacements(); + } + } + WidgetDragHandler { Component.onCompleted: { invokeKWinShortcut.connect(root.invokeKWinShortcut); @@ -249,6 +243,36 @@ PlasmoidItem { invokeKWinShortcut.connect(root.invokeKWinShortcut); } } + + function titleText(windowTitleSource) { + let titleTextResult = ""; + switch (windowTitleSource) { + case 0: + titleTextResult = tasksModel.activeWindow.appName; + break; + case 1: + titleTextResult = tasksModel.activeWindow.decoration; + break; + case 2: + titleTextResult = tasksModel.activeWindow.genericAppName; + break; + case 3: + titleTextResult = plasmoid.configuration.windowTitleUndefined; + break; + } + if (titleTextResult) { + titleTextResult = Utils.Replacement.applyReplacementList(titleTextResult, titleTextReplacements); + } + return titleTextResult; + } + + function updateTitleTextReplacements() { + Qt.callLater(_updateTitleTextReplacements); + } + + function _updateTitleTextReplacements() { + titleTextReplacements = Utils.Replacement.createReplacementList(plasmoid.configuration.titleReplacementsTypes, plasmoid.configuration.titleReplacementsPatterns, plasmoid.configuration.titleReplacementsTemplates); + } } } diff --git a/package/contents/ui/utils.js b/package/contents/ui/utils.js index 9387f88..b5abe44 100644 --- a/package/contents/ui/utils.js +++ b/package/contents/ui/utils.js @@ -120,13 +120,16 @@ class Replacement { static createReplacementList(types, patterns, templates) { const length = types.length; - if (length !== patterns.length || length !== templates.length) { - return []; - } - - let result = new Array(length); + let result = new Array(); for (let i = 0; i < length; i++) { - result[i] = Replacement.createReplacement(types[i], patterns[i], templates[i]); + const type = types[i]; + const pattern = patterns[i]; + const template = templates[i]; + if (type === undefined || pattern === undefined || template === undefined) { + break; // Inconsistent state + } else { + result.push(Replacement.createReplacement(type, pattern, template)); + } } return result; }