mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
shell: Rework configuration implementation
This reworks the implementation of the applet/containment configuration so that it is more optimized for the mobile experience and fixes lateral navigation (between categories). Changes: - Always show a list of category modules (switching from the navigation tab bar) in order to support more modules at once - Split the wallpaper and containment switching view into two modules - Add a close button at the top - Add an animation when the window opens and closes - Refactor the code so that it is clear which files are imported by the shell, and which are implementation details
This commit is contained in:
parent
c8479ca16d
commit
a39401100f
9 changed files with 330 additions and 343 deletions
|
|
@ -12,14 +12,18 @@ import org.kde.kirigami 2.19 as Kirigami
|
||||||
import org.kde.plasma.configuration 2.0
|
import org.kde.plasma.configuration 2.0
|
||||||
import org.kde.kitemmodels 1.0 as KItemModels
|
import org.kde.kitemmodels 1.0 as KItemModels
|
||||||
|
|
||||||
Rectangle {
|
import './private'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is loaded by libplasma when the "configuration window" is requested for an applet.
|
||||||
|
*/
|
||||||
|
Item {
|
||||||
id: root
|
id: root
|
||||||
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
|
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
//BEGIN properties
|
//BEGIN properties
|
||||||
|
// Properties filled in or needed by libplasma
|
||||||
|
|
||||||
property bool isContainment: false
|
property bool isContainment: false
|
||||||
property alias app: appLoader.item
|
property alias app: appLoader.item
|
||||||
|
|
@ -47,105 +51,46 @@ Rectangle {
|
||||||
|
|
||||||
//END model
|
//END model
|
||||||
|
|
||||||
//BEGIN functions
|
|
||||||
|
|
||||||
function saveConfig() {
|
|
||||||
if (app.pageStack.currentItem.saveConfig) {
|
|
||||||
app.pageStack.currentItem.saveConfig()
|
|
||||||
}
|
|
||||||
for (var key in Plasmoid.configuration) {
|
|
||||||
if (app.pageStack.currentItem["cfg_"+key] !== undefined) {
|
|
||||||
Plasmoid.configuration[key] = app.pageStack.currentItem["cfg_"+key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function configurationHasChanged() {
|
|
||||||
for (var key in Plasmoid.configuration) {
|
|
||||||
if (app.pageStack.currentItem["cfg_"+key] !== undefined) {
|
|
||||||
//for objects == doesn't work
|
|
||||||
if (typeof Plasmoid.configuration[key] == 'object') {
|
|
||||||
for (var i in Plasmoid.configuration[key]) {
|
|
||||||
if (Plasmoid.configuration[key][i] != app.pageStack.currentItem["cfg_"+key][i]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else if (app.pageStack.currentItem["cfg_"+key] != Plasmoid.configuration[key]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function settingValueChanged() {
|
|
||||||
if (app.pageStack.currentItem.saveConfig !== undefined) {
|
|
||||||
app.pageStack.currentItem.saveConfig();
|
|
||||||
} else {
|
|
||||||
root.saveConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
function open(item) {
|
||||||
app.isAboutPage = false;
|
|
||||||
if (item.source) {
|
if (item.source) {
|
||||||
app.isAboutPage = item.source === "AboutPlugin.qml";
|
app.pageStack.push(Qt.resolvedUrl("private/ConfigurationAppletPage.qml"), {configItem: item, title: item.name});
|
||||||
pushReplace(Qt.resolvedUrl("ConfigurationAppletPage.qml"), {configItem: item, title: item.name});
|
|
||||||
} else if (item.kcm) {
|
} else if (item.kcm) {
|
||||||
pushReplace(configurationKcmPageComponent, {kcm: item.kcm, internalPage: item.kcm.mainUi});
|
app.pageStack.push(configurationKcmPageComponent, {kcm: item.kcm, internalPage: item.kcm.mainUi});
|
||||||
} else {
|
|
||||||
app.pageStack.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//END functions
|
Binding {
|
||||||
|
// Window bindings
|
||||||
|
root.Window.window.flags: Qt.FramelessWindowHint
|
||||||
//BEGIN connections
|
root.Window.window.visibility: Window.Maximized
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.Window.window
|
|
||||||
function onVisibleChanged() {
|
|
||||||
if (root.Window.window.visible) {
|
|
||||||
root.Window.window.showMaximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//END connections
|
|
||||||
|
|
||||||
//BEGIN UI components
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: configurationKcmPageComponent
|
id: configurationKcmPageComponent
|
||||||
ConfigurationKcmPage {}
|
ConfigurationKcmPage {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: configListPageComponent
|
||||||
|
ConfigListPage {
|
||||||
|
onRequestOpen: (delegate) => root.open(delegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: appLoader
|
id: appLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
active: root.loadApp
|
active: root.loadApp
|
||||||
|
|
||||||
|
// Load first page
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
// if we are a containment then the first item will be ConfigurationContainmentAppearance
|
// Push config list page
|
||||||
// if the applet does not have own configs then the first item will be Shortcuts
|
app.pageStack.push(configListPageComponent, {
|
||||||
if (isContainment || !configDialog.configModel || configDialog.configModel.count === 0) {
|
title: i18nc("The title of the applet configuration window", "Configure %1", Plasmoid.metaData.name),
|
||||||
root.open(root.globalConfigModel.get(0))
|
model1: configDialogFilterModel,
|
||||||
} else {
|
model2: root.globalConfigModel
|
||||||
root.open(configDialog.configModel.get(0))
|
});
|
||||||
}
|
|
||||||
|
|
||||||
root.appLoaded();
|
root.appLoaded();
|
||||||
}
|
}
|
||||||
|
|
@ -154,90 +99,50 @@ Rectangle {
|
||||||
id: app
|
id: app
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
// animation on show
|
pageStack {
|
||||||
opacity: 0
|
globalToolBar {
|
||||||
NumberAnimation on opacity {
|
canContainHandles: true
|
||||||
to: 1
|
style: Kirigami.ApplicationHeaderStyle.ToolBar
|
||||||
running: true
|
showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton
|
||||||
duration: Kirigami.Units.longDuration
|
}
|
||||||
easing.type: Easing.InOutQuad
|
popHiddenPages: true
|
||||||
|
columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
pageStack.globalToolBar.canContainHandles: true
|
// Implement open/close animation
|
||||||
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
|
||||||
pageStack.globalToolBar.showNavigationButtons: Kirigami.ApplicationHeaderStyle.ShowBackButton;
|
|
||||||
|
|
||||||
property var currentConfigPage: null
|
|
||||||
property bool isAboutPage: false
|
|
||||||
|
|
||||||
// pop pages when not in use
|
|
||||||
Connections {
|
Connections {
|
||||||
target: app.pageStack
|
target: root.Window.window
|
||||||
function onCurrentIndexChanged() {
|
|
||||||
// wait for animation to finish before popping pages
|
|
||||||
timer.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
function onVisibleChanged() {
|
||||||
id: timer
|
if (visible) {
|
||||||
interval: 300
|
opacityAnim.to = 1;
|
||||||
onTriggered: {
|
opacityAnim.restart();
|
||||||
let currentIndex = app.pageStack.currentIndex;
|
}
|
||||||
while (app.pageStack.depth > (currentIndex + 1) && currentIndex >= 0) {
|
}
|
||||||
app.pageStack.pop();
|
|
||||||
|
function onClosing(close) {
|
||||||
|
if (app.opacity !== 0) {
|
||||||
|
close.accepted = false;
|
||||||
|
opacityAnim.to = 0;
|
||||||
|
opacityAnim.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: Kirigami.NavigationTabBar {
|
opacity: 0
|
||||||
id: footerBar
|
scale: 0.7 + 0.3 * app.opacity
|
||||||
visible: count > 1
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
Repeater {
|
|
||||||
model: root.isContainment ? globalConfigModel : undefined
|
|
||||||
delegate: configCategoryDelegate
|
|
||||||
}
|
|
||||||
Repeater {
|
|
||||||
model: configDialogFilterModel
|
|
||||||
delegate: configCategoryDelegate
|
|
||||||
}
|
|
||||||
Repeater {
|
|
||||||
model: !root.isContainment ? globalConfigModel : undefined
|
|
||||||
delegate: configCategoryDelegate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
NumberAnimation on opacity {
|
||||||
id: configCategoryDelegate
|
id: opacityAnim
|
||||||
Kirigami.NavigationTabButton {
|
duration: Kirigami.Units.longDuration
|
||||||
icon.name: model.icon
|
easing.type: Easing.OutCubic
|
||||||
text: model.name
|
onFinished: {
|
||||||
width: footerBar.buttonWidth
|
if (app.opacity === 0) {
|
||||||
QQC2.ButtonGroup.group: footerBar.tabGroup
|
root.Window.window.close();
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (checked) {
|
|
||||||
root.open(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checked: {
|
|
||||||
if (app.pageStack.currentItem) {
|
|
||||||
if (model.kcm && app.pageStack.currentItem.kcm) {
|
|
||||||
return model.kcm == app.pageStack.currentItem.kcm;
|
|
||||||
} else if (app.pageStack.currentItem.configItem) {
|
|
||||||
return model.source == app.pageStack.currentItem.configItem.source;
|
|
||||||
} else {
|
|
||||||
return app.pageStack.currentItem.source == Qt.resolvedUrl(model.source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//END UI components
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.0
|
|
||||||
|
|
||||||
import org.kde.plasma.plasmoid
|
|
||||||
import org.kde.kirigami 2.10 as Kirigami
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
title: configItem.name
|
|
||||||
|
|
||||||
required property var configItem
|
|
||||||
|
|
||||||
signal settingValueChanged()
|
|
||||||
onSettingValueChanged: saveConfig() // we save config immediately on mobile
|
|
||||||
|
|
||||||
function saveConfig() {
|
|
||||||
for (let key in Plasmoid.configuration) {
|
|
||||||
if (loader.item["cfg_" + key] != undefined) {
|
|
||||||
Plasmoid.configuration[key] = loader.item["cfg_" + key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For ConfigurationContainmentActions.qml
|
|
||||||
if (loader.item.hasOwnProperty("saveConfig")) {
|
|
||||||
loader.item.saveConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitHeight: loader.height
|
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
bottomPadding: 0
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: loader
|
|
||||||
width: parent.width
|
|
||||||
// HACK the height of the loader is based on the implicitHeight of the content.
|
|
||||||
// Unfortunately not all content items have a sensible implicitHeight.
|
|
||||||
// If it is zero fall back to the height of its children
|
|
||||||
// Also make it at least as high as the page itself. Some existing configs assume they fill the whole space
|
|
||||||
// TODO KF6 clean this up by making all configs based on SimpleKCM/ScrollViewKCM/GridViewKCM
|
|
||||||
height: {
|
|
||||||
if (item) {
|
|
||||||
return Math.max(root.availableHeight, item.implicitHeight ? item.implicitHeight : item.childrenRect.height);
|
|
||||||
} else {
|
|
||||||
return root.availableHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
const plasmoidConfig = Plasmoid.configuration
|
|
||||||
|
|
||||||
const props = {}
|
|
||||||
for (let key in plasmoidConfig) {
|
|
||||||
props["cfg_" + key] = Plasmoid.configuration[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
setSource(configItem.source, props)
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
const plasmoidConfig = Plasmoid.configuration;
|
|
||||||
|
|
||||||
for (let key in plasmoidConfig) {
|
|
||||||
const changedSignal = item["cfg_" + key + "Changed"]
|
|
||||||
if (changedSignal) {
|
|
||||||
changedSignal.connect(root.settingValueChanged)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const configurationChangedSignal = item.configurationChanged
|
|
||||||
if (configurationChangedSignal) {
|
|
||||||
configurationChangedSignal.connect(root.settingValueChanged)
|
|
||||||
}
|
|
||||||
|
|
||||||
var unsavedChangesChangedSignal = item.unsavedChangesChanged
|
|
||||||
if (unsavedChangesChangedSignal) {
|
|
||||||
unsavedChangesChangedSignal.connect( () => {
|
|
||||||
if (item.unsavedChanges) {
|
|
||||||
root.settingValueChanged()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,27 +13,29 @@ import org.kde.plasma.core as PlasmaCore
|
||||||
import org.kde.plasma.configuration 2.0
|
import org.kde.plasma.configuration 2.0
|
||||||
import org.kde.ksvg 1.0 as KSvg
|
import org.kde.ksvg 1.0 as KSvg
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is loaded by libplasma when the "configuration window" is requested for a containment.
|
||||||
|
*/
|
||||||
AppletConfiguration {
|
AppletConfiguration {
|
||||||
id: root
|
id: root
|
||||||
isContainment: true
|
isContainment: true
|
||||||
loadApp: true
|
loadApp: true
|
||||||
|
|
||||||
readonly property bool horizontal: root.width > root.height
|
|
||||||
|
|
||||||
onAppLoaded: {
|
|
||||||
app.width = root.width < root.height ? root.width : Math.min(root.width, Math.max(app.implicitWidth, Kirigami.Units.gridUnit * 45));
|
|
||||||
app.height = Math.min(root.height, Math.max(app.implicitHeight, Kirigami.Units.gridUnit * 29));
|
|
||||||
}
|
|
||||||
|
|
||||||
//BEGIN model
|
//BEGIN model
|
||||||
globalConfigModel: globalContainmentConfigModel
|
globalConfigModel: globalContainmentConfigModel
|
||||||
|
|
||||||
ConfigModel {
|
ConfigModel {
|
||||||
id: globalContainmentConfigModel
|
id: globalContainmentConfigModel
|
||||||
ConfigCategory {
|
ConfigCategory {
|
||||||
name: i18nd("plasma_shell_org.kde.plasma.desktop", "Wallpaper")
|
name: i18n("Wallpaper")
|
||||||
icon: "preferences-desktop-wallpaper"
|
icon: "viewimage-symbolic"
|
||||||
source: "ConfigurationContainmentAppearance.qml"
|
source: "ChangeWallpaperModule.qml" // This is a relative path from inside private (since loading is invoked from there)
|
||||||
|
}
|
||||||
|
ConfigCategory {
|
||||||
|
name: i18n("Change Homescreen")
|
||||||
|
icon: "exchange-positions"
|
||||||
|
source: "ChangeContainmentModule.qml" // This is a relative path from inside private (since loading is invoked from there)
|
||||||
|
visible: configDialog.containmentPluginsConfigModel.count > 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//END model
|
//END model
|
||||||
|
|
|
||||||
8
shell/contents/configuration/README.md
Normal file
8
shell/contents/configuration/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<!--
|
||||||
|
- SPDX-FileCopyrightText: None
|
||||||
|
- SPDX-License-Identifier: CC0-1.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
This folder contains source files for implementing the configuration window for applets/containments (including homescreens).
|
||||||
|
|
||||||
|
[libplasma](https://invent.kde.org/frameworks/libplasma) loads either `AppletConfiguration.qml` or `ContainmentConfiguration.qml` (depending on if its an applet or containment) from this folder when requested by the shell, which in turn initializes the config window.
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.plasma.configuration
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string containmentPlugin: configDialog.containmentPlugin
|
||||||
|
signal configurationChanged // No need to emit, because containment changes apply immediately
|
||||||
|
|
||||||
|
//BEGIN functions
|
||||||
|
function saveConfig() {
|
||||||
|
configDialog.containmentPlugin = root.containmentPlugin
|
||||||
|
}
|
||||||
|
//END functions
|
||||||
|
|
||||||
|
FormCard.FormHeader {
|
||||||
|
title: i18n("Select Homescreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
FormCard.FormCard {
|
||||||
|
Repeater {
|
||||||
|
model: configDialog.containmentPluginsConfigModel
|
||||||
|
delegate: FormCard.FormRadioDelegate {
|
||||||
|
enabled: !Plasmoid.immutable
|
||||||
|
text: model.name
|
||||||
|
checked: configDialog.containmentPlugin === model.pluginName
|
||||||
|
|
||||||
|
// Always restore binding
|
||||||
|
onCheckedChanged: checked = Qt.binding(() => configDialog.containmentPlugin === model.pluginName);
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (root.containmentPlugin === model.pluginName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.containmentPlugin = model.pluginName;
|
||||||
|
confirmationDialog.name = model.name;
|
||||||
|
confirmationDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.PromptDialog {
|
||||||
|
id: confirmationDialog
|
||||||
|
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||||
|
|
||||||
|
property string name
|
||||||
|
|
||||||
|
title: i18n("Change homescreen to %1?", name)
|
||||||
|
subtitle: i18n("Your current homescreen's settings are saved, and will be restored if you switch back.")
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
root.saveConfig();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
onRejected: {
|
||||||
|
root.containmentPlugin = configDialog.containmentPlugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
/*
|
// SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
|
// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
|
||||||
*
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import org.kde.plasma.configuration 2.0
|
import org.kde.plasma.configuration 2.0
|
||||||
|
|
@ -19,7 +17,6 @@ ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
property string currentWallpaper: ""
|
property string currentWallpaper: ""
|
||||||
property string containmentPlugin: configDialog.containmentPlugin
|
|
||||||
signal configurationChanged
|
signal configurationChanged
|
||||||
|
|
||||||
//BEGIN functions
|
//BEGIN functions
|
||||||
|
|
@ -34,59 +31,17 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
configDialog.currentWallpaper = root.currentWallpaper;
|
configDialog.currentWallpaper = root.currentWallpaper;
|
||||||
configDialog.applyWallpaper()
|
configDialog.applyWallpaper()
|
||||||
configDialog.containmentPlugin = root.containmentPlugin
|
|
||||||
}
|
}
|
||||||
//END functions
|
//END functions
|
||||||
|
|
||||||
Kirigami.InlineMessage {
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
visible: Plasmoid.immutable || animating
|
|
||||||
text: i18nd("plasma_shell_org.kde.plasma.desktop", "Layout changes have been restricted by the system administrator")
|
|
||||||
showCloseButton: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2 // we need this because ColumnLayout's spacing is 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: generalConfig
|
id: generalConfig
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
FormCard.FormHeader {
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
title: i18n("General")
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormComboBoxDelegate {
|
|
||||||
id: layoutSelectComboBox
|
|
||||||
enabled: !Plasmoid.immutable
|
|
||||||
text: i18nd("plasma_shell_org.kde.plasma.desktop", "Homescreen Layout")
|
|
||||||
description: i18n("The homescreen layout to use.")
|
|
||||||
visible: model.count > 1 // only show if there are multiple plugins
|
|
||||||
|
|
||||||
model: configDialog.containmentPluginsConfigModel
|
|
||||||
textRole: "name"
|
|
||||||
valueRole: "pluginName"
|
|
||||||
currentIndex: determineCurrentIndex()
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
root.containmentPlugin = configDialog.containmentPluginsConfigModel.get(currentIndex).pluginName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function determineCurrentIndex() {
|
|
||||||
for (var i = 0; i < configDialog.containmentPluginsConfigModel.count; ++i) {
|
|
||||||
var data = configDialog.containmentPluginsConfigModel.get(i);
|
|
||||||
if (configDialog.containmentPlugin === data.pluginName) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormDelegateSeparator { above: layoutSelectComboBox; below: wallpaperPluginSelectComboBox }
|
|
||||||
|
|
||||||
FormCard.FormComboBoxDelegate {
|
FormCard.FormComboBoxDelegate {
|
||||||
id: wallpaperPluginSelectComboBox
|
id: wallpaperPluginSelectComboBox
|
||||||
|
|
@ -138,30 +93,6 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: switchContainmentWarning
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: configDialog.containmentPlugin !== root.containmentPlugin
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: i18nd("plasma_shell_org.kde.plasma.desktop", "Layout changes must be applied before other changes can be made")
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
text: i18nd("plasma_shell_org.kde.plasma.desktop", "Apply now")
|
|
||||||
onClicked: saveConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
Layout.fillHeight: switchContainmentWarning.visible
|
|
||||||
visible: switchContainmentWarning.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: emptyConfig
|
id: emptyConfig
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
@ -172,11 +103,9 @@ ColumnLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.maximumHeight: root.height - generalConfig.height - 70 // HACK: wallpaper configs seem to go over the provisioned height
|
Layout.maximumHeight: root.height - generalConfig.height - Kirigami.Units.smallSpacing // HACK: wallpaper configs seem to go over the provisioned height
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
visible: !switchContainmentWarning.visible
|
|
||||||
|
|
||||||
// Bug 360862: if wallpaper has no config, sourceFile will be ""
|
// Bug 360862: if wallpaper has no config, sourceFile will be ""
|
||||||
// so we wouldn't load emptyConfig and break all over the place
|
// so we wouldn't load emptyConfig and break all over the place
|
||||||
// hence set it to some random value initially
|
// hence set it to some random value initially
|
||||||
77
shell/contents/configuration/private/ConfigListPage.qml
Normal file
77
shell/contents/configuration/private/ConfigListPage.qml
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.configuration
|
||||||
|
import org.kde.kitemmodels as KItemModels
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
property alias model1: repeater1.model
|
||||||
|
property alias model2: repeater2.model
|
||||||
|
|
||||||
|
topPadding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
titleDelegate: RowLayout {
|
||||||
|
// Add close button
|
||||||
|
QQC2.ToolButton {
|
||||||
|
Layout.leftMargin: -Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
|
||||||
|
icon.name: "arrow-left"
|
||||||
|
onClicked: root.Window.window.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
level: 1
|
||||||
|
text: root.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signal requestOpen(var delegate)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Kirigami.InlineMessage {
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
visible: Plasmoid.immutable
|
||||||
|
text: i18n("Layout changes have been restricted by the system administrator")
|
||||||
|
showCloseButton: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2 // we need this because ColumnLayout's spacing is 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater1
|
||||||
|
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
icon.name: model.icon
|
||||||
|
text: model.name
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
onClicked: root.requestOpen(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
id: repeater2
|
||||||
|
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
icon.name: model.icon
|
||||||
|
text: model.name
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
onClicked: root.requestOpen(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
// SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.0
|
||||||
|
|
||||||
|
import org.kde.plasma.plasmoid
|
||||||
|
import org.kde.kirigami 2.10 as Kirigami
|
||||||
|
|
||||||
|
Kirigami.Page {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var configItem
|
||||||
|
|
||||||
|
signal settingValueChanged()
|
||||||
|
onSettingValueChanged: saveConfig() // we save config immediately on mobile
|
||||||
|
|
||||||
|
title: configItem.name
|
||||||
|
|
||||||
|
topPadding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
for (let key in Plasmoid.configuration) {
|
||||||
|
if (loader.item["cfg_" + key] != undefined) {
|
||||||
|
Plasmoid.configuration[key] = loader.item["cfg_" + key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For ConfigurationContainmentActions.qml
|
||||||
|
if (loader.item.hasOwnProperty("saveConfig")) {
|
||||||
|
loader.item.saveConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data: [
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
const plasmoidConfig = Plasmoid.configuration
|
||||||
|
|
||||||
|
const props = {}
|
||||||
|
for (let key in plasmoidConfig) {
|
||||||
|
props["cfg_" + key] = Plasmoid.configuration[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject configurable config values
|
||||||
|
setSource(configItem.source, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
item.parent = root.contentItem;
|
||||||
|
item.anchors.fill = root.contentItem;
|
||||||
|
|
||||||
|
const plasmoidConfig = Plasmoid.configuration;
|
||||||
|
|
||||||
|
for (let key in plasmoidConfig) {
|
||||||
|
const changedSignal = item["cfg_" + key + "Changed"]
|
||||||
|
if (changedSignal) {
|
||||||
|
changedSignal.connect(root.settingValueChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const configurationChangedSignal = item.configurationChanged
|
||||||
|
if (configurationChangedSignal) {
|
||||||
|
configurationChangedSignal.connect(root.settingValueChanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
var unsavedChangesChangedSignal = item.unsavedChangesChanged
|
||||||
|
if (unsavedChangesChangedSignal) {
|
||||||
|
unsavedChangesChangedSignal.connect( () => {
|
||||||
|
if (item.unsavedChanges) {
|
||||||
|
root.settingValueChanged()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -16,11 +16,13 @@ Kirigami.Page {
|
||||||
signal settingValueChanged()
|
signal settingValueChanged()
|
||||||
onSettingValueChanged: saveConfig(); // we save config immediately on mobile
|
onSettingValueChanged: saveConfig(); // we save config immediately on mobile
|
||||||
|
|
||||||
title: kcm.name
|
title: internalPage.title ? internalPage.title : kcm.name
|
||||||
|
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
bottomPadding: 0
|
bottomPadding: 0
|
||||||
|
|
||||||
flickable: internalPage.flickable
|
flickable: internalPage.flickable
|
||||||
actions: [
|
actions: [
|
||||||
internalPage.actions.main,
|
internalPage.actions.main,
|
||||||
|
|
@ -38,7 +40,7 @@ Kirigami.Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
kcm.load()
|
kcm.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfig() {
|
function saveConfig() {
|
||||||
|
|
@ -48,14 +50,21 @@ Kirigami.Page {
|
||||||
data: [
|
data: [
|
||||||
Connections {
|
Connections {
|
||||||
target: kcm
|
target: kcm
|
||||||
onPagePushed: {
|
function onPagePushed() {
|
||||||
app.pageStack.push(configurationKcmPageComponent.createObject(app.pageStack, {"kcm": kcm, "internalPage": page}));
|
app.pageStack.push(configurationKcmPageComponent.createObject(app.pageStack, {"kcm": kcm, "internalPage": page}));
|
||||||
}
|
}
|
||||||
onPageRemoved: app.pageStack.pop();
|
function onPageRemoved() {
|
||||||
|
app.pageStack.pop();
|
||||||
|
}
|
||||||
|
function onNeedsSaveChanged() {
|
||||||
|
if (kcm.needsSave) {
|
||||||
|
container.settingValueChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Connections {
|
Connections {
|
||||||
target: app.pageStack
|
target: app.pageStack
|
||||||
onPageRemoved: {
|
function onPageRemoved() {
|
||||||
if (kcm.needsSave) {
|
if (kcm.needsSave) {
|
||||||
kcm.save()
|
kcm.save()
|
||||||
}
|
}
|
||||||
|
|
@ -65,12 +74,4 @@ Kirigami.Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Connections {
|
|
||||||
target: kcm
|
|
||||||
function onNeedsSaveChanged() {
|
|
||||||
if (kcm.needsSave) {
|
|
||||||
container.settingValueChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue