plasmoides KDE Store/GitHub (gnome-pager, kMenu, kvitals, windowtitle, claude, launchpad)

This commit is contained in:
Josevi
2026-07-08 20:47:59 +02:00
parent ca606377ee
commit dd3b0f6ae8
136 changed files with 20239 additions and 0 deletions
@@ -0,0 +1,251 @@
/*
* Plasma Gnome Pager — ConfigAppearance.qml (Appearance settings page)
*
* SPDX-FileCopyrightText: 2026 Kenan Salar
* SPDX-License-Identifier: GPL-3.0-or-later
*
* The Appearance page, built on ConfigPageBase. Ratios use ConfigSlider; dotSize/pillSize are integer
* sliders where 0 reads "Default"/"Match dots" (the 0 = auto sentinel). Colours use ColorButton, lazy-loaded
* with the dialog so the import never affects the always-on widget. Each `cfg_<key>` MUST match main.xml.
*/
pragma ComponentBehavior: Bound // the occupancyStyle delegate references outer ids (occupancyStyle/root)
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kquickcontrols as KQuickControls
ConfigPageBase {
id: root
property alias cfg_dotStyle: dotStyle.currentIndex
property alias cfg_singleLine: singleLine.checked
property alias cfg_matchDesktopGrid: matchDesktopGrid.checked
property alias cfg_dotSize: dotSize.value
property alias cfg_pillSize: pillSize.value
property alias cfg_spacingFactor: spacingFactor.value
property alias cfg_pillWidthFactor: pillWidthFactor.value
property alias cfg_inactiveOpacity: inactiveOpacity.value
property alias cfg_hoverOpacity: hoverOpacity.value
property alias cfg_showOccupancy: showOccupancy.checked
property alias cfg_occupancyStyle: occupancyStyle.currentIndex
property alias cfg_occupiedOpacity: occupiedOpacity.value
property alias cfg_followThemeColors: followThemeColors.checked
property alias cfg_activeColor: activeColor.color
property alias cfg_inactiveColor: inactiveColor.color
property alias cfg_occupiedColor: occupiedColor.color
// Injected by the config dialog from the main.xml defaults; read by the Defaults handler below.
property int cfg_dotStyleDefault
property bool cfg_singleLineDefault
property bool cfg_matchDesktopGridDefault
property int cfg_dotSizeDefault
property int cfg_pillSizeDefault
property real cfg_spacingFactorDefault
property real cfg_pillWidthFactorDefault
property real cfg_inactiveOpacityDefault
property real cfg_hoverOpacityDefault
property bool cfg_showOccupancyDefault
property int cfg_occupancyStyleDefault
property real cfg_occupiedOpacityDefault
property bool cfg_followThemeColorsDefault
property color cfg_activeColorDefault
property color cfg_inactiveColorDefault
property color cfg_occupiedColorDefault
// This page's keys + compare kind; ConfigPageBase binds isModified + the Defaults reset off it
// (reals within epsilon, colours via Qt.colorEqual).
configKeys: [
{ n: "dotStyle", t: "int" },
{ n: "singleLine", t: "bool" },
{ n: "matchDesktopGrid", t: "bool" },
{ n: "dotSize", t: "int" },
{ n: "pillSize", t: "int" },
{ n: "spacingFactor", t: "real" },
{ n: "pillWidthFactor", t: "real" },
{ n: "inactiveOpacity", t: "real" },
{ n: "hoverOpacity", t: "real" },
{ n: "showOccupancy", t: "bool" },
{ n: "occupancyStyle", t: "int" },
{ n: "occupiedOpacity", t: "real" },
{ n: "followThemeColors", t: "bool" },
{ n: "activeColor", t: "color" },
{ n: "inactiveColor", t: "color" },
{ n: "occupiedColor", t: "color" }
]
// Which pager style is selected, by the dotStyle combo index (order matches Logic.DOT_STYLE / main.xml:
// 0 = Sliding pill, 1 = Filled & ring). The pill knobs only apply to Sliding pill; Filled & ring disables
// the redundant Hollow ring occupancy marker. Named once here so the index checks aren't repeated below.
readonly property bool pillStyle: dotStyle.currentIndex === 0
readonly property bool ringStyle: dotStyle.currentIndex === 1
Kirigami.FormLayout {
QQC2.ComboBox {
id: dotStyle
Kirigami.FormData.label: i18n("Pager style:")
// Order MUST match Logic.DOT_STYLE / main.xml dotStyle (currentIndex is stored as the index).
model: [i18n("Sliding pill"), i18n("Filled & ring")]
Layout.preferredWidth: root.fieldWidth // match the other field widths (ConfigPageBase.fieldWidth)
// Filled & ring disables the Hollow ring occupancy marker (index 2) — the dot is already a ring —
// so migrate a previously-chosen Hollow ring to Filled (0) when the user switches to it.
onActivated: {
if (root.ringStyle && occupancyStyle.currentIndex === 2)
occupancyStyle.currentIndex = 0;
}
}
QQC2.CheckBox {
id: singleLine
Kirigami.FormData.label: i18n("Multiple rows:")
text: i18n("Show all desktops in a single line")
}
QQC2.Label {
// Hint: ignore the KWin grid entirely and lay everything out as one strip following the panel.
text: i18n("Ignore the grid rows from System Settings and lay every desktop out in one strip along the panel (a single vertical strip on a vertical panel).")
wrapMode: Text.WordWrap
opacity: 0.7
font: Kirigami.Theme.smallFont
Layout.fillWidth: true
Layout.preferredWidth: root.fieldWidth // wrap within the field column
}
QQC2.CheckBox {
id: matchDesktopGrid
Kirigami.FormData.label: i18n("Vertical panels:")
text: i18n("Match the virtual-desktop grid layout")
// Orthogonal to "single line": this sets the direction (across vs. down), so it composes — single
// line + match grid gives a single HORIZONTAL row. Hence no longer greyed while single line is on.
}
QQC2.Label {
// Hint: this only matters on a vertical panel (a horizontal panel already mirrors the grid).
text: i18n("Arrange the dots like the desktop grid in System Settings (rows top to bottom) instead of running them down the panel. No effect on horizontal panels.")
wrapMode: Text.WordWrap
opacity: 0.7
font: Kirigami.Theme.smallFont
Layout.fillWidth: true
Layout.preferredWidth: root.fieldWidth // wrap within the field column
}
ConfigSlider {
id: dotSize
label: i18n("Dot size:")
from: 0
to: 64
stepSize: 1
// 0 = auto: the widget falls back to the HiDPI-aware themed size.
format: v => v === 0 ? i18n("Default") : i18np("%1 px", "%1 px", Math.round(v))
}
ConfigSlider {
id: pillSize
// "Thickness" (not "size") disambiguates from "Pill length:" below — the pill's two axes (also avoids an msgmerge fuzzy collision).
label: i18n("Pill thickness:")
enabled: root.pillStyle // the pill only exists in the Sliding pill style
from: 0
to: 64
stepSize: 1
// 0 = auto: the pill thickness matches the (effective) dot size, so the pill tracks the dots.
format: v => v === 0 ? i18n("Match dots") : i18np("%1 px", "%1 px", Math.round(v))
}
ConfigSlider {
id: spacingFactor
label: i18n("Spacing:")
from: 0.0
to: 2.0
stepSize: 0.05
format: v => i18n("%1× dot", v.toFixed(2))
}
ConfigSlider {
id: pillWidthFactor
label: i18n("Pill length:")
enabled: root.pillStyle // the pill only exists in the Sliding pill style
from: 1.0
to: 10.0
stepSize: 0.1
// Length as a multiple of the PILL thickness (its aspect ratio), not the dot size.
format: v => i18n("%1× pill", v.toFixed(1))
}
ConfigSlider {
id: inactiveOpacity
label: i18n("Inactive opacity:")
from: 0.0
to: 1.0
stepSize: 0.01 // 1% increments for fine control (drag or arrow keys)
format: v => Math.round(v * 100) + "%"
}
ConfigSlider {
id: hoverOpacity
label: i18n("Hover opacity:")
from: 0.0
to: 1.0
stepSize: 0.01 // 1% increments for fine control (drag or arrow keys)
format: v => Math.round(v * 100) + "%"
}
QQC2.CheckBox {
id: showOccupancy
Kirigami.FormData.label: i18n("Occupied desktops:")
text: i18n("Highlight desktops with open windows")
}
QQC2.ComboBox {
id: occupancyStyle
Kirigami.FormData.label: i18n("Indicator style:")
enabled: showOccupancy.checked
// Order MUST match Logic.OCCUPANCY / main.xml occupancyStyle (currentIndex is stored as the index).
model: [i18n("Filled"), i18n("Inner dot"), i18n("Hollow ring")]
// "Hollow ring" (index 2) is redundant in the Filled & ring pager style — the dot is ALREADY a
// hollow ring — so disable that item there (selection blocked; a stored value is suppressed at runtime).
delegate: QQC2.ItemDelegate {
id: occStyleItem
required property int index
required property string modelData
width: occupancyStyle.width
text: occStyleItem.modelData
enabled: !(root.ringStyle && occStyleItem.index === 2)
highlighted: occupancyStyle.highlightedIndex === occStyleItem.index
}
}
ConfigSlider {
id: occupiedOpacity
label: i18n("Occupied opacity:")
enabled: showOccupancy.checked // every indicator style uses the occupied-marker opacity
from: 0.0
to: 1.0
stepSize: 0.01 // 1% increments for fine control (drag or arrow keys)
format: v => Math.round(v * 100) + "%"
}
Item {
Kirigami.FormData.isSection: true // a little vertical breathing room before the colours
}
QQC2.CheckBox {
id: followThemeColors
Kirigami.FormData.label: i18n("Colors:")
text: i18n("Follow the color scheme")
}
KQuickControls.ColorButton {
id: activeColor
Kirigami.FormData.label: i18n("Active desktop:")
enabled: !followThemeColors.checked // custom colours apply only when not following the theme
showAlphaChannel: false
}
KQuickControls.ColorButton {
id: inactiveColor
Kirigami.FormData.label: i18n("Inactive desktop:")
enabled: !followThemeColors.checked
showAlphaChannel: false
}
KQuickControls.ColorButton {
id: occupiedColor
Kirigami.FormData.label: i18n("Occupied desktop:")
enabled: !followThemeColors.checked // the occupied marker; theme accent is used while following the scheme
showAlphaChannel: false
}
}
}
@@ -0,0 +1,145 @@
/*
* Plasma Gnome Pager — ConfigGeneral.qml (Behavior settings page)
*
* SPDX-FileCopyrightText: 2026 Kenan Salar
* SPDX-License-Identifier: GPL-3.0-or-later
*
* The Behavior page, built on ConfigPageBase. Each `cfg_<key>` alias MUST match a main.xml entry exactly
* (the dialog wires load/save); `cfg_<key>Default` is injected from the schema.
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
ConfigPageBase {
id: root
property alias cfg_enableScroll: enableScroll.checked
property alias cfg_scrollWrap: scrollWrap.checked
property alias cfg_invertScroll: invertScroll.checked
property alias cfg_pillClickAction: pillClickAction.currentIndex
property alias cfg_showTooltips: showTooltips.checked
property alias cfg_showWindowList: showWindowList.checked
property alias cfg_enableAddRemove: enableAddRemove.checked
property alias cfg_enableRename: enableRename.checked
property alias cfg_dynamicWorkspaces: dynamicWorkspaces.checked
property alias cfg_dynamicNamePrefix: dynamicNamePrefix.text
property alias cfg_animationDuration: animationDuration.value
// Injected by the config dialog from the main.xml defaults; read by the Defaults handler below.
property bool cfg_enableScrollDefault
property bool cfg_scrollWrapDefault
property bool cfg_invertScrollDefault
property int cfg_pillClickActionDefault
property bool cfg_showTooltipsDefault
property bool cfg_showWindowListDefault
property bool cfg_enableAddRemoveDefault
property bool cfg_enableRenameDefault
property bool cfg_dynamicWorkspacesDefault
property string cfg_dynamicNamePrefixDefault
property int cfg_animationDurationDefault
// This page's keys + compare kind; ConfigPageBase binds isModified AND the Defaults reset off it.
configKeys: [
{ n: "enableScroll", t: "bool" },
{ n: "scrollWrap", t: "bool" },
{ n: "invertScroll", t: "bool" },
{ n: "pillClickAction", t: "int" },
{ n: "showTooltips", t: "bool" },
{ n: "showWindowList", t: "bool" },
{ n: "enableAddRemove", t: "bool" },
{ n: "enableRename", t: "bool" },
{ n: "dynamicWorkspaces", t: "bool" },
{ n: "dynamicNamePrefix", t: "string" },
{ n: "animationDuration", t: "int" }
]
Kirigami.FormLayout {
QQC2.CheckBox {
id: enableScroll
Kirigami.FormData.label: i18n("Mouse:")
text: i18n("Scroll over the pager to switch desktops")
}
QQC2.CheckBox {
id: scrollWrap
text: i18n("Wrap around at the first and last desktop")
enabled: enableScroll.checked // wrap only matters when scrolling is on
}
QQC2.CheckBox {
id: invertScroll
text: i18n("Invert the scroll direction")
enabled: enableScroll.checked // inversion only matters when scrolling is on
}
QQC2.ComboBox {
id: pillClickAction
Kirigami.FormData.label: i18n("Click current desktop:")
// Order MUST match Logic.PILL_CLICK_ACTION / main.xml pillClickAction (currentIndex is stored as the index).
model: [i18n("Nothing"), i18n("Show Desktop"), i18n("Overview"), i18n("Grid")]
Layout.preferredWidth: root.fieldWidth // match the other field widths (ConfigPageBase.fieldWidth)
}
QQC2.Label {
// Clarify the action only fires on the highlighted (current) desktop; other dots just switch.
text: i18n("Action when clicking the highlighted current desktop. Clicking any other desktop switches to it.")
wrapMode: Text.WordWrap
opacity: 0.7
font: Kirigami.Theme.smallFont
Layout.fillWidth: true
Layout.preferredWidth: root.fieldWidth // wrap within the field column
}
QQC2.CheckBox {
id: showTooltips
Kirigami.FormData.label: i18n("Tooltips:")
text: i18n("Show the desktop name on hover")
}
QQC2.CheckBox {
id: showWindowList
text: i18n("List the open windows in the tooltip")
enabled: showTooltips.checked // the window list only shows when tooltips are on
}
QQC2.CheckBox {
id: enableAddRemove
Kirigami.FormData.label: i18n("Menu:")
text: i18n("Add and remove desktops from the right-click menu")
// Greyed while dynamic workspaces is on (mutually exclusive); value preserved, returns when off.
enabled: !dynamicWorkspaces.checked
}
QQC2.CheckBox {
id: enableRename
text: i18n("Rename the current desktop from the right-click menu")
}
QQC2.CheckBox {
id: dynamicWorkspaces
Kirigami.FormData.label: i18n("Dynamic desktops:")
text: i18n("Automatically add and remove desktops (GNOME-style)")
}
QQC2.Label {
// Hint explaining the exclusivity above, so a new user sees why Add/Remove greys out.
text: i18n("While on, desktops are managed automatically — the menu Add/Remove options are disabled.")
visible: dynamicWorkspaces.checked
wrapMode: Text.WordWrap
opacity: 0.7
font: Kirigami.Theme.smallFont
Layout.fillWidth: true
Layout.preferredWidth: root.fieldWidth // wrap within the field column
}
QQC2.TextField {
id: dynamicNamePrefix
Kirigami.FormData.label: i18n("New desktop name:")
// Base name for auto-created desktops (number appended); empty → the placeholder default.
enabled: dynamicWorkspaces.checked
placeholderText: i18nc("@info default base name for auto-created virtual desktops", "Desktop")
Layout.preferredWidth: root.fieldWidth // match the slider track width (ConfigPageBase.fieldWidth)
}
ConfigSlider {
id: animationDuration
label: i18n("Animation duration:")
from: 0
to: 2000
stepSize: 25 // clean 25 ms increments (SnapAlways is the ConfigSlider default)
// 0 = follow the theme's default (and the global "reduce animations" setting).
format: v => v === 0 ? i18n("Default") : i18np("%1 ms", "%1 ms", Math.round(v))
}
}
}
@@ -0,0 +1,58 @@
/*
* Plasma Gnome Pager — ConfigPageBase.qml (shared settings-page base)
*
* SPDX-FileCopyrightText: 2026 Kenan Salar
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Shared skeleton for every settings page: a Kirigami.ScrollablePage for the KDE header + scrolling, plus
* the "Defaults" button the Plasma footer lacks (defined ONCE here). A derived page only declares its
* `configKeys` { n, t } list; the modified-check and the Defaults reset are bound off it here, once.
*/
import QtQuick
import org.kde.kirigami as Kirigami
Kirigami.ScrollablePage {
id: root
// The derived page's keys + compare kind ({ n, t }); drives both isModified and the Defaults reset.
property var configKeys: []
// True when any key differs from its default (gates the Defaults action). Bound off configKeys here
// so a derived page need only declare the list; empty configKeys ⇒ false (unmodified).
property bool isModified: configKeys.some(k => root.fieldChanged(root, k.n, k.t))
// Field-column width for non-slider fields; kept equal to ConfigSlider.trackWidth so every row lines up.
readonly property int fieldWidth: Kirigami.Units.gridUnit * 18
// Tolerance for real-valued "differs from default": SnapAlways can land a value a ULP off the default.
readonly property real epsilon: 1e-9
// Does cfg_<name> differ from cfg_<name>Default? Type-aware: reals within epsilon, colours via Qt.colorEqual, else exact.
function fieldChanged(page, name, kind) {
var a = page["cfg_" + name];
var b = page["cfg_" + name + "Default"];
if (kind === "real")
return Math.abs(a - b) > root.epsilon;
if (kind === "color")
return !Qt.colorEqual(a, b);
return a !== b;
}
// Reset cfg_<name> to its injected schema default.
function resetField(page, name) {
page["cfg_" + name] = page["cfg_" + name + "Default"];
}
// Raised by the Defaults action; resets every configKeys entry to its injected schema default.
signal defaultsRequested()
onDefaultsRequested: configKeys.forEach(k => root.resetField(root, k.n))
actions: [
Kirigami.Action {
text: i18n("Defaults")
icon.name: "edit-undo-symbolic"
enabled: root.isModified
onTriggered: root.defaultsRequested()
}
]
}
@@ -0,0 +1,61 @@
/*
* Plasma Gnome Pager — ConfigSlider.qml (reusable config-page control)
*
* SPDX-FileCopyrightText: 2026 Kenan Salar
* SPDX-License-Identifier: GPL-3.0-or-later
*
* A FormLayout row of a Slider + a right-hand value read-out. One `format` closure (value → string) drives
* BOTH the read-out AND its reserved width (widest of from/to) so the label can't reflow mid-drag. The
* track is a FIXED width (NOT fillWidth) so sliders match across both pages; the value label absorbs slack.
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
RowLayout {
id: root
property alias value: slider.value
property alias from: slider.from
property alias to: slider.to
property alias stepSize: slider.stepSize
property alias snapMode: slider.snapMode
property string label: "" // the row's Kirigami.FormData label
property var format: (v) => String(v) // value → read-out string (drives text AND reserved width)
// Fixed track length, matched to ConfigPageBase.fieldWidth so every row's field column lines up.
readonly property int trackWidth: Kirigami.Units.gridUnit * 18
Kirigami.FormData.label: root.label
QQC2.Slider {
id: slider
// Fixed track length (NOT fillWidth): a fillWidth track would stretch wider on the Behavior page's long labels. min == preferred so it can't shrink.
Layout.preferredWidth: root.trackWidth
Layout.minimumWidth: root.trackWidth
snapMode: QQC2.Slider.SnapAlways // clean increments even when dragged (override via alias)
}
QQC2.Label {
id: valueLabel
text: root.format(slider.value)
horizontalAlignment: Text.AlignRight
// fillWidth so extra column width is absorbed here — the right-aligned read-out pins to the column edge and the slider keeps its length.
Layout.fillWidth: true
// Reserve the widest the read-out can get (+ buffer) so the cell never resizes mid-drag.
Layout.minimumWidth: Math.max(valueMetricsFrom.advanceWidth, valueMetricsTo.advanceWidth) + Kirigami.Units.smallSpacing
Layout.preferredWidth: Layout.minimumWidth
TextMetrics {
id: valueMetricsFrom
font: valueLabel.font
text: root.format(slider.from)
}
TextMetrics {
id: valueMetricsTo
font: valueLabel.font
text: root.format(slider.to)
}
}
}