diff --git a/Modules/ControlCenter/Widgets/CustomButton.qml b/Modules/ControlCenter/Widgets/CustomButton.qml index 2a8adafc..b36c6ae6 100644 --- a/Modules/ControlCenter/Widgets/CustomButton.qml +++ b/Modules/ControlCenter/Widgets/CustomButton.qml @@ -13,24 +13,22 @@ Item { property string widgetId: "CustomButton" property var widgetSettings: null - property string onClickedCommand: "" property string onRightClickedCommand: "" property string onMiddleClickedCommand: "" - property string initialIcon: "heart" - property string onStateIcon: "heart" - property string onStateCommand: "" + property string stateChecksJson: "[]" // Store state checks as JSON string + property string generalTooltipText: "Custom Button" property bool enableOnStateLogic: false - - property string _currentIcon: initialIcon + property string _currentIcon: "heart" // Default icon property bool _isHot: false + property var _parsedStateChecks: [] // Local array for parsed state checks Connections { target: root - function _updatePropertiesFromSettings() { + function _updatePropertiesFromSettings() { if (!widgetSettings) { return } @@ -38,16 +36,19 @@ Item { onClickedCommand = widgetSettings.onClicked || "" onRightClickedCommand = widgetSettings.onRightClicked || "" onMiddleClickedCommand = widgetSettings.onMiddleClicked || "" - initialIcon = (widgetSettings.icon && widgetSettings.icon !== "") ? widgetSettings.icon : "heart" - onStateIcon = (widgetSettings.onStateIcon && widgetSettings.onStateIcon !== "") ? widgetSettings.onStateIcon : "heart" - onStateCommand = widgetSettings.onStateCommand || "" + stateChecksJson = widgetSettings.stateChecksJson || "[]" // Populate from widgetSettings + try { + _parsedStateChecks = JSON.parse(stateChecksJson) + } catch (e) { + console.error("CustomButton: Failed to parse stateChecksJson:", e.message) + _parsedStateChecks = [] + } generalTooltipText = widgetSettings.generalTooltipText || "Custom Button" enableOnStateLogic = widgetSettings.enableOnStateLogic || false updateState() } - function onWidgetSettingsChanged() { if (widgetSettings) { _updatePropertiesFromSettings() @@ -55,23 +56,24 @@ Item { } } - Process { - id: onStateCheckProcess + property int _currentStateCheckIndex: -1 + property string _activeStateIcon: "" + Process { + id: stateCheckProcessExecutor running: false - command: ["sh", "-c", onStateCommand] + command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : [] onExited: function(exitCode, stdout, stderr) { - if (enableOnStateLogic && onStateCommand) { - if (exitCode === 0) { - _isHot = true - _currentIcon = onStateIcon - } else { - _isHot = false - _currentIcon = initialIcon - } + var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex] + var currentCommand = currentCheckItem.command + if (exitCode === 0) { + // Command succeeded, this is the active state + _isHot = true + _activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart" } else { - _isHot = false - _currentIcon = initialIcon + // Command failed, try next one + _currentStateCheckIndex++ + _checkNextState() } } } @@ -82,62 +84,80 @@ Item { running: false repeat: false onTriggered: { - if (enableOnStateLogic && onStateCommand && !onStateCheckProcess.running) { - onStateCheckProcess.running = true + if (enableOnStateLogic && _parsedStateChecks.length > 0) { + _currentStateCheckIndex = 0 + _checkNextState() + } else { + _isHot = false + _activeStateIcon = widgetSettings.icon || "heart" } } } + function _checkNextState() { + if (_currentStateCheckIndex < _parsedStateChecks.length) { + var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex] + if (currentCheckItem && currentCheckItem.command) { + stateCheckProcessExecutor.running = true + } else { + _currentStateCheckIndex++ + _checkNextState() + } + } else { + // All checks failed + _isHot = false + _activeStateIcon = widgetSettings.icon || "heart" + } + } + function updateState() { - if (!enableOnStateLogic || !onStateCommand) { + if (!enableOnStateLogic || _parsedStateChecks.length === 0) { _isHot = false; - _currentIcon = initialIcon; + _activeStateIcon = widgetSettings.icon || "heart"; return; } stateUpdateTimer.restart(); - } - - - function _buildTooltipText() { - let tooltip = generalTooltipText - if (onClickedCommand) { - tooltip += `\nLeft click: ${onClickedCommand}` - } - if (onRightClickedCommand) { - tooltip += `\nRight click: ${onRightClickedCommand}` - } - if (onMiddleClickedCommand) { - tooltip += `\nMiddle click: ${onMiddleClickedCommand}` - } - - return tooltip - } - - implicitWidth: button.implicitWidth - implicitHeight: button.implicitHeight - - NIconButtonHot { - id: button - icon: _currentIcon - hot: _isHot - tooltipText: _buildTooltipText() - onClicked: { + } + function _buildTooltipText() { + let tooltip = generalTooltipText if (onClickedCommand) { - Quickshell.execDetached(["sh", "-c", onClickedCommand]) - updateState() + tooltip += `\nLeft click: ${onClickedCommand}` } - } - onRightClicked: { if (onRightClickedCommand) { - Quickshell.execDetached(["sh", "-c", onRightClickedCommand]) - updateState() + tooltip += `\nRight click: ${onRightClickedCommand}` } - } - onMiddleClicked: { if (onMiddleClickedCommand) { - Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]) - updateState() + tooltip += `\nMiddle click: ${onMiddleClickedCommand}` + } + + return tooltip + } + + implicitWidth: button.implicitWidth + implicitHeight: button.implicitHeight + + NIconButtonHot { + id: button + icon: _activeStateIcon + hot: _isHot + tooltipText: _buildTooltipText() + onClicked: { + if (onClickedCommand) { + Quickshell.execDetached(["sh", "-c", onClickedCommand]) + updateState() + } + } + onRightClicked: { + if (onRightClickedCommand) { + Quickshell.execDetached(["sh", "-c", onRightClickedCommand]) + updateState() + } + } + onMiddleClicked: { + if (onMiddleClickedCommand) { + Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]) + updateState() + } } } - } } diff --git a/Modules/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml b/Modules/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml index 13c6aaac..6d71a674 100644 --- a/Modules/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml +++ b/Modules/Settings/ControlCenter/WidgetSettings/CustomButtonSettings.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Layouts +import QtQml.Models // Import ListModel import qs.Commons import qs.Widgets @@ -13,35 +14,58 @@ ColumnLayout { id: _settings property string icon: (widgetData && widgetData.icon !== undefined) ? widgetData.icon : widgetMetadata.icon - property string onStateIcon: (widgetData && widgetData.onStateIcon !== undefined) ? widgetData.onStateIcon : widgetMetadata.onStateIcon property string onClicked: (widgetData && widgetData.onClicked !== undefined) ? widgetData.onClicked : widgetMetadata.onClicked property string onRightClicked: (widgetData && widgetData.onRightClicked !== undefined) ? widgetData.onRightClicked : widgetMetadata.onRightClicked property string onMiddleClicked: (widgetData && widgetData.onMiddleClicked !== undefined) ? widgetData.onMiddleClicked : widgetMetadata.onMiddleClicked - property string onStateCommand: (widgetData && widgetData.onStateCommand !== undefined) ? widgetData.onStateCommand : widgetMetadata.onStateCommand + property ListModel _stateChecksListModel: ListModel {} + property string stateChecksJson: "[]" property string generalTooltipText: (widgetData && widgetData.generalTooltipText !== undefined) ? widgetData.generalTooltipText : widgetMetadata.generalTooltipText property bool enableOnStateLogic: (widgetData && widgetData.enableOnStateLogic !== undefined) ? widgetData.enableOnStateLogic : widgetMetadata.enableOnStateLogic - + Component.onCompleted: { + stateChecksJson = (widgetData && widgetData.stateChecksJson !== undefined) ? widgetData.stateChecksJson : widgetMetadata.stateChecksJson || "[]" + try { + var initialChecks = JSON.parse(stateChecksJson) + if (initialChecks && Array.isArray(initialChecks)) { + for (var i = 0; i < initialChecks.length; i++) { + var item = initialChecks[i] + if (item && typeof item === "object") { + _settings._stateChecksListModel.append({ + command: item.command || "", + icon: item.icon || "" + }) + } else { + console.warn("⚠️ Invalid stateChecks entry at index " + i + ":", item) + } + } + } + } catch (e) { + console.error("CustomButtonSettings: Failed to parse stateChecksJson:", e.message) + } + } } function saveSettings() { - var saved = { + var savedStateChecksArray = [] + for (var i = 0; i < _settings._stateChecksListModel.count; i++) { + savedStateChecksArray.push(_settings._stateChecksListModel.get(i)) + } + _settings.stateChecksJson = JSON.stringify(savedStateChecksArray) + + return { id: widgetData.id, icon: _settings.icon, - onStateIcon: _settings.onStateIcon, onClicked: _settings.onClicked, onRightClicked: _settings.onRightClicked, onMiddleClicked: _settings.onMiddleClicked, - onStateCommand: _settings.onStateCommand, + stateChecksJson: _settings.stateChecksJson, generalTooltipText: _settings.generalTooltipText, enableOnStateLogic: _settings.enableOnStateLogic } - - return saved } RowLayout { - spacing: Style.marginM + spacing: Style?.marginM ?? 8 NLabel { label: I18n.tr("settings.control-center.shortcuts.custom-button.icon.label") @@ -51,7 +75,7 @@ ColumnLayout { NIcon { Layout.alignment: Qt.AlignVCenter icon: _settings.icon || widgetMetadata.icon - pointSize: Style.fontSizeXL + pointSize: Style?.fontSizeXL ?? 24 visible: (_settings.icon || widgetMetadata.icon) !== "" } @@ -70,7 +94,6 @@ ColumnLayout { } NTextInput { - id: generalTooltipTextInput Layout.fillWidth: true label: I18n.tr("settings.control-center.shortcuts.custom-button.general-tooltip-text.label") description: I18n.tr("settings.control-center.shortcuts.custom-button.general-tooltip-text.description") @@ -80,7 +103,6 @@ ColumnLayout { } NTextInput { - id: onClickedCommandInput Layout.fillWidth: true label: I18n.tr("settings.control-center.shortcuts.custom-button.on-clicked.label") description: I18n.tr("settings.control-center.shortcuts.custom-button.on-clicked.description") @@ -90,7 +112,6 @@ ColumnLayout { } NTextInput { - id: onRightClickedCommandInput Layout.fillWidth: true label: I18n.tr("settings.control-center.shortcuts.custom-button.on-right-clicked.label") description: I18n.tr("settings.control-center.shortcuts.custom-button.on-right-clicked.description") @@ -100,7 +121,6 @@ ColumnLayout { } NTextInput { - id: onMiddleClickedCommandInput Layout.fillWidth: true label: I18n.tr("settings.control-center.shortcuts.custom-button.on-middle-clicked.label") description: I18n.tr("settings.control-center.shortcuts.custom-button.on-middle-clicked.description") @@ -120,48 +140,69 @@ ColumnLayout { onToggled: checked => _settings.enableOnStateLogic = checked } - // On-State Icon - RowLayout { + ColumnLayout { Layout.fillWidth: true - spacing: Style.marginS visible: _settings.enableOnStateLogic + spacing: (Style?.marginM ?? 8) * 2 NLabel { - label: I18n.tr("settings.control-center.shortcuts.custom-button.on-state-icon.label") - description: I18n.tr("settings.control-center.shortcuts.custom-button.on-state-icon.description") + label: "State Checks" } - NIcon { - Layout.alignment: Qt.AlignVCenter - icon: _settings.onStateIcon || widgetMetadata.onStateIcon - pointSize: Style.fontSizeXL - visible: (_settings.onStateIcon || widgetMetadata.onStateIcon) !== "" + Repeater { + model: _settings._stateChecksListModel + delegate: ColumnLayout { + Layout.fillWidth: true + spacing: Style?.marginM ?? 8 + property int currentIndex: index + + RowLayout { + Layout.fillWidth: true + spacing: Style?.marginS ?? 4 + + NTextInput { + Layout.fillWidth: true + label: "Command" + text: model.command + onEditingFinished: _settings._stateChecksListModel.set(currentIndex, { "command": text, "icon": model.icon }) + } + + NIcon { + Layout.alignment: Qt.AlignVCenter + icon: model.icon + visible: model.icon !== undefined && model.icon !== "" + } + + NButton { + text: "Browse Icon" + Layout.preferredWidth: Style?.buttonWidthM ?? 100 + onClicked: iconPickerDelegate.open() + } + + NIconPicker { + id: iconPickerDelegate + initialIcon: model.icon + onIconSelected: function (iconName) { + _settings._stateChecksListModel.set(currentIndex, { "command": model.command, "icon": iconName }) + } + } + + NButton { + text: "Remove" + Layout.preferredWidth: Style?.buttonWidthM ?? 100 + onClicked: _settings._stateChecksListModel.remove(currentIndex) + } + } + + NDivider {} + } } NButton { - Layout.fillWidth: true - text: I18n.tr("settings.control-center.shortcuts.custom-button.browse") - onClicked: onStateIconPicker.open() + text: "Add State Check" + onClicked: _settings._stateChecksListModel.append({ command: "", icon: "" }) } } - NIconPicker { - id: onStateIconPicker - initialIcon: _settings.onStateIcon - onIconSelected: function (iconName) { - _settings.onStateIcon = iconName - } - } - - NTextInput { - id: onStateCommandInput - Layout.fillWidth: true - label: I18n.tr("settings.control-center.shortcuts.custom-button.on-state-command.label") - description: I18n.tr("settings.control-center.shortcuts.custom-button.on-state-command.description") - placeholderText: I18n.tr("placeholders.enter-command") - text: _settings.onStateCommand - onTextChanged: _settings.onStateCommand = text - enabled: _settings.enableOnStateLogic - visible: _settings.enableOnStateLogic - } -} + NDivider {} +} \ No newline at end of file diff --git a/Modules/Settings/Tabs/ControlCenterTab.qml b/Modules/Settings/Tabs/ControlCenterTab.qml index 69110393..298a2946 100644 --- a/Modules/Settings/Tabs/ControlCenterTab.qml +++ b/Modules/Settings/Tabs/ControlCenterTab.qml @@ -329,8 +329,12 @@ ColumnLayout { } function _updateWidgetSettingsInSection(section, index, settings) { - // Update the widget settings in the Settings data - Settings.data.controlCenter.shortcuts[section][index] = settings + // Create a new array to trigger QML's change detection for persistence. + // This is crucial for Settings.data to detect the change and persist it. + var newSectionArray = Settings.data.controlCenter.shortcuts[section].slice() + newSectionArray[index] = settings + Settings.data.controlCenter.shortcuts[section] = newSectionArray + Settings.saveImmediate() } // Base list model for all combo boxes diff --git a/Services/ControlCenterWidgetRegistry.qml b/Services/ControlCenterWidgetRegistry.qml index d460fdfa..5a1431f1 100644 --- a/Services/ControlCenterWidgetRegistry.qml +++ b/Services/ControlCenterWidgetRegistry.qml @@ -25,11 +25,10 @@ Singleton { "CustomButton": { "allowUserSettings": true, "icon": "heart", - "onStateIcon": "heart", "onClicked": "", "onRightClicked": "", "onMiddleClicked": "", - "onStateCommand": "", + "stateChecks": [], "generalTooltipText": "Custom Button", "enableOnStateLogic": false }