Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-3-Clause
ecm_add_qml_module(kcmutilsqmlplugin URI "org.kde.kcmutils" VERSION 1.0 DEPENDENCIES QtQuick org.kde.kcmutils.private org.kde.config GENERATE_PLUGIN_SOURCE)
target_sources(kcmutilsqmlplugin
PRIVATE
settingstateproxy.cpp
kcmlauncher.cpp
types.h
)
target_link_libraries(kcmutilsqmlplugin
PRIVATE
KF6::KIOGui
Qt6::Qml
Qt6::Quick
kcmutils_proxy_model
kcmutils_logging_STATIC
KF6KCMUtilsQuick
)
ecm_target_qml_sources(kcmutilsqmlplugin SOURCES
components/PluginDelegate.qml
components/PluginSelector.qml
components/AbstractKCM.qml
components/ContextualHelpButton.qml
components/GridDelegate.qml
components/GridView.qml
components/GridViewKCM.qml
components/ScrollView.qml
components/ScrollViewKCM.qml
components/SettingHighlighter.qml
components/SettingStateBinding.qml
components/SimpleKCM.qml
)
ecm_target_qml_sources(kcmutilsqmlplugin PATH private SOURCES
components/private/AboutPlugin.qml
components/private/GridDelegateMenu.qml
components/private/GridViewInternal.qml
)
ecm_finalize_qml_module(kcmutilsqmlplugin DESTINATION ${KDE_INSTALL_QMLDIR})
ecm_add_qml_module(kcmutilsprivateqmlplugin URI "org.kde.kcmutils.private" DEPENDENCIES QtCore QtQuick GENERATE_PLUGIN_SOURCE)
target_sources(kcmutilsprivateqmlplugin PRIVATE private_types.h settinghighlighterprivate.cpp)
target_link_libraries(kcmutilsprivateqmlplugin PRIVATE Qt6::Quick kcmutils_proxy_model)
ecm_finalize_qml_module(kcmutilsprivateqmlplugin DESTINATION ${KDE_INSTALL_QMLDIR})
@@ -0,0 +1,206 @@
/*
SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* This component is intended to be used as root item for
* KCMs with arbitrary content. Unlike SimpleKCM this does NOT
* provide a scrollable view, The developer will have to manage
* their own scrollviews.
* Most of the times SimpleKCM should be used instead
* @code
* import QtQuick
* import QtQuick.Controls as QQC2
* import QtQuick.Layouts
* import org.kde.kcmutils as KCMUtils
*
* KCMUtils.AbstractKCM {
* RowLayout {
* QQC2.ScrollView { }
* QQC2.ScrollView { }
* }
* footer: QQC2.ToolBar { }
* }
* @endcode
* @inherits org.kde.kirigami.Page
* @since 6.0
*/
Kirigami.Page {
id: root
readonly property int margins: 6 // Layout_ChildMarginWidth from Breeze
/**
* framedView: bool
* Whether to use this component as the base of a "framed" KCM with an
* inner scrollview that draws its own frame.
* Default: true
*/
property bool framedView: true
/**
* extraFooterTopPadding: bool
* Whether the footer should have extra space and a separator line drawn
* above it. Set this to true in a KCM with a custom footer and a ListView
* immediately above it.
* Default: false
*/
property bool extraFooterTopPadding: false
/**
* headerPaddingEnabled: bool
* Whether the contents of the header will have automatic padding around it.
* Should be disabled when using an InlineMessage or custom content item in
* the header that's intended to touch the window edges.
* Default: true
*/
property bool headerPaddingEnabled: true
/**
* footerPaddingEnabled: bool
* Whether the contents of the footer will have automatic padding around it.
* Should be disabled when using an InlineMessage or custom content item in
* the footer that's intended to touch the window edges.
* Default: true
*/
property bool footerPaddingEnabled: true
property bool sidebarMode: false
function __itemVisible(item: Item): bool {
return item !== null && item.visible && item.implicitHeight > 0;
}
function __headerContentVisible(): bool {
return __itemVisible(headerParent.contentItem);
}
function __footerContentVisible(): bool {
return __itemVisible(footerParent.contentItem);
}
// Deliberately not checking for __footerContentVisible because
// we always want the footer line to be visible when the scrollview
// doesn't have a frame of its own, because System Settings always
// adds its own footer for the Apply, Help, and Defaults buttons
function __headerSeparatorVisible(): bool {
return !framedView && __headerContentVisible();
}
function __footerSeparatorVisible(): bool {
return !framedView && extraFooterTopPadding;
}
title: (typeof kcm !== "undefined") ? kcm.name : ""
// Make pages fill the whole view by default
Kirigami.ColumnView.fillWidth: sidebarMode
? Kirigami.ColumnView.view
&& (Kirigami.ColumnView.view.width < Kirigami.Units.gridUnit * 36
|| Kirigami.ColumnView.index >= Kirigami.ColumnView.view.count - 1)
: true
padding: 0
topPadding: framedView && !__headerContentVisible() ? margins : 0
leftPadding: undefined
rightPadding: undefined
bottomPadding: framedView && !__footerContentVisible() ? margins : 0
verticalPadding: undefined
horizontalPadding: framedView ? margins : 0
header: Column {
Kirigami.Padding {
id: headerParent
anchors {
left: parent.left
right: parent.right
}
height: root.__headerContentVisible() ? undefined : 0
padding: root.headerPaddingEnabled ? root.margins : 0
}
// When the scrollview isn't drawing its own frame, we need to add a
// line below the header (when visible) to separate it from the view
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
}
visible: root.__headerSeparatorVisible()
}
}
// View background, shown when the scrollview isn't drawing its own frame
Rectangle {
anchors.fill: parent
visible: !root.framedView
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Kirigami.Theme.backgroundColor
}
footer: Column {
// When the scrollview isn't drawing its own frame, we need to add a
// line above the footer ourselves to separate it from the view
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
}
visible: root.__footerSeparatorVisible()
}
Kirigami.Padding {
id: footerParent
anchors {
left: parent.left
right: parent.right
}
height: root.__footerContentVisible() ? undefined : 0
padding: root.footerPaddingEnabled ? root.margins : 0
}
}
function __swapContentIntoContainer(property: string, container: Item): void {
const content = this[property];
const rootContainer = container.parent;
if (content && content !== rootContainer) {
// Revert the effect of repeated onHeaderChanged invocations
// during initialization in Page super-type.
content.anchors.top = undefined;
this[property] = rootContainer;
container.contentItem = content;
}
}
function __adoptOverlaySheets(): void {
// Search overlaysheets in contentItem, parent to root if found
for (const object of contentItem.data) {
if (object instanceof Kirigami.OverlaySheet) {
if (object.parent === null) {
object.parent = this;
}
data.push(object);
}
}
}
Component.onCompleted: {
__swapContentIntoContainer("header", headerParent);
__swapContentIntoContainer("footer", footerParent);
__adoptOverlaySheets();
}
}
@@ -0,0 +1,51 @@
/*
SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.7 as QQC2
import QtQuick.Window 2.15
import org.kde.kirigami 2.3 as Kirigami
@Deprecated { reason: "Use the version in Kirigami instead!" }
QQC2.Button {
id: root
icon.name: "help-contextual"
flat: true
property alias toolTipText: toolTip.text
property var toolTipVisible: false
onReleased: {
toolTipVisible ? toolTip.delay = Kirigami.Units.toolTipDelay : toolTip.delay = 0;
toolTipVisible = !toolTipVisible;
}
onActiveFocusChanged: {
toolTip.delay = Kirigami.Units.toolTipDelay;
toolTipVisible = false;
}
Layout.maximumHeight: parent.height
QQC2.ToolTip {
id: toolTip
implicitWidth: Math.min(21 * Kirigami.Units.gridUnit, root.Window.width) // Wikipedia says anything between 45 and 75 characters per line is acceptable. 21 * Kirigami.Units.gridUnit feels right.
visible: parent.hovered || parent.toolTipVisible
onVisibleChanged: {
if (!visible && parent.toolTipVisible) {
parent.toolTipVisible = false;
delay = Kirigami.Units.toolTipDelay;
}
}
timeout: -1
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.WhatsThisCursor
acceptedButtons: Qt.NoButton
}
Accessible.name: i18ndc("kcmutils6", "@action:button", "Show Contextual Help")
}
@@ -0,0 +1,250 @@
/*
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as Private
/**
* Base delegate for KControlmodules based on Grid views of thumbnails
* Use the onClicked signal handler for managing the main action when
* the user clicks on the thumbnail
* @inherits QtQuick.Templates.ItemDelegate
*/
T.ItemDelegate {
id: delegate
/**
* toolTip: string
* string for a tooltip for the whole delegate
*/
property string toolTip
/**
* subtitle: string
* optional string for the text to show below the main label
*/
property string subtitle
/**
* thumbnail: Item
* the item actually implementing the thumbnail: the visualization is up to the implementation
*/
property alias thumbnail: thumbnailArea.data
/**
* thumbnailAvailable: bool
* Set it to true when a thumbnail is actually available: when false,
* only an icon will be shown instead of the actual thumbnail
* ("edit-none" if pluginName is "None", otherwise it uses "view-preview").
*/
property bool thumbnailAvailable: false
/**
* actions: list<Kirigami.Action>
* A list of extra actions for the thumbnails. They will be shown as
* icons on the bottom-right corner of the thumbnail on mouse over
*/
property list<Kirigami.Action> actions
width: GridView.view.cellWidth
height: GridView.view.cellHeight
hoverEnabled: !Kirigami.Settings.isMobile
Accessible.description: {
if (toolTip.length === 0) {
return subtitle;
} else if (subtitle.length === 0) {
return toolTip;
}
return `${subtitle}; ${toolTip}`
}
Keys.onEnterPressed: event => thumbnail.trigger()
Keys.onMenuPressed: event => thumbnail.trigger()
Keys.onSpacePressed: event => thumbnail.trigger()
Kirigami.ShadowedRectangle {
id: thumbnail
anchors {
centerIn: parent
verticalCenterOffset: Math.ceil(-labelLayout.height / 2)
}
width: Kirigami.Settings.isMobile ? delegate.width - Kirigami.Units.gridUnit : Math.min(delegate.GridView.view.implicitCellWidth, delegate.width - Kirigami.Units.gridUnit)
height: Kirigami.Settings.isMobile ? Math.round((delegate.width - Kirigami.Units.gridUnit) / 1.6)
: Math.min(delegate.GridView.view.implicitCellHeight - Kirigami.Units.gridUnit * 3,
delegate.height - Kirigami.Units.gridUnit)
radius: Kirigami.Units.cornerRadius
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
shadow.xOffset: 0
shadow.yOffset: 2
shadow.size: 10
shadow.color: Qt.rgba(0, 0, 0, 0.3)
color: {
if (delegate.GridView.isCurrentItem) {
if (delegate.enabled && delegate.GridView.view.neutralHighlight) {
return Kirigami.Theme.neutralTextColor;
}
return Kirigami.Theme.highlightColor;
}
if (delegate.enabled && delegate.hovered) {
// Match appearance of hovered list items
return Qt.alpha(Kirigami.Theme.highlightColor, 0.5);
}
return Kirigami.Theme.backgroundColor;
}
// The menu is only used for keyboard navigation, so no need to always load
// it. This speeds up the compilation of GridDelegate.
property Private.GridDelegateMenu menu
function trigger() {
if (!menu) {
const component = Qt.createComponent("private/GridDelegateMenu.qml");
menu = component.createObject(delegate);
component.destroy();
}
menu.trigger();
}
Rectangle {
id: thumbnailArea
radius: Math.round(Kirigami.Units.cornerRadius / 2)
anchors {
fill: parent
margins: Kirigami.Units.smallSpacing
}
color: Kirigami.Theme.backgroundColor
// "None/There's nothing here" indicator
Kirigami.Icon {
visible: !delegate.thumbnailAvailable
anchors.centerIn: parent
width: Kirigami.Units.iconSizes.large
height: Kirigami.Units.iconSizes.large
source: typeof pluginName === "string" && pluginName === "None" ? "edit-none" : "view-preview"
}
RowLayout {
anchors {
right: parent.right
rightMargin: Kirigami.Units.smallSpacing
bottom: parent.bottom
bottomMargin: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
// Always show above thumbnail content
z: 9999
visible: delegate.actions.length > 0 && (Kirigami.Settings.isMobile || delegate.hovered || delegate.GridView.isCurrentItem)
Repeater {
model: delegate.actions
delegate: QQC2.Button {
required property Kirigami.Action modelData
icon.name: modelData.icon.name
text: modelData.text || modelData.tooltip
display: QQC2.AbstractButton.IconOnly
enabled: modelData.enabled
visible: modelData.visible
activeFocusOnTab: false
onClicked: modelData.trigger()
QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && (QQC2.ToolTip.text !== "")
QQC2.ToolTip.delay: Kirigami.Settings.tabletMode ? Qt.styleHints.mousePressAndHoldInterval : Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: text
}
}
}
}
}
ColumnLayout {
id: labelLayout
spacing: 0
height: Kirigami.Units.gridUnit * 2
anchors {
left: thumbnail.left
right: thumbnail.right
top: thumbnail.bottom
topMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
id: title
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignTop
text: delegate.text
color: enabled ? Kirigami.Theme.textColor : Kirigami.Theme.disabledTextColor
maximumLineCount: 1
elide: Text.ElideRight
font.bold: delegate.GridView.isCurrentItem
textFormat: Text.PlainText
}
QQC2.Label {
id: caption
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
visible: delegate.subtitle.length > 0
opacity: 0.6
text: delegate.subtitle
font.pointSize: Kirigami.Theme.smallFont.pointSize
font.bold: delegate.GridView.isCurrentItem
maximumLineCount: 1
elide: Text.ElideRight
textFormat: Text.PlainText
}
Rectangle {
Layout.preferredHeight: 1
Layout.preferredWidth: Math.max(title.paintedWidth, caption.paintedWidth)
Layout.maximumWidth: labelLayout.width // Otherwise labels can overflow
Layout.alignment: Qt.AlignHCenter
color: Kirigami.Theme.highlightColor
opacity: delegate.visualFocus ? 1 : 0
}
Item { Layout.fillWidth: true; Layout.fillHeight: true; }
}
QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && (QQC2.ToolTip.text !== "")
QQC2.ToolTip.delay: Kirigami.Settings.tabletMode ? Qt.styleHints.mousePressAndHoldInterval : Kirigami.Units.toolTipDelay
QQC2.ToolTip.text: {
const parts = [];
if (delegate.toolTip.length > 0) {
parts.push(delegate.toolTip);
}
if (title.truncated) {
parts.push(title.text);
}
if (caption.truncated) {
parts.push(caption.text);
}
return parts.join("\n");
}
}
@@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as Private
/**
* A ScrollView containing a GridView, with the default behavior about
* sizing and background as recommended by the user interface guidelines
* For most KControl modules, it's recommended to use instead the GridViewKCM
* component as the root element of your module.
* @see GridViewKCM
*/
QQC2.ScrollView {
id: scroll
/**
* view: GridView
* Exposes the internal GridView: in order to set a model or a delegate to it,
* use the following code:
* @code
* import org.kde.kcmutils as KCMUtils
*
* KCMUtils.GridView {
* view.model: kcm.model
* view.delegate: KCMUtils.GridDelegate { }
* }
* @endcode
*/
property alias view: view
property bool framedView: true
activeFocusOnTab: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Component.onCompleted: {
if (background) {
background.visible = Qt.binding(() => framedView);
}
}
Private.GridViewInternal {
id: view
}
QQC2.ScrollBar.horizontal.visible: false
}
@@ -0,0 +1,76 @@
/*
SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCMutils
/**
* This component is intended to be used as the root item for KCMs that are based upon
* a grid view of thumbnails, such as the theme or wallpaper selectors.
* It contains a GridView as its main item.
* It is possible to specify a header and footer component.
* @code
* import org.kde.kcmutils as KCMutils
*
* KCMutils.GridViewKCM {
* header: Item { }
* view.model: kcm.model
* view.delegate: KCMutils.GridDelegate { }
* footer: Item { }
* }
* @endcode
*/
KCMutils.AbstractKCM {
id: root
/**
* view: GridView
* Exposes the internal GridView: in order to set a model or a delegate to it,
* use the following code:
* @code
* import org.kde.kcmutils as KCMutils
*
* KCMutils.GridViewKCM {
* view.model: kcm.model
* view.delegate: KCMutils.GridDelegate { }
* }
* @endcode
*/
property alias view: scroll.view
/**
* framedView: bool
* Whether to draw a frame around the KCM's inner scrollable grid view.
* Default: false
*/
framedView: false
implicitWidth: {
let width = 0;
// Show three columns at once, every column occupies implicitCellWidth + Units.gridUnit
width += 3 * (view.implicitCellWidth + Kirigami.Units.gridUnit);
const scrollBar = scroll.QQC2.ScrollBar.vertical;
width += scrollBar.width + scrollBar.leftPadding + scrollBar.rightPadding;
width += scroll.leftPadding + scroll.rightPadding
width += root.leftPadding + root.rightPadding;
return width;
}
implicitHeight: view.implicitCellHeight * 3 + (header ? header.height : 0) + (footer ? footer.height : 0) + Kirigami.Units.gridUnit
flickable: scroll.view
KCMutils.GridView {
id: scroll
anchors.fill: parent
framedView: root.framedView
}
}
@@ -0,0 +1,108 @@
/*
SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCMUtils
/// @since 6.0, this got renamed from KPluginDelegate to PluginDelegate
Kirigami.CheckSubtitleDelegate {
id: listItem
// Note: when PluginDelegate is embedded in a more complex delegate, model
// object should be passed down explicitly, but it also means that it may
// become null right before delegate's destruction.
required property var model
property list<T.Action> additionalActions
property alias leading: leadingProxy.target
readonly property bool enabledByDefault: model?.enabledByDefault ?? false
readonly property var metaData: model?.metaData
readonly property bool configureVisible: model?.config.isValid ?? false
signal configTriggered()
// Let Optional chaining (?.) operator fall back to `undefined` which resets the width to an implicit value.
width: ListView.view?.width
icon.name: model?.icon ?? ""
text: model?.name ?? ""
subtitle: model?.description ?? ""
checked: model?.enabled ?? false
// TODO: It should be possible to disable this
onToggled: model.enabled = checked
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
// Used by CheckSubtitleDelegate through duck-typing
readonly property alias truncated: titleSubtitle.truncated
LayoutItemProxy {
id: leadingProxy
visible: target !== null
}
Kirigami.IconTitleSubtitle {
id: titleSubtitle
Layout.fillWidth: true
Layout.maximumWidth: Math.ceil(implicitWidth)
icon: icon.fromControlsIcon(listItem.icon)
title: listItem.text
subtitle: listItem.subtitle
reserveSpaceForSubtitle: true
}
Kirigami.ActionToolBar {
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
alignment: Qt.AlignRight
actions: [infoAction, configureAction, ...listItem.additionalActions]
}
}
KCMUtils.SettingHighlighter {
target: listItem.indicator
highlight: listItem.checked !== listItem.enabledByDefault
}
// Take care of displaying the actions
readonly property Kirigami.Action __infoAction: Kirigami.Action {
id: infoAction
icon.name: "help-about-symbolic"
text: i18ndc("kcmutils6", "@info:tooltip", "About")
displayHint: Kirigami.DisplayHint.IconOnly
onTriggered: {
const aboutDialog = (listItem.ListView.view ?? listItem.parent.ListView.view).__aboutDialog;
aboutDialog.metaDataInfo = listItem.metaData;
aboutDialog.open();
}
}
readonly property Kirigami.Action __configureAction: Kirigami.Action {
id: configureAction
visible: listItem.configureVisible
enabled: listItem.checked
icon.name: "configure-symbolic"
text: i18ndc("kcmutils6", "@info:tooltip", "Configure…")
displayHint: Kirigami.DisplayHint.IconOnly
onTriggered: listItem.configTriggered()
}
}
@@ -0,0 +1,100 @@
/*
SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kcmutils.private as KCMUtilsPrivate
import "private" as Private
/**
* ListView for showing plugins with their info and configuration.
* If extra butons should be added, a custom KPluginDelegate with the additionalActions
* property should be defined.
*
* @since 6.0, this got renamed from KPluginSelector to PluginSelector
*/
ListView {
id: pluginSelector
// KPluginModel which contains the plugins that should be displayed
required property QtObject sourceModel
// Query that is typed into the search field. Ideally, this is part of the KCM header
property var query
// PluginDelegate should be used with this, it contains an ActionToolBar that is incredibly expensive to construct,
// make sure to cache delegates a fair amount right out of the box.
cacheBuffer: parent.height * 2
clip: true
// Don't select anything by default as selection is not used here
currentIndex: -1
model: KCMUtilsPrivate.ProxyModel {
id: proxyModel
model: pluginSelector.sourceModel
query: pluginSelector.query ?? ""
}
delegate: PluginDelegate {
}
section.property: "category"
section.delegate: Kirigami.ListSectionHeader {
width: pluginSelector.width
text: section
}
Kirigami.OverlaySheet {
id: internalAboutDialog
parent: pluginSelector.Window.window?.contentItem ?? null
property var metaDataInfo
Loader {
active: internalAboutDialog.metaDataInfo !== undefined
sourceComponent: ColumnLayout {
Private.AboutPlugin {
metaData: internalAboutDialog.metaDataInfo
Layout.maximumWidth: Math.min(Kirigami.Units.gridUnit * 30, Math.round(pluginSelector.width * 0.8))
}
}
}
}
// Only for internal usage in KPluginDelegate!
property var __aboutDialog: internalAboutDialog
Loader {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 8)
active: pluginSelector.count === 0 && !startupTimer.running
opacity: active && status === Loader.Ready ? 1 : 0
visible: opacity > 0
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
sourceComponent: Kirigami.PlaceholderMessage {
icon.name: "edit-none"
text: pluginSelector.query && pluginSelector.query.length > 0 ? i18nd("kcmutils6", "No matches") : i18nd("kcmutils6", "No plugins found")
}
}
// The view can take a bit of time to initialize itself when the KCM first
// loads, during which time count is 0, which would cause the placeholder
// message to appear for a moment and then disappear. To prevent this, let's
// suppress it appearing for a moment after the KCM loads.
Timer {
id: startupTimer
interval: Kirigami.Units.humanMoment
running: false
}
Component.onCompleted: {
startupTimer.start()
}
}
@@ -0,0 +1,55 @@
/*
SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* A ScrollView containing a GridView, with the default behavior about
* sizing and background as recommended by the user interface guidelines
* For most KControl modules, it's recommended to use instead the GridViewKCM
* component as the root element of your module.
* @code
* import org.kde.kcmutils as KCMUtils
*
* KCMUtils.ScrollView {
* view: ListView { }
* }
* @endcode
* @see GridViewKCM
*/
QQC2.ScrollView {
id: scroll
/**
* view: GridView
* Exposes the internal flickable
*/
property Flickable view
property bool framedView: true
contentItem: view
onViewChanged: {
view.parent = scroll;
if (!view.KeyNavigation.up) {
view.KeyNavigation.up = Qt.binding(() => root.globalToolBarItem);
}
}
activeFocusOnTab: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
Component.onCompleted: {
if (background) {
background.visible = Qt.binding(() => framedView);
}
}
QQC2.ScrollBar.horizontal.visible: false
}
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kcmutils as KCMUtils
/**
* This component is intended to be used as the root item for KCMs that are based upon a list view or another vertical flickable.
* It contains a ScrollView as its main item.
* It is possible to specify a header and footer component.
* @code
* import org.kde.kcmutils as KCMUtils
*
* KCMUtils.ScrollViewKCM {
* header: Item { }
* view: ListView { }
* footer: Item { }
* }
* @endcode
*/
KCMUtils.AbstractKCM {
id: root
/**
* view: ScrollView
* Exposes the internal flickable
*/
property alias view: scroll.view
/**
* framedView: bool
* Whether to draw a frame around the KCM's inner scrollable list view.
* Default: false
*
* @since 5.90
*/
framedView: false
onViewChanged: {
if (view) {
// Deliberately don't take separators into account, because those are opaque anyway
view.clip = Qt.binding(() => __headerContentVisible() || __footerContentVisible());
}
}
KCMUtils.ScrollView {
id: scroll
anchors.fill: parent
framedView: root.framedView
}
}
@@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-FileCopyrightText: 2020 David Redondo <kde@david.redondo.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kcmutils.private as KCMUtilsPrivate
/**
* SettingHighlighter automatically impacts the representation of an item based on
* the value of a setting. When you are using this item you need to manually
* manage whether the highlighting is enabled or not. For a higher level component
* see KCM.SettingStateBinding which will manage the state of the Item
* @since 6.0
*/
Loader {
id: root
active: typeof kcm !== "undefined" && root.target !== null
/**
* target: Item
* The graphical element whose appearance will be altered.
* If target is not set, it will try to find the visual parent item
*/
property Item target: root.parent
/**
* highlight: bool
* Whether the target will be highlighted.
*/
property bool highlight: false
sourceComponent: KCMUtilsPrivate.SettingHighlighterPrivate {
id: helper
highlight: root.highlight
target: root.target
defaultIndicatorVisible: kcm.defaultsIndicatorsVisible
}
}
@@ -0,0 +1,81 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kcmutils as KCMUtils
import org.kde.kcmutils.private as KCMUtilsPrivate
/**
* SettingStateBinding automatically impacts the representation
* of an item based on the state of a setting. It will disable
* the item if the setting is immutable and use a visual indicator
* for the state of the setting.
*
* This is a higher level convenience wrapper for KCMUtils.SettingStateProxy
* and KCMUtils.SettingStateIndicator.
*
* @since 6.0
*/
Loader {
id: root
active: typeof kcm !== "undefined" && root.target !== null
/**
* target: Item
* The graphical element whose state we want to manage based on a setting
* If target is not set, it will try to find the visual parent item
*/
property Item target: root.parent
/**
* configObject: KCoreConfigSkeleton
* The config object which will be monitored for setting state changes
*/
property alias configObject: settingState.configObject
/**
* settingName: string
* The name of the setting in the config object to be monitored
*/
property alias settingName: settingState.settingName
/**
* extraEnabledConditions: bool
* SettingStateBinding will manage the enabled property of the target
* based on the immutability state of the setting it represents. But,
* sometimes that enabled state needs to bind to other properties as
* well to be computed properly. This extra condition will thus be
* combined with the immutability state of the setting to determine
* the effective enabled state of the target.
*/
property bool extraEnabledConditions: true
/**
* nonDefaultHighlightVisible: bool
* Expose whether the non default highlight is visible.
* Allow one to implement highlight with custom items.
*/
readonly property bool nonDefaultHighlightVisible: root.active && root.item.highlight && kcm.defaultsIndicatorsVisible
Binding {
when: root.active
target: root.target
property: "enabled"
value: extraEnabledConditions && !settingState.immutable
}
KCMUtils.SettingStateProxy {
id: settingState
}
sourceComponent: KCMUtilsPrivate.SettingHighlighterPrivate {
id: helper
defaultIndicatorVisible: kcm.defaultsIndicatorsVisible
highlight: !settingState.defaulted
target: root.target
}
}
@@ -0,0 +1,133 @@
/*
SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* This component is intended to be used as root item for
* KCMs with arbitrary content. Often a Kirigami.FormLayout
* is used as main element.
* It is possible to specify a header and footer component.
* @code
* import org.kde.kcmutils as KCMUtils
* import org.kde.kirigami as Kirigami
*
* KCMUtils.SimpleKCM {
* Kirigami.FormLayout {
* TextField {
* Kirigami.FormData.label: "Label:"
* }
* TextField {
* Kirigami.FormData.label: "Label:"
* }
* }
* footer: Item {...}
* }
* @endcode
* @inherits org.kde.kirigami.ScrollablePage
*/
Kirigami.ScrollablePage {
id: root
readonly property int margins: 6 // Layout_ChildMarginWidth from Breeze
/**
* extraFooterTopPadding: bool
* @deprecated unused
* Default: false
*/
property bool extraFooterTopPadding: false
/**
* headerPaddingEnabled: bool
* Whether the contents of the header will have automatic padding around it.
* Should be disabled when using an InlineMessage or custom content item in
* the header that's intended to touch the window edges.
* Default: false
*/
property bool headerPaddingEnabled: false
function __itemVisible(item: Item): bool {
return item !== null && item.visible && item.implicitHeight > 0;
}
function __headerContentVisible(): bool {
return __itemVisible(headerParent.contentItem);
}
property bool __flickableOverflows: flickable.contentHeight + flickable.topMargin + flickable.bottomMargin > flickable.height
// Context properties are not reliable
title: (typeof kcm !== "undefined") ? kcm.name : ""
// Make pages fill the whole view by default
Kirigami.ColumnView.fillWidth: true
property bool sidebarMode: false
topPadding: margins
leftPadding: margins
rightPadding: margins
bottomPadding: margins
header: Column {
Kirigami.Padding {
id: headerParent
anchors {
left: parent.left
right: parent.right
}
height: root.__headerContentVisible() ? undefined : 0
padding: root.headerPaddingEnabled ? root.margins : 0
}
// When the header is visible, we need to add a line below to separate
// it from the view
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
}
visible: root.__headerContentVisible()
}
}
function __swapContentIntoContainer(property: string, container: Item): void {
const content = this[property];
const rootContainer = container.parent;
if (content && content !== rootContainer) {
// Revert the effect of repeated onHeaderChanged invocations
// during initialization in Page super-type.
content.anchors.top = undefined;
this[property] = rootContainer;
container.contentItem = content;
}
}
function __adoptOverlaySheets(): void {
// Search overlaysheets in contentItem, parent to root if found
for (const object of contentItem.data) {
if (object instanceof Kirigami.OverlaySheet) {
if (object.parent === null) {
object.parent = this;
}
data.push(object);
}
}
}
Component.onCompleted: {
__swapContentIntoContainer("header", headerParent);
__adoptOverlaySheets();
}
}
@@ -0,0 +1,206 @@
/*
SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* A copy of Kirigami.AboutPage adapted to KPluginMetadata instead of KAboutData
*/
ColumnLayout {
id: root
property var metaData
// Icon, name, version, and description
RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
source: root.metaData.iconName
fallback: "application-x-plasma"
}
ColumnLayout {
Layout.fillWidth: true
Kirigami.Heading {
Layout.fillWidth: true
text: root.metaData.version ? i18ndc("kcmutils6", "Plugin name and plugin version", "%1 %2", root.metaData.name, root.metaData.version) : root.metaData.name
wrapMode: Text.WordWrap
}
Kirigami.Heading {
Layout.fillWidth: true
level: 2
text: root.metaData.description
wrapMode: Text.WordWrap
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
}
// Copyright
Kirigami.Heading {
text: i18nd("kcmutils6", "Copyright")
}
QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
text: root.metaData.copyrightText
visible: text.length > 0
}
Kirigami.UrlButton {
Layout.leftMargin: Kirigami.Units.gridUnit
url: root.metaData.website ? root.metaData.website : ""
visible: url.length > 0
}
// License
RowLayout {
QQC2.Label {
text: i18nd("kcmutils6", "License:")
}
Kirigami.LinkButton {
text: root.metaData.license
onClicked: {
licenseSheet.text = root.metaData.licenseText
licenseSheet.title = root.metaData.license
licenseSheet.open()
}
}
}
// Authors, if any
Item {
implicitHeight: Kirigami.Units.largeSpacing
visible: repAuthors.visible
}
Kirigami.Heading {
text: i18nd("kcmutils6", "Authors")
visible: repAuthors.visible
}
Repeater {
id: repAuthors
visible: count > 0
model: root.metaData.authors
delegate: personDelegate
}
// Credits, if any
Item {
implicitHeight: Kirigami.Units.largeSpacing
visible: repCredits.visible
}
Kirigami.Heading {
text: i18nd("kcmutils6", "Credits")
visible: repCredits.visible
}
Repeater {
id: repCredits
visible: count > 0
model: root.metaData.otherContributors
delegate: personDelegate
}
// Translators, if any
Item {
implicitHeight: Kirigami.Units.largeSpacing
visible: repTranslators.visible
}
Kirigami.Heading {
text: i18nd("kcmutils6", "Translators")
visible: repTranslators.visible
}
Repeater {
id: repTranslators
visible: count > 0
model: root.metaData.translators
delegate: personDelegate
}
Component {
id: personDelegate
RowLayout {
height: implicitHeight + (Kirigami.Units.largeSpacing)
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
text: modelData.name
}
QQC2.ToolButton {
visible: modelData.emailAddress
icon.name: "mail-sent"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: i18nd("kcmutils6", "Send an email to %1", modelData.emailAddress)
onClicked: Qt.openUrlExternally("mailto:%1".arg(modelData.emailAddress))
}
QQC2.ToolButton {
visible: modelData.webAddress
icon.name: "globe"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: modelData.webAddress
onClicked: Qt.openUrlExternally(modelData.webAddress)
}
}
}
QQC2.Dialog {
id: licenseSheet
property alias text: licenseLabel.text
width: parent.width
height: parent.height
anchors.centerIn: parent
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
contentItem: QQC2.ScrollView {
id: scroll
Component.onCompleted: {
if (background) {
background.visible = true;
}
}
Flickable {
id: flickable
contentWidth: width
contentHeight: licenseLabel.contentHeight
clip: true
QQC2.Label {
id: licenseLabel
width: parent.width
wrapMode: Text.WordWrap
}
}
}
}
}
@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.Menu {
id: menu
function trigger() {
parent.clicked();
if (parent.actions.length > 0) {
popup(parent, thumbnail.x, thumbnail.y + thumbnail.height);
}
}
onClosed: parent.forceActiveFocus()
Repeater {
model: menu.parent.actions
delegate: QQC2.MenuItem {
required property Kirigami.Action modelData
text: modelData.text || modelData.tooltip
icon.name: modelData.icon.name
enabled: modelData.enabled
visible: modelData.visible
onTriggered: modelData.trigger()
}
}
}
@@ -0,0 +1,93 @@
/*
SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
GridView {
id: view
property int implicitCellWidth: Kirigami.Units.gridUnit * 10
property int implicitCellHeight: Math.round(implicitCellWidth / 1.6) + Kirigami.Units.gridUnit*3
/**
* Allow to highlight the selected item with Kirigami.Theme.neutralTextColor
*/
property bool neutralHighlight: false
onCurrentIndexChanged: positionViewAtIndex(currentIndex, GridView.Contain);
QtObject {
id: internal
readonly property int availableWidth: scroll.width - internal.scrollBarSpace - 4
readonly property int scrollBarSpace: scroll.QQC2.ScrollBar.vertical.width
}
anchors {
fill: parent
margins: 2
leftMargin: 2 + (scroll.QQC2.ScrollBar.vertical.visible ? 0 : Math.round(internal.scrollBarSpace / 2))
}
clip: true
activeFocusOnTab: true
Keys.onTabPressed: event => nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason)
Keys.onBacktabPressed: event => nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
cellWidth: Math.floor(internal.availableWidth / Math.max(Math.floor(internal.availableWidth / (implicitCellWidth + Kirigami.Units.gridUnit)), (Kirigami.Settings.isMobile ? 1 : 2)))
cellHeight: Kirigami.Settings.isMobile ? cellWidth/1.6 + Kirigami.Units.gridUnit : implicitCellHeight
keyNavigationEnabled: true
keyNavigationWraps: true
highlightMoveDuration: 0
remove: Transition {
ParallelAnimation {
NumberAnimation { property: "scale"; to: 0.5; duration: Kirigami.Units.longDuration }
NumberAnimation { property: "opacity"; to: 0.0; duration: Kirigami.Units.longDuration }
}
}
removeDisplaced: Transition {
SequentialAnimation {
// wait for the "remove" animation to finish
PauseAnimation { duration: Kirigami.Units.longDuration }
NumberAnimation { properties: "x,y"; duration: Kirigami.Units.longDuration }
}
}
Loader {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 8)
active: parent.count === 0 && !startupTimer.running
opacity: active && status === Loader.Ready ? 1 : 0
visible: opacity > 0
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
sourceComponent: Kirigami.PlaceholderMessage {
anchors.centerIn: parent
icon.name: "edit-none"
text: i18nd("kcmutils6", "No items found")
}
}
// The view can take a bit of time to initialize itself when the KCM first
// loads, during which time count is 0, which would cause the placeholder
// message to appear for a moment and then disappear. To prevent this, let's
// suppress it appearing for a moment after the KCM loads.
Timer {
id: startupTimer
interval: Kirigami.Units.humanMoment
running: false
}
Component.onCompleted: {
startupTimer.start()
}
}
@@ -0,0 +1,59 @@
/*
SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcmlauncher_p.h"
#include <KIO/CommandLauncherJob>
#include <KService>
void KCMLauncher::open(const QStringList &names) const
{
KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), names);
job->start();
}
void KCMLauncher::openSystemSettings(const QString &name, const QStringList &args) const
{
// The desktop filename is the same as the binary and icon
const QString systemSettings = QStringLiteral("systemsettings");
KIO::CommandLauncherJob *job = nullptr;
QStringList cmdline{name};
if (!args.isEmpty()) {
cmdline.append(QStringLiteral("--args"));
cmdline.append(args.join(QLatin1Char(' ')));
}
// Open in System Settings if it's available
if (KService::serviceByDesktopName(systemSettings)) {
job = new KIO::CommandLauncherJob(systemSettings, cmdline);
job->setDesktopName(systemSettings);
} else {
job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), cmdline);
}
job->start();
}
void KCMLauncher::openInfoCenter(const QString &name) const
{
const QString infoCenterDesktopFile = QStringLiteral("org.kde.kinfocenter");
const QString infoCenterbinary = QStringLiteral("kinfocenter");
KIO::CommandLauncherJob *job = nullptr;
// Open in Info Center if it's available
if (KService::serviceByDesktopName(infoCenterDesktopFile)) {
job = new KIO::CommandLauncherJob(infoCenterbinary, QStringList(name));
job->setDesktopName(infoCenterDesktopFile);
} else {
job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), QStringList(name));
}
job->start();
}
#include "moc_kcmlauncher_p.cpp"
@@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCMSHELL_H
#define KCMSHELL_H
#include <QObject>
#include <QQmlEngine>
class KCMLauncher : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public Q_SLOTS:
void open(const QStringList &names) const;
/**
* Opens the specified module in System Settings. Only a single KCM name may
* be provided.
*
* @code
* import QtQuick.Controls as QQC2
* import org.kde.kcmutils as KCMUtils
*
* QQC2.Button {
* onClicked: KCMUtils.KCMLauncher.openSystemSettings("kcm_kscreen")
* }
* @endcode
*
* @param name A single kcm name to open in System Settings. Opening multiple
* KCMs using this function is not supported; to do that, use KCMLauncher.open().
* @param args Additional arguments to pass to the module.
*
* @since 5.71
*/
void openSystemSettings(const QString &name, const QStringList &args = QStringList()) const;
/**
* Opens the specified module in InfCenter. Only a single KCM name may
* be provided.
*
* @code
* import QtQuick.Controls as QQC2
* import org.kde.kcmutils as KCMUtils
* QQC2.Button {
* onClicked: KCMUtils.KCMLauncher.openInfoCenter("kcm_energy")
* }
* @endcode
*
* @param name A single kcm name to open in Info Center. Opening multiple
* KCMs using this function is not supported; to do that, use KCMLauncher.open().
*
* @since 5.71
*/
void openInfoCenter(const QString &name) const;
};
#endif // KCMSHELL_H
@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2024 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCMUTILS_PRIVATE_QML_TYPES
#define KCMUTILS_PRIVATE_QML_TYPES
#include <QQmlEngine>
#include <kpluginproxymodel.h>
struct PluginProxyModelForeign {
Q_GADGET
QML_NAMED_ELEMENT(ProxyModel)
QML_FOREIGN(KPluginProxyModel)
};
#endif
@@ -0,0 +1,141 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "settinghighlighterprivate.h"
#include <QGuiApplication>
#include <QQmlContext>
namespace
{
QByteArray itemClassName(QQuickItem *item)
{
// Split since some exported types will be of the form: Foo_QMLTYPE_XX
const auto className = QByteArray(item->metaObject()->className()).split('_').first();
return className;
}
QList<QQuickItem *> findDescendantItems(QQuickItem *item)
{
const auto children = item->childItems();
auto result = children;
for (auto child : children) {
result += findDescendantItems(child);
}
return result;
}
QQuickItem *findStyleItem(QQuickItem *item)
{
const auto className = itemClassName(item);
auto descendant = findDescendantItems(item);
for (auto child : std::as_const(descendant)) {
if (className.contains("FontWidget") && itemClassName(child).contains("TextField")) {
return child->property("background").value<QQuickItem *>();
}
if (itemClassName(child).contains("GridViewInternal")) {
return child;
}
if (itemClassName(child).contains("GridView")) {
return child->property("view").value<QQuickItem *>();
}
if (itemClassName(child).contains("CheckIndicator") //
|| itemClassName(child).contains("KQuickStyleItem")) {
return child;
}
}
return nullptr;
}
} // namespace
QQuickItem *SettingHighlighterPrivate::target() const
{
return m_target;
}
void SettingHighlighterPrivate::setTarget(QQuickItem *target)
{
if (m_target == target) {
return;
}
if (m_target) {
disconnect(m_target, &QQuickItem::childrenChanged, this, &SettingHighlighterPrivate::updateTarget);
}
m_target = target;
if (m_target) {
connect(m_target, &QQuickItem::childrenChanged, this, &SettingHighlighterPrivate::updateTarget);
}
Q_EMIT targetChanged();
updateTarget();
}
bool SettingHighlighterPrivate::highlight() const
{
return m_highlight;
}
void SettingHighlighterPrivate::setHighlight(bool highlight)
{
if (m_highlight == highlight) {
return;
}
m_highlight = highlight;
Q_EMIT highlightChanged();
updateTarget();
}
bool SettingHighlighterPrivate::defaultIndicatorVisible() const
{
return m_enabled;
}
void SettingHighlighterPrivate::setDefaultIndicatorVisible(bool enabled)
{
if (m_enabled == enabled) {
return;
}
m_enabled = enabled;
Q_EMIT defaultIndicatorVisibleChanged(m_enabled);
updateTarget();
}
void SettingHighlighterPrivate::updateTarget()
{
if (!m_isComponentComplete) {
return;
}
if (!m_styleTarget && m_target) {
m_styleTarget = findStyleItem(m_target);
}
if (m_styleTarget) {
if (itemClassName(m_styleTarget).contains("GridViewInternal")) {
m_styleTarget->setProperty("neutralHighlight", m_highlight && m_enabled);
} else {
m_styleTarget->setProperty("_kde_highlight_neutral", m_highlight && m_enabled);
}
m_styleTarget->polish();
}
}
void SettingHighlighterPrivate::componentComplete()
{
m_isComponentComplete = true;
updateTarget();
}
#include "moc_settinghighlighterprivate.cpp"
@@ -0,0 +1,56 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef SETTINGSHIGHLIGHTERPRIVATE_H
#define SETTINGSHIGHLIGHTERPRIVATE_H
#include <QPointer>
#include <QQmlParserStatus>
#include <QQuickItem>
class SettingHighlighterPrivate : public QObject, public QQmlParserStatus
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
Q_PROPERTY(bool highlight READ highlight WRITE setHighlight NOTIFY highlightChanged)
Q_PROPERTY(bool defaultIndicatorVisible READ defaultIndicatorVisible WRITE setDefaultIndicatorVisible NOTIFY defaultIndicatorVisibleChanged)
Q_INTERFACES(QQmlParserStatus)
public:
using QObject::QObject;
QQuickItem *target() const;
void setTarget(QQuickItem *target);
bool highlight() const;
void setHighlight(bool highlight);
bool defaultIndicatorVisible() const;
void setDefaultIndicatorVisible(bool enabled);
Q_SIGNALS:
void targetChanged();
void highlightChanged();
void defaultIndicatorVisibleChanged(bool enabled);
private:
void updateTarget();
void classBegin() override
{
}
void componentComplete() override;
bool m_isComponentComplete = false;
QPointer<QQuickItem> m_target = nullptr;
QPointer<QQuickItem> m_styleTarget = nullptr;
bool m_highlight = false;
bool m_enabled = false;
};
#endif
@@ -0,0 +1,125 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "settingstateproxy.h"
#include "kcmutils_debug.h"
#include <QDebug>
#include <QMetaMethod>
KCoreConfigSkeleton *SettingStateProxy::configObject() const
{
return m_configObject;
}
void SettingStateProxy::setConfigObject(KCoreConfigSkeleton *configObject)
{
if (m_configObject == configObject) {
return;
}
if (m_configObject) {
m_configObject->disconnect(this);
}
m_configObject = configObject;
Q_EMIT configObjectChanged();
updateState();
connectSetting();
}
QString SettingStateProxy::settingName() const
{
return m_settingName;
}
void SettingStateProxy::setSettingName(const QString &settingName)
{
if (m_settingName == settingName) {
return;
}
if (m_configObject) {
m_configObject->disconnect(this);
}
m_settingName = settingName;
Q_EMIT settingNameChanged();
updateState();
connectSetting();
}
bool SettingStateProxy::isImmutable() const
{
return m_immutable;
}
bool SettingStateProxy::isDefaulted() const
{
return m_defaulted;
}
void SettingStateProxy::updateState()
{
const auto item = m_configObject ? m_configObject->findItem(m_settingName) : nullptr;
const auto immutable = item ? item->isImmutable() : false;
const auto defaulted = item ? item->isDefault() : true;
if (m_immutable != immutable) {
m_immutable = immutable;
Q_EMIT immutableChanged();
}
if (m_defaulted != defaulted) {
m_defaulted = defaulted;
Q_EMIT defaultedChanged();
}
}
void SettingStateProxy::connectSetting()
{
const auto item = m_configObject ? m_configObject->findItem(m_settingName) : nullptr;
if (!item) {
return;
}
const auto updateStateSlotIndex = metaObject()->indexOfMethod("updateState()");
Q_ASSERT(updateStateSlotIndex >= 0);
const auto updateStateSlot = metaObject()->method(updateStateSlotIndex);
Q_ASSERT(updateStateSlot.isValid());
const auto itemHasSignals = dynamic_cast<KConfigCompilerSignallingItem *>(item) || dynamic_cast<KPropertySkeletonItem *>(item);
if (!itemHasSignals) {
qCWarning(KCMUTILS_LOG) << "Attempting to use SettingStateProxy with a non signalling item:" << m_settingName;
return;
}
const auto propertyName = [this] {
auto name = m_settingName;
if (name.at(0).isUpper()) {
name[0] = name[0].toLower();
}
return name.toUtf8();
}();
const auto metaObject = m_configObject->metaObject();
const auto propertyIndex = metaObject->indexOfProperty(propertyName.constData());
Q_ASSERT(propertyIndex >= 0);
const auto property = metaObject->property(propertyIndex);
Q_ASSERT(property.isValid());
if (!property.hasNotifySignal()) {
qCWarning(KCMUTILS_LOG) << "Attempting to use SettingStateProxy with a non notifying property:" << propertyName;
return;
}
const auto changedSignal = property.notifySignal();
Q_ASSERT(changedSignal.isValid());
connect(m_configObject, changedSignal, this, updateStateSlot);
connect(m_configObject, &KCoreConfigSkeleton::configChanged, this, &SettingStateProxy::updateState);
}
#include "moc_settingstateproxy.cpp"
@@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef SETTINGSTATEPROXY_H
#define SETTINGSTATEPROXY_H
#include <QObject>
#include <QPointer>
#include <QQmlEngine>
#include <KCoreConfigSkeleton>
/**
* This element allows to represent in a declarative way the
* state of a particular setting in a config object.
*
* @since 5.73
*/
class SettingStateProxy : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* The config object which will be monitored for setting state changes
*/
Q_PROPERTY(KCoreConfigSkeleton *configObject READ configObject WRITE setConfigObject NOTIFY configObjectChanged)
/**
* The name of the setting in the config object
*/
Q_PROPERTY(QString settingName READ settingName WRITE setSettingName NOTIFY settingNameChanged)
/**
* Indicates if the setting is marked as immutable
*/
Q_PROPERTY(bool immutable READ isImmutable NOTIFY immutableChanged)
/**
* Indicates if the setting differs from its default value
*/
Q_PROPERTY(bool defaulted READ isDefaulted NOTIFY defaultedChanged)
public:
using QObject::QObject;
KCoreConfigSkeleton *configObject() const;
void setConfigObject(KCoreConfigSkeleton *configObject);
QString settingName() const;
void setSettingName(const QString &settingName);
bool isImmutable() const;
bool isDefaulted() const;
Q_SIGNALS:
void configObjectChanged();
void settingNameChanged();
void immutableChanged();
void defaultedChanged();
private Q_SLOTS:
void updateState();
private:
void connectSetting();
QPointer<KCoreConfigSkeleton> m_configObject;
QString m_settingName;
bool m_immutable = false;
bool m_defaulted = true;
};
#endif
@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2024 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCMUTILS_QML_TYPES
#define KCMUTILS_QML_TYPES
#include <QQmlEngine>
#include <kquickconfigmodule.h>
struct ConfigModuleForeign {
Q_GADGET
QML_NAMED_ELEMENT(ConfigModule)
QML_UNCREATABLE("")
QML_FOREIGN(KQuickConfigModule)
};
#endif