/*SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>SPDX-FileCopyrightText: 2022-2023 ivan tkachenko <me@ratijas.tk>SPDX-License-Identifier: GPL-2.0-or-later*/import QtQuickimport QtQuick.Controls as QQC2import QtQuick.Layoutsimport org.kde.kcmutils as KCMUtilsimport org.kde.kirigami as Kirigamiimport org.kde.kitemmodels as KItemModelsimport org.kde.plasma.configurationimport org.kde.plasma.plasmoidRectangle {id: rootimplicitWidth: Kirigami.Units.gridUnit * 45implicitHeight: Kirigami.Units.gridUnit * 35Layout.minimumWidth: Kirigami.Units.gridUnit * 30Layout.minimumHeight: Kirigami.Units.gridUnit * 21LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeftLayoutMirroring.childrenInherit: truecolor: Kirigami.Theme.backgroundColorproperty bool isContainment: falseproperty ConfigModel globalConfigModel: globalAppletConfigModelproperty url currentSourceproperty bool wasConfigurationChangedSignalSent: falsefunction closing() {if (applyButton.enabled) {messageDialog.item = null;messageDialog.open();return false;}return true;}function saveConfig() {const config = Plasmoid.configuration; // type: KConfigPropertyMap// call applet's own config handling first so it can set cfg_ properties if neededif (app.pageStack.currentItem.saveConfig) {app.pageStack.currentItem.saveConfig()}config.keys().forEach(key => {const cfgKey = "cfg_" + key;if (cfgKey in app.pageStack.currentItem) {config[key] = app.pageStack.currentItem[cfgKey];}})plasmoid.configuration.writeConfig();}function isConfigurationChanged() {const config = Plasmoid.configuration;return config.keys().some(key => {const cfgKey = "cfg_" + keyif (!app.pageStack.currentItem.hasOwnProperty(cfgKey))return falsereturn config[key] != app.pageStack.currentItem[cfgKey] &&config[key].toString() != app.pageStack.currentItem[cfgKey].toString()})}Connections {target: configDialogfunction onClosing(event) {event.accepted = closing();}}ConfigModel {id: globalAppletConfigModelConfigCategory {name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for configuration dialog page", "Keyboard Shortcuts")icon: "preferences-desktop-keyboard"source: Qt.resolvedUrl("ConfigurationShortcuts.qml")}}KItemModels.KSortFilterProxyModel {id: configDialogFilterModelsourceModel: configDialog.configModelfilterRowCallback: (row, parent) => {return sourceModel.data(sourceModel.index(row, 0), ConfigModel.VisibleRole);}}function settingValueChanged() {applyButton.enabled = wasConfigurationChangedSignalSent || isConfigurationChanged() || (app?.pageStack?.currentItem?.unsavedChanges ?? false);}function pushReplace(item, config) {let page;if (app.pageStack.depth === 0) {page = app.pageStack.push(item, config);} else {page = app.pageStack.replace(item, config);}app.currentConfigPage = page;}function open(item) {app.isAboutPage = false;root.currentSource = item.sourceif (item.configUiModule && item.configUiComponent) {root.currentSource = item.configUiModule + item.configUiComponent; // Just for the highlight statusconst config = Plasmoid.configuration; // type: KConfigPropertyMapconst props = {"title": item.name,};config.keys().forEach(key => {props["cfg_" + key] = config[key];});pushReplace(Qt.createComponent(item.configUiModule, item.configUiComponent), props);} else if (item.source) {app.isAboutPage = item.source === Qt.resolvedUrl("AboutPlugin.qml");const config = Plasmoid.configuration; // type: KConfigPropertyMapconst props = { "title": item.name };config.keys().forEach(key => {props["cfg_" + key] = config[key];});pushReplace(Qt.resolvedUrl(item.source), props);} else {app.pageStack.pop();}applyButton.enabled = false}Connections {target: app.currentConfigPageignoreUnknownSignals: true// This is an artifact of old internal architecture. If control beyond the automated// monitoring based on cfg_ properties is required, plasmoids should not emit// settingValueChanged() but configurationChanged() to force prompting the user// for saving changes, or use the unsavedChanges property to dynamically indicate// whether saving is needed in the current state).// We keep it around for now as third-party plasmoids might use it (even though they// really shouldn't as it's not documented).// TODO Plasma 7: remove and document in porting guide.function onSettingValueChanged() {wasConfigurationChangedSignalSent = true;}function onUnsavedChangesChanged() {root.settingValueChanged()}}Connections {target: app.pageStackfunction onCurrentItemChanged() {if (app.pageStack.currentItem !== null) {const config = Plasmoid.configuration; // type: KConfigPropertyMapconfig.keys().forEach(key => {const changedSignal = app.pageStack.currentItem["cfg_" + key + "Changed"];if (changedSignal) {changedSignal.connect(() => root.settingValueChanged());}});const configurationChangedSignal = app.pageStack.currentItem.configurationChanged;if (configurationChangedSignal) {configurationChangedSignal.connect(() => {root.wasConfigurationChangedSignalSent = trueroot.settingValueChanged()});}const configurationUnsavedChangesSignal = app.pageStack.currentItem.unsavedChangesChangedif (configurationUnsavedChangesSignal) {configurationUnsavedChangesSignal.connect(() => root.settingValueChanged())}}}}Component.onCompleted: {// if we are a containment then the first item will be ConfigurationContainmentAppearance// if the applet does not have own configs then the first item will be Shortcutsif (isContainment || !configDialog.configModel || configDialog.configModel.count === 0) {open(root.globalConfigModel.get(0))} else {open(configDialog.configModel.get(0))}}function applicationWindow() {return app;}QQC2.ScrollView {id: categoriesScrollanchors {left: parent.lefttop: parent.topbottom: parent.bottom}width: Kirigami.Units.gridUnit * 7contentWidth: availableWidthKirigami.Theme.colorSet: Kirigami.Theme.ViewKirigami.Theme.inherit: falseactiveFocusOnTab: truefocus: trueAccessible.role: Accessible.PageTabListbackground: Rectangle {color: Kirigami.Theme.backgroundColor}Keys.onUpPressed: {const buttons = categories.childrenlet foundPrevious = falsefor (let i = buttons.length - 1; i >= 0; --i) {const button = buttons[i];if (!button.hasOwnProperty("highlighted")) {// not a ConfigCategoryDelegatecontinue;}if (foundPrevious) {categories.openCategory(button.item)categoriesScroll.forceActiveFocus(Qt.TabFocusReason)return} else if (button.highlighted) {foundPrevious = true}}event.accepted = false}Keys.onDownPressed: {const buttons = categories.childrenlet foundNext = falsefor (let i = 0, length = buttons.length; i < length; ++i) {const button = buttons[i];if (!button.hasOwnProperty("highlighted")) {continue;}if (foundNext) {categories.openCategory(button.item)categoriesScroll.forceActiveFocus(Qt.TabFocusReason)return} else if (button.highlighted) {foundNext = true}}event.accepted = false}ColumnLayout {id: categoriesspacing: 0width: categoriesScroll.contentWidthfocus: truefunction openCategory(item) {if (applyButton.enabled) {messageDialog.item = item;messageDialog.open();return;}open(item)}Component {id: categoryDelegateConfigCategoryDelegate {id: delegateonActivated: categories.openCategory(model);highlighted: {if (app.pageStack.currentItem) {if (model.configUiModule && model.configUiComponent) {return root.currentSource == (model.configUiModule + model.configUiComponent)} else {return root.currentSource == model.source}}return false}item: model}}Repeater {Layout.fillWidth: truemodel: root.isContainment ? globalConfigModel : undefineddelegate: categoryDelegate}Repeater {Layout.fillWidth: truemodel: configDialogFilterModeldelegate: categoryDelegate}Repeater {Layout.fillWidth: truemodel: !root.isContainment ? globalConfigModel : undefineddelegate: categoryDelegate}Repeater {Layout.fillWidth: truemodel: ConfigModel {ConfigCategory{name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for About dialog page", "About")icon: "help-about"source: Qt.resolvedUrl("AboutPlugin.qml")}}delegate: categoryDelegate}}}Kirigami.Separator {anchors {left: parent.leftright: parent.righttop: parent.top}z: 1}Kirigami.Separator {id: verticalSeparatoranchors {top: parent.topleft: categoriesScroll.rightbottom: parent.bottom}z: 1}Kirigami.ApplicationItem {id: appanchors {top: parent.topleft: verticalSeparator.rightright: parent.rightbottom: parent.bottom}pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.AutowideScreen: truepageStack.globalToolBar.separatorVisible: bottomSeparator.visiblepageStack.globalToolBar.colorSet: Kirigami.Theme.Windowproperty var currentConfigPage: nullproperty bool isAboutPage: falseKirigami.PromptDialog {id: messageDialogproperty var itemtitle: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window dialog title", "Apply Settings")subtitle: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label dialog body", "The current page has unsaved changes. Apply the changes or discard them?")standardButtons: Kirigami.Dialog.Apply | Kirigami.Dialog.Discard | Kirigami.Dialog.CancelonApplied: {applyAction.trigger()discarded();}onDiscarded: {wasConfigurationChangedSignalSent = false;if (item) {root.open(item);messageDialog.close();} else {applyButton.enabled = false;configDialog.close();}}}footer: QQC2.Pane {padding: Kirigami.Units.largeSpacingcontentItem: RowLayout {id: buttonsRowspacing: Kirigami.Units.smallSpacingItem {Layout.fillWidth: true}QQC2.Button {icon.name: "dialog-ok"text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "OK")onClicked: acceptAction.trigger()}QQC2.Button {id: applyButtonenabled: falseicon.name: "dialog-ok-apply"text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Apply")visible: !app.isAboutPage && app.pageStack.currentItemonClicked: applyAction.trigger()}QQC2.Button {icon.name: "dialog-cancel"text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Cancel")onClicked: cancelAction.trigger()visible: !app.isAboutPageKeyNavigation.tab: categories}}background: Item {Kirigami.Separator {id: bottomSeparatorvisible: (app.pageStack.currentItem&& app.pageStack.currentItem.flickable&& !(app.pageStack.currentItem instanceof KCMUtils.GridViewKCM)&& !(app.pageStack.currentItem.flickable.atYBeginning&& app.pageStack.currentItem.flickable.atYEnd)) ?? falseanchors {left: parent.leftright: parent.righttop: parent.top}}}}QQC2.Action {id: acceptActiononTriggered: {applyButton.enabled = false;configDialog.close();applyAction.trigger();}}QQC2.Action {id: applyActiononTriggered: {root.saveConfig()wasConfigurationChangedSignalSent = falseapplyButton.enabled = false;}}QQC2.Action {id: cancelActiononTriggered: {if (root.closing()) {configDialog.close();}}}Keys.onReturnPressed: acceptAction.trigger();Keys.onEscapePressed: cancelAction.trigger();}}