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,121 @@
# SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-3-Clause
add_library(kcmutils_logging_STATIC STATIC)
set_target_properties(kcmutils_logging_STATIC PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(kcmutils_logging_STATIC PRIVATE Qt6::Core)
ecm_qt_declare_logging_category(kcmutils_logging_STATIC
HEADER kcmutils_debug.h
IDENTIFIER KCMUTILS_LOG
CATEGORY_NAME kf.kcmutils
DESCRIPTION "KCMUtils"
EXPORT KCMUTILS
)
add_subdirectory(core)
#####add_subdirectory(qml)
#####add_subdirectory(quick)
########### kcmutils ###############
set(kcmutils_LIB_SRCS
kcmoduleloader.cpp
kcmoduleloader.h
kcmultidialog.cpp
kcmultidialog.h
kcmultidialog_p.h
kpluginwidget.cpp
kcmodule.cpp
kcmodule.h
)
add_library(KF6KCMUtils ${kcmutils_LIB_SRCS})
set_target_properties(KF6KCMUtils PROPERTIES
VERSION ${KCMUTILS_VERSION}
SOVERSION ${KCMUTILS_SOVERSION}
EXPORT_NAME KCMUtils
)
if (WITH_KAUTH)
set(WITH_KAUTH_DEFINE_VALUE 1)
else()
set(WITH_KAUTH_DEFINE_VALUE 0)
endif()
set(define_with_kauth_code "#define KCMUTILS_WITH_KAUTH ${WITH_KAUTH_DEFINE_VALUE}\n")
ecm_generate_export_header(KF6KCMUtils
BASE_NAME KCMUtils
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
CUSTOM_CONTENT_FROM_VARIABLE define_with_kauth_code
)
target_include_directories(KF6KCMUtils INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtils>")
target_link_libraries(KF6KCMUtils
PUBLIC
Qt6::Widgets
KF6::CoreAddons # KPluginMetaData
KF6::ConfigWidgets # KPageDialog
PRIVATE
kcmutils_proxy_model
KF6::GuiAddons # KIconUtils
KF6::I18n
KF6::ItemViews # KWidgetItemDelegate
KF6::XmlGui # KAboutApplicationDialog
kcmutils_logging_STATIC
)
ecm_generate_headers(KCMUtils_HEADERS
HEADER_NAMES
KCModuleLoader
KCMultiDialog
KPluginWidget
KCModule
REQUIRED_HEADERS KCMUtils_HEADERS
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kcmutils_export.h
${KCMUtils_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtils COMPONENT Devel
)
install(TARGETS KF6KCMUtils EXPORT KF6KCMUtilsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
if(BUILD_QCH)
ecm_add_qch(
KF6KCMUtils_QCH
NAME KCMUtils
BASE_NAME KF6KCMUtils
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KCMUtils_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Widgets_QCH
KF6ConfigWidgets_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KCMUTILS_EXPORT
KCMUTILS_DEPRECATED
KCMUTILS_DEPRECATED_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
ecm_qt_install_logging_categories(
EXPORT KCMUTILS
FILE kcmutils.categories
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
#####add_subdirectory(kcmshell)
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
if [ -n "$lst" ] ; then
$EXTRACTRC $lst >> rc.cpp
fi
# Extract strings from all source files.
# If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation
# system, use $EXTRACT_TR_STRINGS.
$XGETTEXT ` find -not -path "./kcmshell/*" -name \*.cpp -o -not -path "./kcmshell/*" -name \*.h -o -name \*.qml` -o $podir/kcmutils6.pot
@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-3-Clause
add_library(KF6KCMUtilsCore)
qt_extract_metatypes(KF6KCMUtilsCore)
set_target_properties(KF6KCMUtilsCore PROPERTIES
VERSION ${KCMUTILS_VERSION}
SOVERSION ${KCMUTILS_SOVERSION}
EXPORT_NAME KCMUtilsCore
)
ecm_qt_declare_logging_category(KF6KCMUtilsCore
HEADER kcmutilscore_debug.h
IDENTIFIER KCMUTILS_CORE_LOG
CATEGORY_NAME kf.kcmutils.core
DESCRIPTION "KCMUtilsCore"
EXPORT KCMUTILS_CORE
)
ecm_generate_export_header(KF6KCMUtilsCore
BASE_NAME KCMUtilsCore
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME KCMUtils
DEPRECATED_BASE_VERSION 0
)
target_include_directories(KF6KCMUtilsCore PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/..>" # module version header
)
target_include_directories(KF6KCMUtilsCore INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtilsCore>"
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtils>" # module version header
)
target_sources(KF6KCMUtilsCore PRIVATE
kpluginmodel.cpp
kabstractconfigmodule.cpp
kcmoduledata.cpp
kcmoduledata.h
)
target_link_libraries(KF6KCMUtilsCore
PUBLIC
KF6::CoreAddons
PRIVATE
KF6::ConfigCore
KF6::ItemViews
)
ecm_generate_headers(KCMUtilsCore_HEADERS
HEADER_NAMES
KPluginModel
KAbstractConfigModule
KCModuleData
REQUIRED_HEADERS KCMUtilsCore_HEADERS
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kcmutilscore_export.h
${KCMUtilsCore_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtilsCore COMPONENT Devel
)
install(TARGETS KF6KCMUtilsCore EXPORT KF6KCMUtilsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
add_library(kcmutils_proxy_model STATIC kpluginproxymodel.cpp)
qt_extract_metatypes(kcmutils_proxy_model)
# Needed to link this static lib to shared libs
set_property(TARGET kcmutils_proxy_model PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_libraries(kcmutils_proxy_model PUBLIC KF6KCMUtilsCore KF6::ItemViews)
if(NOT BUILD_SHARED_LIBS)
install(TARGETS kcmutils_proxy_model EXPORT KF6KCMUtilsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
endif()
@@ -0,0 +1,145 @@
/*
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kabstractconfigmodule.h"
#include <kpluginmetadata.h>
class KAbstractConfigModulePrivate
{
public:
KAbstractConfigModulePrivate(const KPluginMetaData &data)
: m_data(data)
{
}
const KPluginMetaData m_data;
QString m_rootOnlyMessage;
QString m_errorString;
bool m_useRootOnlyMessage = false;
bool m_needsSave = false;
bool m_representsDefaults = false;
bool m_defaultsIndicatorVisible = false;
QString m_authActionName;
KAbstractConfigModule::Buttons m_buttons = KAbstractConfigModule::Help | KAbstractConfigModule::Default | KAbstractConfigModule::Apply;
};
KAbstractConfigModule::KAbstractConfigModule(QObject *parent, const KPluginMetaData &metaData)
: QObject(parent)
, d(new KAbstractConfigModulePrivate(metaData))
{
}
KAbstractConfigModule::~KAbstractConfigModule() = default;
KAbstractConfigModule::Buttons KAbstractConfigModule::buttons() const
{
return d->m_buttons;
}
void KAbstractConfigModule::setButtons(const KAbstractConfigModule::Buttons buttons)
{
if (d->m_buttons == buttons) {
return;
}
d->m_buttons = buttons;
Q_EMIT buttonsChanged();
}
bool KAbstractConfigModule::needsAuthorization() const
{
return !d->m_authActionName.isEmpty();
}
QString KAbstractConfigModule::name() const
{
return d->m_data.name();
}
QString KAbstractConfigModule::description() const
{
return d->m_data.description();
}
void KAbstractConfigModule::setAuthActionName(const QString &name)
{
if (d->m_authActionName == name) {
return;
}
d->m_authActionName = name;
Q_EMIT authActionNameChanged();
}
QString KAbstractConfigModule::authActionName() const
{
return d->m_authActionName;
}
void KAbstractConfigModule::load()
{
setNeedsSave(false);
}
void KAbstractConfigModule::save()
{
setNeedsSave(false);
}
void KAbstractConfigModule::defaults()
{
}
void KAbstractConfigModule::setNeedsSave(bool needs)
{
if (needs == d->m_needsSave) {
return;
}
d->m_needsSave = needs;
Q_EMIT needsSaveChanged();
}
bool KAbstractConfigModule::needsSave() const
{
return d->m_needsSave;
}
void KAbstractConfigModule::setRepresentsDefaults(bool defaults)
{
if (defaults == d->m_representsDefaults) {
return;
}
d->m_representsDefaults = defaults;
Q_EMIT representsDefaultsChanged();
}
bool KAbstractConfigModule::representsDefaults() const
{
return d->m_representsDefaults;
}
bool KAbstractConfigModule::defaultsIndicatorsVisible() const
{
return d->m_defaultsIndicatorVisible;
}
void KAbstractConfigModule::setDefaultsIndicatorsVisible(bool visible)
{
if (d->m_defaultsIndicatorVisible == visible) {
return;
}
d->m_defaultsIndicatorVisible = visible;
Q_EMIT defaultsIndicatorsVisibleChanged();
}
KPluginMetaData KAbstractConfigModule::metaData() const
{
return d->m_data;
}
#include "moc_kabstractconfigmodule.cpp"
@@ -0,0 +1,237 @@
/*
SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KABSTRACTCONFIGMODULE_H
#define KABSTRACTCONFIGMODULE_H
#include "kcmutilscore_export.h"
#include <QObject>
#include <memory>
class KPluginMetaData;
class KAbstractConfigModulePrivate;
/**
* Base class for QML and QWidgets config modules.
*
* @author Alexander Lohnau
* @since 6.0
*/
class KCMUTILSCORE_EXPORT KAbstractConfigModule : public QObject
{
Q_OBJECT
Q_PROPERTY(KAbstractConfigModule::Buttons buttons READ buttons WRITE setButtons NOTIFY buttonsChanged)
Q_PROPERTY(bool defaultsIndicatorsVisible READ defaultsIndicatorsVisible WRITE setDefaultsIndicatorsVisible NOTIFY defaultsIndicatorsVisibleChanged)
Q_PROPERTY(bool needsAuthorization READ needsAuthorization NOTIFY authActionNameChanged)
Q_PROPERTY(bool representsDefaults READ representsDefaults WRITE setRepresentsDefaults NOTIFY representsDefaultsChanged)
Q_PROPERTY(bool needsSave READ needsSave WRITE setNeedsSave NOTIFY needsSaveChanged)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString description READ description CONSTANT)
public:
/**
* An enumeration type for the buttons used by this module.
* You should only use Help, Default and Apply. The rest is obsolete.
* NoAdditionalButton can be used when we do not want have other button that Ok Cancel
*
* @see ConfigModule::buttons @see ConfigModule::setButtons
*/
enum Button {
NoAdditionalButton = 0,
Help = 1,
Default = 2,
Apply = 4,
Export = 8,
};
Q_ENUM(Button)
Q_DECLARE_FLAGS(Buttons, Button)
Q_FLAG(Buttons)
explicit KAbstractConfigModule(QObject *parent, const KPluginMetaData &metaData);
~KAbstractConfigModule() override;
/**
* @brief Set if the module's save() method requires authorization to be executed
*
* It will still have to execute the action itself using the KAuth library, so
* this method is not technically needed to perform the action, but
* using this method will ensure that hosting
* applications like System Settings or kcmshell behave correctly.
*
* @param action the action that will be used by this ConfigModule
*/
void setAuthActionName(const QString &action);
/**
* Returns the action previously set with setAuthActionName(). By default this will be a empty string.
*
* @return The action that has to be authorized to execute the save() method.
*/
QString authActionName() const;
/**
* The auth action name has changed
*/
Q_SIGNAL void authActionNameChanged();
/**
* Set this property to true when the user changes something in the module,
* signaling that a save (such as user pressing Ok or Apply) is needed.
*/
void setNeedsSave(bool needs);
/**
* True when the module has something changed and needs save.
*/
bool needsSave() const;
/**
* Indicate that the state of the modules contents has changed.
*
* This signal is emitted whenever the state of the configuration
* shown in the module changes. It allows the module container to
* keep track of unsaved changes.
*/
Q_SIGNAL void needsSaveChanged();
/**
* Set this property to true when the user sets the state of the module
* to the default settings (e.g. clicking Defaults would do nothing).
*/
void setRepresentsDefaults(bool defaults);
/**
* True when the module state represents the default settings.
*/
bool representsDefaults() const;
/**
* Indicate that the state of the modules contents has changed
* in a way that it might represents the defaults settings, or
* stopped representing them.
*/
Q_SIGNAL void representsDefaultsChanged();
/**
* Sets the buttons to display.
*
* Help: shows a "Help" button.
*
* Default: shows a "Use Defaults" button.
*
* Apply: shows an "Ok", "Apply" and "Cancel" button.
*
* If Apply is not specified, kcmshell will show a "Close" button.
*
* @see ConfigModule::buttons
*/
void setButtons(const Buttons btn);
/**
* Indicate which buttons will be used.
*
* The return value is a value or'ed together from
* the Button enumeration type.
*
* @see ConfigModule::setButtons
*/
Buttons buttons() const;
/**
* Buttons to display changed.
*/
Q_SIGNAL void buttonsChanged();
/**
* @return true, if the authActionName is not empty
* @sa setAuthActionName
*/
bool needsAuthorization() const;
/**
* @returns the name of the config module
*/
QString name() const;
/**
* @returns the description of the config module
*/
QString description() const;
/**
* Change defaultness indicator visibility
* @param visible
*/
void setDefaultsIndicatorsVisible(bool visible);
/**
* @returns defaultness indicator visibility
*/
bool defaultsIndicatorsVisible() const;
/**
* Emitted when kcm need to display indicators for field with non default value
*/
Q_SIGNAL void defaultsIndicatorsVisibleChanged();
/**
* Returns the metaData that was used when instantiating the plugin
*/
KPluginMetaData metaData() const;
/**
* This signal will be emited by a single-instance application (such as
* System Settings) to request activation and update arguments to a module
* that is already running
*
* The module should connect to this signal when it needs to handle
* the activation request and specially the new arguments
*
* @param args A list of arguments that get passed to the module
*/
Q_SIGNAL void activationRequested(const QVariantList &args);
/**
* Load the configuration data into the module.
*
* The load method sets the user interface elements of the
* module to reflect the current settings stored in the
* configuration files.
*
* This method is invoked whenever the module should read its configuration
* (most of the times from a config file) and update the user interface.
* This happens when the user clicks the "Reset" button in the control
* center, to undo all of his changes and restore the currently valid
* settings. It is also called right after construction.
*/
virtual void load();
/**
* The save method stores the config information as shown
* in the user interface in the config files.
*
* This method is called when the user clicks "Apply" or "Ok".
*
*/
virtual void save();
/**
* Sets the configuration to default values.
*
* This method is called when the user clicks the "Default"
* button.
*/
virtual void defaults();
private:
const std::unique_ptr<KAbstractConfigModulePrivate> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KAbstractConfigModule::Buttons)
#endif // KABSTRACTCONFIGMODULE_H
@@ -0,0 +1,82 @@
/*
SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kcmoduledata.h"
#include <QList>
#include <QPointer>
#include <KCoreConfigSkeleton>
class KCModuleDataPrivate
{
public:
explicit KCModuleDataPrivate(KCModuleData *probe)
: _q(probe)
{
}
KCModuleData *_q;
QList<QPointer<KCoreConfigSkeleton>> _skeletons;
};
KCModuleData::KCModuleData(QObject *parent)
: QObject(parent)
, d(new KCModuleDataPrivate(this))
{
connect(this, &KCModuleData::aboutToLoad, this, &KCModuleData::loaded);
QMetaObject::invokeMethod(
this,
[this] {
aboutToLoad(QPrivateSignal());
},
Qt::QueuedConnection);
}
KCModuleData::~KCModuleData() = default;
void KCModuleData::registerSkeleton(KCoreConfigSkeleton *skeleton)
{
if (!skeleton || d->_skeletons.contains(skeleton)) {
return;
}
d->_skeletons.append(skeleton);
}
bool KCModuleData::isDefaults() const
{
bool defaults = true;
for (const auto &skeleton : std::as_const(d->_skeletons)) {
defaults &= skeleton->isDefaults();
}
return defaults;
}
void KCModuleData::revertToDefaults()
{
for (const auto &skeleton : std::as_const(d->_skeletons)) {
skeleton->useDefaults(true);
skeleton->save();
}
}
void KCModuleData::autoRegisterSkeletons()
{
const auto skeletons = findChildren<KCoreConfigSkeleton *>();
for (auto *skeleton : skeletons) {
registerSkeleton(skeleton);
}
}
bool KCModuleData::matchesQuery(const QString &query) const
{
// Currently not implemented, here for future use case
Q_UNUSED(query)
return false;
}
#include "moc_kcmoduledata.cpp"
@@ -0,0 +1,83 @@
/*
SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@enioka.com>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCMODULEDATA_H
#define KCMODULEDATA_H
#include <QObject>
#include <QVariantList>
#include <kcmutilscore_export.h>
#include <memory>
class KCModuleDataPrivate;
class KCoreConfigSkeleton;
/**
* @short A base class that offers information about a KCModule state
*
* @author Benjamin Port <benjamin.port@enioka.com>
*
* @since 5.74
*/
class KCMUTILSCORE_EXPORT KCModuleData : public QObject
{
Q_OBJECT
public:
explicit KCModuleData(QObject *parent = nullptr);
~KCModuleData() override;
/**
* Checks if the configuration is identical to the default one.
*
* @return @c true if the module configuration is in the default state, @c false otherwise
*/
virtual bool isDefaults() const;
/**
* Revert module to default values and save them.
*/
virtual void revertToDefaults();
/**
* Checks if this module matches a given query.
* @param query the text user search for, it is not expected to be a regex pattern but a full text search.
* @return @c true if this module matches a given query, @c false otherwise
*/
virtual bool matchesQuery(const QString &query) const;
Q_SIGNALS:
/**
* This signal is emitted when KCModuleData is loaded.
*/
void loaded();
/**
* Internal use
*
* Triggers the emit of @see loaded() signal. This is the default behavior.
* To handle when loaded() is emitted in subclass, disconnect this signal in derived constructor.
*/
void aboutToLoad(QPrivateSignal);
protected Q_SLOTS:
/**
* Allow to register manually skeleton class.
* Used by derived class when automatic discovery is not possible.
*/
void registerSkeleton(KCoreConfigSkeleton *skeleton);
/**
* Automatically register child skeletons
* Call it in your subclass constructor after skeleton creation
*/
void autoRegisterSkeletons();
private:
const std::unique_ptr<KCModuleDataPrivate> d;
friend class KCModuleDataPrivate;
};
#endif
@@ -0,0 +1,299 @@
/*
SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginmodel.h"
#include "kpluginproxymodel.h"
#include <QPluginLoader>
#include <KCategorizedSortFilterProxyModel>
#include <KConfigGroup>
#include <utility>
#include "kcmutilscore_debug.h"
class KPluginModelPrivate
{
public:
bool isDefaulted()
{
return std::all_of(m_plugins.cbegin(), m_plugins.cend(), [this](const KPluginMetaData &data) {
return isPluginEnabled(data) == data.isEnabledByDefault();
});
}
bool isPluginEnabled(const KPluginMetaData &plugin) const
{
auto pendingState = m_pendingStates.constFind(plugin.pluginId());
if (pendingState != m_pendingStates.constEnd()) {
return pendingState.value();
}
if (m_config.isValid()) {
return m_config.readEntry(plugin.pluginId() + QLatin1String("Enabled"), plugin.isEnabledByDefault());
}
return plugin.isEnabledByDefault();
}
KPluginMetaData findConfig(const KPluginMetaData &plugin) const
{
const QString metaDataKCM = plugin.value(QStringLiteral("X-KDE-ConfigModule"));
if (!metaDataKCM.isEmpty()) {
const QString absoluteKCMPath = QPluginLoader(metaDataKCM).fileName();
// If we have a static plugin the file does not exist on disk
// instead we query in the plugin namespace
if (absoluteKCMPath.isEmpty()) {
const int idx = metaDataKCM.lastIndexOf(QLatin1Char('/'));
const QString pluginNamespace = metaDataKCM.left(idx);
const QString pluginId = metaDataKCM.mid(idx + 1);
return KPluginMetaData::findPluginById(pluginNamespace, pluginId);
} else {
return KPluginMetaData(plugin.rawData(), absoluteKCMPath);
}
}
return KPluginMetaData();
}
QList<KPluginMetaData> m_plugins;
QSet<KPluginMetaData> m_unsortablePlugins;
QHash<QString, KPluginMetaData> m_pluginKcms;
KConfigGroup m_config;
QList<QString> m_orderedCategories; // Preserve order of categories in which they were added
QHash<QString, QString> m_categoryLabels;
QHash<QString, bool> m_pendingStates;
};
KPluginModel::KPluginModel(QObject *parent)
: QAbstractListModel(parent)
, d(new KPluginModelPrivate())
{
}
KPluginModel::~KPluginModel() = default;
QVariant KPluginModel::data(const QModelIndex &index, int role) const
{
const KPluginMetaData &plugin = d->m_plugins[index.row()];
switch (role) {
case Roles::NameRole:
return plugin.name();
case Roles::DescriptionRole:
return plugin.description();
case Roles::IconRole:
return plugin.iconName();
case Roles::EnabledRole:
return d->isPluginEnabled(plugin);
case Roles::IsChangeableRole:
if (d->m_unsortablePlugins.contains(plugin)) {
return false;
}
if (d->m_config.isValid()) {
return !d->m_config.isEntryImmutable(plugin.pluginId() + QLatin1String("Enabled"));
}
return true;
case MetaDataRole:
return QVariant::fromValue(plugin);
case KCategorizedSortFilterProxyModel::CategoryDisplayRole:
case KCategorizedSortFilterProxyModel::CategorySortRole:
return d->m_categoryLabels[plugin.pluginId()];
case ConfigRole:
return QVariant::fromValue(d->m_pluginKcms.value(plugin.pluginId()));
case IdRole:
return plugin.pluginId();
case EnabledByDefaultRole:
return plugin.isEnabledByDefault();
case SortableRole:
return !d->m_unsortablePlugins.contains(plugin);
}
return {};
}
bool KPluginModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Roles::EnabledRole) {
const QString pluginId = d->m_plugins[index.row()].pluginId();
// If we already have a pending state and the user reverts it remove it from the map
auto pendingStateIt = d->m_pendingStates.constFind(pluginId);
if (pendingStateIt != d->m_pendingStates.constEnd()) {
if (pendingStateIt.value() != value.toBool()) {
d->m_pendingStates.erase(pendingStateIt);
}
} else {
d->m_pendingStates[pluginId] = value.toBool();
}
Q_EMIT dataChanged(index, index, {Roles::EnabledRole});
Q_EMIT defaulted(d->isDefaulted());
Q_EMIT isSaveNeededChanged();
return true;
}
return false;
}
int KPluginModel::rowCount(const QModelIndex & /*parent*/) const
{
return d->m_plugins.count();
}
QHash<int, QByteArray> KPluginModel::roleNames() const
{
return {
{KCategorizedSortFilterProxyModel::CategoryDisplayRole, "category"},
{Roles::NameRole, "name"},
{Roles::IconRole, "icon"},
{Roles::EnabledRole, "enabled"},
{Roles::DescriptionRole, "description"},
{Roles::IsChangeableRole, "changable"},
{Roles::EnabledByDefaultRole, "enabledByDefault"},
{Roles::MetaDataRole, "metaData"},
{Roles::ConfigRole, "config"},
};
};
void KPluginModel::addUnsortablePlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel)
{
d->m_unsortablePlugins.unite(QSet(newPlugins.begin(), newPlugins.end()));
addPlugins(newPlugins, categoryLabel);
}
bool KPluginModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
{
if (sourceParent.isValid() || destinationParent.isValid()) {
return false;
}
if ((sourceRow + count - 1) >= d->m_plugins.size()) {
return false;
}
const bool isMoveDown = destinationChild > sourceRow;
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, isMoveDown ? destinationChild + 1 : destinationChild)) {
return false;
}
for (int i = 0; i < count; i++) {
d->m_plugins.insert(destinationChild, d->m_plugins.takeAt(sourceRow + i));
}
endMoveRows();
return true;
}
void KPluginModel::addPlugins(const QList<KPluginMetaData> &newPlugins, const QString &categoryLabel)
{
beginInsertRows({}, d->m_plugins.size(), d->m_plugins.size() + newPlugins.size() - 1);
d->m_orderedCategories << categoryLabel;
d->m_plugins.append(newPlugins);
for (const KPluginMetaData &plugin : newPlugins) {
d->m_categoryLabels[plugin.pluginId()] = categoryLabel;
d->m_pluginKcms.insert(plugin.pluginId(), d->findConfig(plugin));
}
endInsertRows();
Q_EMIT defaulted(d->isDefaulted());
}
void KPluginModel::removePlugin(const KPluginMetaData &data)
{
if (const int index = d->m_plugins.indexOf(data); index != -1) {
beginRemoveRows({}, index, index);
d->m_plugins.removeAt(index);
d->m_unsortablePlugins.remove(data);
endRemoveRows();
}
}
void KPluginModel::setConfig(const KConfigGroup &config)
{
d->m_config = config;
if (!d->m_plugins.isEmpty()) {
Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole, Roles::IsChangeableRole});
}
}
void KPluginModel::clear()
{
if (d->m_plugins.isEmpty()) {
return;
}
beginRemoveRows({}, 0, d->m_plugins.size() - 1);
d->m_plugins.clear();
d->m_pluginKcms.clear();
// In case of the "Reset"-button of the KCMs load is called again with the goal
// of discarding all local changes. Consequently, the pending states have to be cleared here.
d->m_pendingStates.clear();
endRemoveRows();
}
void KPluginModel::save()
{
if (d->m_config.isValid()) {
for (auto it = d->m_pendingStates.cbegin(); it != d->m_pendingStates.cend(); ++it) {
d->m_config.writeEntry(it.key() + QLatin1String("Enabled"), it.value());
}
d->m_config.sync();
}
d->m_pendingStates.clear();
}
KPluginMetaData KPluginModel::findConfigForPluginId(const QString &pluginId) const
{
for (const KPluginMetaData &plugin : std::as_const(d->m_plugins)) {
if (plugin.pluginId() == pluginId) {
return d->findConfig(plugin);
}
}
return KPluginMetaData();
}
void KPluginModel::load()
{
if (!d->m_config.isValid()) {
return;
}
d->m_pendingStates.clear();
Q_EMIT dataChanged(index(0, 0), index(d->m_plugins.size() - 1, 0), {Roles::EnabledRole});
}
void KPluginModel::defaults()
{
for (int pluginIndex = 0, count = d->m_plugins.count(); pluginIndex < count; ++pluginIndex) {
const KPluginMetaData plugin = d->m_plugins.at(pluginIndex);
const bool changed = d->isPluginEnabled(plugin) != plugin.isEnabledByDefault();
if (changed) {
// If the entry was marked as changed, but we flip the value it is unchanged again
if (d->m_pendingStates.remove(plugin.pluginId()) == 0) {
// If the entry was not changed before, we have to mark it as changed
d->m_pendingStates.insert(plugin.pluginId(), plugin.isEnabledByDefault());
}
Q_EMIT dataChanged(index(pluginIndex, 0), index(pluginIndex, 0), {Roles::EnabledRole});
}
}
Q_EMIT defaulted(true);
}
bool KPluginModel::isSaveNeeded()
{
return !d->m_pendingStates.isEmpty();
}
QStringList KPluginModel::getOrderedCategoryLabels()
{
return d->m_orderedCategories;
}
#include "moc_kpluginmodel.cpp"
@@ -0,0 +1,137 @@
/*
SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINMODEL_H
#define KPLUGINMODEL_H
#include "kcmutilscore_export.h"
#include <QAbstractListModel>
#include <QList>
#include <KPluginMetaData>
#include <memory>
class KConfigGroup;
class KPluginModelPrivate;
/**
* @class KPluginModel kpluginmodel.h KPluginModel
* A model that provides a list of available plugins and allows to disable/enable them.
*
* Plugins need to define the @c X-KDE-ConfigModule property for their config modules to be found.
* The value for this property is the namespace and file name of the KCM for the plugin.
* An example value is "kf6/krunner/kcms/kcm_krunner_charrunner", "kf6/krunner/kcms" is the namespace
* and "kcm_krunner_charrunner" the file name. The loaded KCMs don't need any embedded JSON metadata.
*
* @see KPluginWidget
*
* @since 5.94
*/
class KCMUTILSCORE_EXPORT KPluginModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::DisplayRole,
IconRole = Qt::DecorationRole,
EnabledRole = Qt::CheckStateRole,
DescriptionRole = Qt::UserRole + 1,
IsChangeableRole,
MetaDataRole,
ConfigRole,
IdRole,
EnabledByDefaultRole,
SortableRole, /// @internal
};
explicit KPluginModel(QObject *parent = nullptr);
~KPluginModel() override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) override;
/**
* Append plugins to the model. This will not replace existing entries.
*
* @param plugins the plugins to be added.
* @param categoryLabel a user-visible label for the section the plugins are added to.
*
*/
void addPlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel);
/**
* Add plugins that should not be sorted automatically based on their name
* This is useful in case your app has a custom sorting mechanism or implements reordering of plugins
*
* @since 6.0
*/
void addUnsortablePlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel);
/// @since 6.0
void removePlugin(const KPluginMetaData &data);
/**
* Removes all plugins.
*/
void clear();
/**
* Set the KConfigGroup that is used to load/save the enabled state.
*/
void setConfig(const KConfigGroup &config);
/**
* Save the enabled state of the plugins to the config group set by @ref setConfig.
*/
void save();
/**
* Load the enabled state of the plugins from the config group set by @ref setConfig.
*/
void load();
/**
* Reset the enabled state of the plugins to its defaults.
*/
void defaults();
/**
* Whether or not there are unsaved changes to the enabled state of the plugins.
*/
bool isSaveNeeded();
/**
* Returns the KPluginMetaData object of the plugin's config module. If no plugin is found or the plugin does not have a config, the resulting
* KPluginMetaData object will be invalid.
* @since 5.94
*/
KPluginMetaData findConfigForPluginId(const QString &pluginId) const;
/**
* Emitted when the enabled state matches the default changes.
*
* @see defaults
*/
Q_SIGNAL void defaulted(bool isDefaulted);
/**
* Emitted when @ref isSaveNeeded is changed.
*/
Q_SIGNAL void isSaveNeededChanged();
private:
const std::unique_ptr<KPluginModelPrivate> d;
friend class KPluginProxyModel;
QStringList getOrderedCategoryLabels();
};
#endif
@@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginproxymodel.h"
#include "kpluginmodel.h"
KPluginProxyModel::KPluginProxyModel(QObject *parent)
: KCategorizedSortFilterProxyModel(parent)
{
sort(0);
setCategorizedModel(true);
}
KPluginProxyModel::~KPluginProxyModel() = default;
QString KPluginProxyModel::query() const
{
return m_query;
}
void KPluginProxyModel::setQuery(const QString &query)
{
if (m_query != query) {
m_query = query;
invalidate();
Q_EMIT queryChanged();
}
}
bool KPluginProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
{
if (m_query.isEmpty()) {
return true;
}
const QModelIndex index = sourceModel()->index(sourceRow, 0);
const QString name = index.data(KPluginModel::NameRole).toString();
if (name.contains(m_query, Qt::CaseInsensitive)) {
return true;
}
const QString description = index.data(KPluginModel::DescriptionRole).toString();
if (description.contains(m_query, Qt::CaseInsensitive)) {
return true;
}
return false;
}
bool KPluginProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const
{
if (left.data(KPluginModel::SortableRole).toBool() && right.data(KPluginModel::SortableRole).toBool()) {
return left.data(KPluginModel::NameRole).toString().compare(right.data(KPluginModel::NameRole).toString(), Qt::CaseInsensitive) < 0;
}
return 0;
}
int KPluginProxyModel::compareCategories(const QModelIndex &left, const QModelIndex &right) const
{
const QStringList orderedCategoryLabels = m_model->getOrderedCategoryLabels();
const QString leftLabel = left.data(KCategorizedSortFilterProxyModel::CategorySortRole).toString();
const QString rightLabel = right.data(KCategorizedSortFilterProxyModel::CategorySortRole).toString();
// Preserve the order in which they were passed in the model from consumers
return orderedCategoryLabels.indexOf(leftLabel) - orderedCategoryLabels.indexOf(rightLabel);
}
#include "moc_kpluginproxymodel.cpp"
@@ -0,0 +1,45 @@
/*
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINPROXYMODEL_H
#define KPLUGINPROXYMODEL_H
#include "kcmutilscore_export.h"
#include "kpluginmodel.h"
#include <KCategorizedSortFilterProxyModel>
class Q_DECL_HIDDEN KPluginProxyModel : public KCategorizedSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged)
Q_PROPERTY(QAbstractListModel *model WRITE setModel)
public:
explicit KPluginProxyModel(QObject *parent = nullptr);
~KPluginProxyModel() override;
QString query() const;
void setQuery(const QString &query);
void setModel(QAbstractListModel *model)
{
setSourceModel(model);
m_model = qobject_cast<KPluginModel *>(model);
Q_ASSERT(m_model);
}
Q_SIGNALS:
void queryChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const override;
int compareCategories(const QModelIndex &left, const QModelIndex &right) const override;
private:
QString m_query;
KPluginModel *m_model;
};
#endif
@@ -0,0 +1,170 @@
/*
SPDX-FileCopyrightText: 2001 Michael Goffioul <kdeprint@swing.be>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcmodule.h"
#include "kabstractconfigmodule.h"
#include "kcmutils_debug.h"
#include <QVBoxLayout>
#include <KConfigDialogManager>
#include <KConfigSkeleton>
#include <KLocalizedString>
#include <KPluginMetaData>
class KCModuleProxyInternal : public QWidget
{
Q_OBJECT
public:
KCModuleProxyInternal(QWidget *parent)
: QWidget(parent)
{
}
};
class KCModulePrivate
{
public:
KCModulePrivate(QWidget *parentWidget)
: _needsAuthorization(false)
, _unmanagedWidgetChangeState(false)
, _unmanagedWidgetDefaultState(false)
, _unmanagedWidgetDefaultStateCalled(false)
, parentWidget(parentWidget)
{
}
void authStatusChanged(int status);
QList<KConfigDialogManager *> managers;
bool _needsAuthorization : 1;
// this member is used to record the state on non-automatically
// managed widgets, allowing for mixed KConfigXT-drive and manual
// widgets to coexist peacefully and do the correct thing with
// the changed(bool) signal
bool _unmanagedWidgetChangeState : 1;
bool _unmanagedWidgetDefaultState : 1;
bool _unmanagedWidgetDefaultStateCalled : 1;
QVBoxLayout *m_topLayout = nullptr; /* Contains QScrollView view, and root stuff */
QWidget *parentWidget;
KCModuleProxyInternal *m_proxyInternal = nullptr;
};
KCModule::KCModule(QWidget *parent, const KPluginMetaData &data)
: KAbstractConfigModule(parent, data)
, d(new KCModulePrivate(parent))
{
}
KConfigDialogManager *KCModule::addConfig(KCoreConfigSkeleton *config, QWidget *widget)
{
KConfigDialogManager *manager = new KConfigDialogManager(widget, config);
manager->setObjectName(objectName());
connect(manager, &KConfigDialogManager::widgetModified, this, &KCModule::widgetChanged);
connect(manager, &QObject::destroyed, this, [this, manager]() {
d->managers.removeOne(manager);
});
d->managers.append(manager);
return manager;
}
KCModule::~KCModule()
{
qDeleteAll(d->managers);
d->managers.clear();
}
void KCModule::load()
{
KAbstractConfigModule::load();
for (KConfigDialogManager *manager : std::as_const(d->managers)) {
manager->updateWidgets();
}
widgetChanged();
}
void KCModule::save()
{
KAbstractConfigModule::save();
for (KConfigDialogManager *manager : std::as_const(d->managers)) {
manager->updateSettings();
}
}
void KCModule::defaults()
{
KAbstractConfigModule::defaults();
for (KConfigDialogManager *manager : std::as_const(d->managers)) {
manager->updateWidgetsDefault();
}
}
QWidget *KCModule::widget()
{
if (!d->m_proxyInternal) {
d->m_topLayout = new QVBoxLayout(d->parentWidget);
d->m_proxyInternal = new KCModuleProxyInternal(d->parentWidget);
d->m_topLayout->addWidget(d->m_proxyInternal);
}
return d->m_proxyInternal;
}
void KCModule::widgetChanged()
{
setNeedsSave(d->_unmanagedWidgetChangeState || managedWidgetChangeState());
if (d->_unmanagedWidgetDefaultStateCalled) {
setRepresentsDefaults(d->_unmanagedWidgetDefaultState && managedWidgetDefaultState());
} else {
setRepresentsDefaults(!d->managers.isEmpty() && managedWidgetDefaultState());
}
}
bool KCModule::managedWidgetChangeState() const
{
for (KConfigDialogManager *manager : std::as_const(d->managers)) {
if (manager->hasChanged()) {
return true;
}
}
return false;
}
bool KCModule::managedWidgetDefaultState() const
{
for (KConfigDialogManager *manager : std::as_const(d->managers)) {
if (!manager->isDefault()) {
return false;
}
}
return true;
}
void KCModule::unmanagedWidgetChangeState(bool changed)
{
d->_unmanagedWidgetChangeState = changed;
widgetChanged();
}
void KCModule::unmanagedWidgetDefaultState(bool defaulted)
{
d->_unmanagedWidgetDefaultStateCalled = true;
d->_unmanagedWidgetDefaultState = defaulted;
widgetChanged();
}
QList<KConfigDialogManager *> KCModule::configs() const
{
return d->managers;
}
#include "kcmodule.moc"
#include "moc_kcmodule.cpp"
@@ -0,0 +1,168 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCMODULE_H
#define KCMODULE_H
#include "kcmutils_export.h"
#include <KAbstractConfigModule>
#include <KPluginMetaData>
#include <QVariant>
#include <QWidget>
#include <memory>
class KConfigDialogManager;
class KCoreConfigSkeleton;
class KConfigSkeleton;
class KCModulePrivate;
/**
* @class KCModule kcmodule.h KCModule
*
* The base class for QWidgets configuration modules.
* Configuration modules are loaded as plugins.
*
* The module in principle is a simple widget displaying the
* item to be changed. The module has a very small interface.
*
* To write a config module, you have to create a library
* that contains a factory class like the following:
*
* \code
* #include <KPluginFactory>
*
* K_PLUGIN_CLASS_WITH_JSON(MyKCModule, "mykcmodule.json")
* \endcode
*
* The constructor of the KCModule then looks like this:
* \code
* YourKCModule::YourKCModule(QWidget *parent, const KPluginMetaData &data)
* : KCModule(parent, data)
* {
* // KCModule does not directly extend QWidget due to ambiguity with KAbstractConfigModule
* // Because of this, you need to call widget() to get the parent widget
* auto label = new QLabel(widget());
* label->setText(QStringLiteral("Demo Text"));
* }
* \endcode
*
* This KCM can be loaded in a KCMultiDialog of kcmshell6
*
* @since 6.0
*/
class KCMUTILS_EXPORT KCModule : public KAbstractConfigModule
{
Q_OBJECT
public:
/**
* Base class for all QWidgets configuration modules.
*
* @note do not emit changed signals here, since they are not yet connected
* to any slot.
*/
explicit KCModule(QWidget *parent, const KPluginMetaData &data);
/**
* Destroys the module.
*/
~KCModule() override;
/**
* @return a list of @ref KConfigDialogManager's in use, if any.
*/
QList<KConfigDialogManager *> configs() const;
void load() override;
void save() override;
void defaults() override;
/**
* Utility function that marks the KCM as changed
*/
void markAsChanged()
{
setNeedsSave(true);
}
/**
* Get the associated widget that can be embedded
* The returned widget should be used as a parent for widgets you create
*
* @note Overwriting this function should not be necessary for consumers!
*/
virtual QWidget *widget();
protected:
/**
* Adds a KCoreConfigskeleton @p config to watch the widget @p widget
*
* This function is useful if you need to handle multiple configuration files.
*
* @return a pointer to the KCoreConfigDialogManager in use
* @param config the KCoreConfigSkeleton to use
* @param widget the widget to watch
*/
KConfigDialogManager *addConfig(KCoreConfigSkeleton *config, QWidget *widget);
protected Q_SLOTS:
/**
* A managed widget was changed, the widget settings and the current
* settings are compared and a corresponding needsSaveChanged() signal is emitted
*/
void widgetChanged();
protected:
/**
* Returns the changed state of automatically managed widgets in this dialog
*/
bool managedWidgetChangeState() const;
/**
* Returns the defaulted state of automatically managed widgets in this dialog
*/
bool managedWidgetDefaultState() const;
/**
* Call this method when your manually managed widgets change state between
* changed and not changed
*/
void unmanagedWidgetChangeState(bool);
/**
* Call this method when your manually managed widgets change state between
* defaulted and not defaulted
*/
void unmanagedWidgetDefaultState(bool);
/**
* Utility overload to avoid having to take both parent and parentWidget
* KCModuleLoader::loadModule enforces the parent to be a QWidget anyway
*/
explicit KCModule(QObject *parent, const KPluginMetaData &data)
: KCModule(qobject_cast<QWidget *>(parent), data)
{
}
/**
* Utility constructor for creating a KCModule that is embedded, for example in a KPluginWidget
* This constructor should not be used for KCMs that are part launched in systemsettings!
*
* @note do not emit changed signals here, since they are not yet connected
* to any slot.
*/
explicit KCModule(QObject *parent)
: KCModule(qobject_cast<QWidget *>(parent), KPluginMetaData{})
{
}
private:
std::unique_ptr<KCModulePrivate> const d;
};
#endif // KCMODULE_H
@@ -0,0 +1,90 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2003, 2004, 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kcmoduleloader.h"
#include "kcmoduledata.h"
#include <kcmutils_debug.h>
#include <QJsonArray>
#include <QLabel>
#include <QLibrary>
#include <QVBoxLayout>
#include <KAboutData>
#include <KAuthorized>
#include <KLocalizedString>
#include <KMessageBox>
#include <KPluginFactory>
#include <memory>
using namespace KCModuleLoader;
/***************************************************************/
/**
* When something goes wrong in loading the module, this one
* jumps in as a "dummy" module.
*/
class KCMError : public KCModule
{
Q_OBJECT
public:
KCMError(const QString &msg, const QString &details, QWidget *parent)
: KCModule(parent, KPluginMetaData())
{
QString realDetails = details.trimmed();
if (realDetails.isNull()) {
realDetails = i18n(
"<qt><p>Possible reasons:<ul><li>An error occurred during your last "
"system upgrade, leaving an orphaned control module behind</li><li>You have old third party "
"modules lying around.</li></ul></p><p>Check these points carefully and try to remove "
"the module mentioned in the error message. If this fails, consider contacting "
"your distributor or packager.</p></qt>");
}
QVBoxLayout *topLayout = new QVBoxLayout(widget());
QLabel *lab = new QLabel(msg, widget());
{
// Similar to Kirigami.Heading: Primary, level 3
QFont font = lab->font();
font.setPointSizeF(font.pointSizeF() * 1.15);
font.setBold(true);
lab->setFont(font);
}
lab->setWordWrap(true);
lab->setTextInteractionFlags(lab->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
topLayout->addWidget(lab);
lab = new QLabel(realDetails, widget());
lab->setWordWrap(true);
lab->setTextInteractionFlags(lab->textInteractionFlags() | Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
topLayout->addWidget(lab);
}
};
KCModule *KCModuleLoader::loadModule(const KPluginMetaData &metaData, QWidget *parent, const QVariantList &args, const std::shared_ptr<QQmlEngine> &eng)
{
if (!KAuthorized::authorizeControlModule(metaData.pluginId())) {
return new KCMError(i18n("The module %1 is disabled.", metaData.pluginId()), i18n("The module has been disabled by the system administrator."), parent);
}
(void)eng;
const QVariantList pluginArgs = QVariantList(args) << metaData.rawData().value(QLatin1String("X-KDE-KCM-Args")).toArray().toVariantList();
const auto kcmoduleResult = KPluginFactory::instantiatePlugin<KCModule>(metaData, parent, pluginArgs);
if (kcmoduleResult) {
qCDebug(KCMUTILS_LOG) << "loaded KCM" << metaData.fileName();
return kcmoduleResult.plugin;
}
return new KCMError(QString(), kcmoduleResult.errorString, parent);
}
#include "kcmoduleloader.moc"
@@ -0,0 +1,41 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2002-2003 Daniel Molkentin <molkentin@kde.org>
SPDX-FileCopyrightText: 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCMODULELOADER_H
#define KCMODULELOADER_H
#include <KPluginMetaData>
#include <kcmodule.h>
#include <memory>
class QWidget;
class QQmlEngine;
/**
* @short Loads a KCModule
* In case the provided metadata points to a KQuickConfigModule, it is wrapped in a KCModule
*/
namespace KCModuleLoader
{
/**
* Loads a @ref KCModule. If loading fails a KCM which displays an error message is returned.
*
* @param metaData KPluginMetaData for loading the plugin
* @param engine QQmlEngine that will be used for KQuickConfigModule classes.
* If none is set, a internal engine will be created and reused for further modules.
* In case your app already has an engine, you should pass it in explicitly
*
* @return a pointer to the loaded @ref KCModule
*/
KCMUTILS_EXPORT KCModule *
loadModule(const KPluginMetaData &metaData, QWidget *parent = nullptr, const QVariantList &args = {}, const std::shared_ptr<QQmlEngine> &engine = {});
}
#endif // KCMODULELOADER_H
@@ -0,0 +1,282 @@
/*
SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kcmoduleqml_p.h"
#include <QQuickItem>
#include <QQuickWidget>
#include <QQuickWindow>
#include <QVBoxLayout>
#include <KAboutData>
#include <KLocalizedContext>
#include <KPageWidget>
#include <QQmlEngine>
#include "quick/kquickconfigmodule.h"
#include <kcmutils_debug.h>
class QmlConfigModuleWidget;
class KCModuleQmlPrivate
{
public:
KCModuleQmlPrivate(KQuickConfigModule *cm, KCModuleQml *qq)
: q(qq)
, configModule(std::move(cm))
{
}
~KCModuleQmlPrivate()
{
}
void syncCurrentIndex()
{
if (!configModule || !pageRow) {
return;
}
configModule->setCurrentIndex(pageRow->property("currentIndex").toInt());
}
KCModuleQml *q;
QQuickWindow *quickWindow = nullptr;
QQuickWidget *quickWidget = nullptr;
QQuickItem *rootPlaceHolder = nullptr;
QQuickItem *pageRow = nullptr;
KQuickConfigModule *configModule;
QmlConfigModuleWidget *widget = nullptr;
};
class QmlConfigModuleWidget : public QWidget
{
Q_OBJECT
public:
QmlConfigModuleWidget(KCModuleQml *module, QWidget *parent)
: QWidget(parent)
, m_module(module)
{
setFocusPolicy(Qt::StrongFocus);
}
void focusInEvent(QFocusEvent *event) override
{
if (event->reason() == Qt::TabFocusReason) {
m_module->d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
} else if (event->reason() == Qt::BacktabFocusReason) {
m_module->d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
}
}
QSize sizeHint() const override
{
if (!m_module->d->rootPlaceHolder) {
return QSize();
}
return QSize(m_module->d->rootPlaceHolder->implicitWidth(), m_module->d->rootPlaceHolder->implicitHeight());
}
bool eventFilter(QObject *watched, QEvent *event) override
{
if (watched == m_module->d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
auto focusEvent = static_cast<QFocusEvent *>(event);
if (focusEvent->reason() == Qt::TabFocusReason) {
QWidget *w = m_module->d->quickWidget->nextInFocusChain();
while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
w = w->nextInFocusChain();
}
w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
return true;
} else if (focusEvent->reason() == Qt::BacktabFocusReason) {
QWidget *w = m_module->d->quickWidget->previousInFocusChain();
while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
w = w->previousInFocusChain();
}
w->setFocus(Qt::BacktabFocusReason);
return true;
}
}
return QWidget::eventFilter(watched, event);
}
private:
KCModuleQml *m_module;
};
KCModuleQml::KCModuleQml(KQuickConfigModule *configModule, QWidget *parent)
: KCModule(parent, configModule->metaData())
, d(new KCModuleQmlPrivate(configModule, this))
{
d->widget = new QmlConfigModuleWidget(this, parent);
setButtons(d->configModule->buttons());
connect(d->configModule, &KQuickConfigModule::buttonsChanged, d->configModule, [this] {
setButtons(d->configModule->buttons());
});
setNeedsSave(d->configModule->needsSave());
connect(d->configModule, &KQuickConfigModule::needsSaveChanged, this, [this] {
setNeedsSave(d->configModule->needsSave());
});
setRepresentsDefaults(d->configModule->representsDefaults());
connect(d->configModule, &KQuickConfigModule::representsDefaultsChanged, this, [this] {
setRepresentsDefaults(d->configModule->representsDefaults());
});
setAuthActionName(d->configModule->authActionName());
connect(d->configModule, &KQuickConfigModule::authActionNameChanged, this, [this] {
setAuthActionName(d->configModule->authActionName());
});
connect(this, &KCModule::defaultsIndicatorsVisibleChanged, d->configModule, [this] {
d->configModule->setDefaultsIndicatorsVisible(defaultsIndicatorsVisible());
});
connect(this, &KAbstractConfigModule::activationRequested, d->configModule, &KQuickConfigModule::activationRequested);
// Build the UI
QVBoxLayout *layout = new QVBoxLayout(d->widget);
layout->setContentsMargins(0, 0, 0, 0);
d->quickWidget = new QQuickWidget(d->configModule->engine().get(), d->widget);
d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
d->quickWidget->setFocusPolicy(Qt::StrongFocus);
d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
d->quickWidget->setAttribute(Qt::WA_NoMousePropagation, true); // Workaround for QTBUG-109861 to fix drag everywhere
d->quickWindow = d->quickWidget->quickWindow();
d->quickWindow->setColor(Qt::transparent);
QQmlComponent *component = new QQmlComponent(d->configModule->engine().get(), this);
// this has activeFocusOnTab to notice when the navigation wraps
// around, so when we need to go outside and inside
// pushPage/popPage are needed as push of StackView can't be directly invoked from c++
// because its parameters are QQmlV4Function which is not public.
// The managers of onEnter/ReturnPressed are a workaround of
// Qt bug https://bugreports.qt.io/browse/QTBUG-70934
// clang-format off
// TODO: move this in an instantiable component which would be used by the qml-only version as well
component->setData(QByteArrayLiteral(R"(
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami 2 as Kirigami
import org.kde.kcmutils as KCMUtils
Kirigami.ApplicationItem {
// force it to *never* try to resize itself
width: Window.width
implicitWidth: Math.max(pageStack.implicitWidth, Kirigami.Units.gridUnit * 36)
implicitHeight: Math.max(pageStack.implicitHeight, Kirigami.Units.gridUnit * 20)
activeFocusOnTab: true
property KCMUtils.ConfigModule kcm
QQC2.ToolButton {
id: toolButton
visible: false
icon.name: "go-previous"
}
pageStack.separatorVisible: pageStack.depth > 0 && (pageStack.items[0].sidebarMode ?? false)
pageStack.globalToolBar.preferredHeight: toolButton.implicitHeight + Kirigami.Units.smallSpacing * 2
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : Kirigami.ApplicationHeaderStyle.NoNavigationButtons
pageStack.columnView.columnResizeMode: pageStack.items.length > 0 && (pageStack.items[0].Kirigami.ColumnView.fillWidth || pageStack.items.filter(item => item.visible).length === 1)
? Kirigami.ColumnView.SingleColumn
: Kirigami.ColumnView.FixedColumns
pageStack.defaultColumnWidth: kcm && kcm.columnWidth > 0 ? kcm.columnWidth : Kirigami.Units.gridUnit * 15
footer: null
Keys.onReturnPressed: event => {
event.accepted = true
}
Keys.onEnterPressed: event => {
event.accepted = true
}
Window.onWindowChanged: {
if (Window.window) {
Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft)
Window.window.LayoutMirroring.childrenInherit = true
}
}
}
)"), QUrl(QStringLiteral("kcmutils/kcmmoduleqml.cpp")));
// clang-format on
d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create());
if (!d->rootPlaceHolder) {
qCCritical(KCMUTILS_LOG) << component->errors();
qFatal("Failed to initialize KCModuleQML");
}
d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule));
d->rootPlaceHolder->installEventFilter(d->widget);
d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder);
d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>();
if (d->pageRow) {
d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi()));
for (int i = 0; i < d->configModule->depth() - 1; i++) {
QMetaObject::invokeMethod(d->pageRow,
"push",
Qt::DirectConnection,
Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))),
Q_ARG(QVariant, QVariant()));
if (d->configModule->mainUi()->property("sidebarMode").toBool()) {
d->pageRow->setProperty("currentIndex", 0);
d->configModule->setCurrentIndex(0);
}
}
connect(d->configModule, &KQuickConfigModule::pagePushed, this, [this](QQuickItem *page) {
QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant()));
});
connect(d->configModule, &KQuickConfigModule::pageRemoved, this, [this]() {
QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant()));
});
connect(d->configModule, &KQuickConfigModule::currentIndexChanged, this, [this]() {
d->pageRow->setProperty("currentIndex", d->configModule->currentIndex());
});
// New syntax cannot be used to connect to QML types
connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex()));
}
layout->addWidget(d->quickWidget);
}
KCModuleQml::~KCModuleQml() = default;
void KCModuleQml::load()
{
KCModule::load(); // calls setNeedsSave(false)
d->configModule->load();
}
void KCModuleQml::save()
{
d->configModule->save();
d->configModule->setNeedsSave(false);
}
void KCModuleQml::defaults()
{
d->configModule->defaults();
}
QWidget *KCModuleQml::widget()
{
return d->widget;
}
#include "kcmoduleqml.moc"
#include "moc_kcmoduleqml_p.cpp"
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCMODULEQML_H
#define KCMODULEQML_H
#include "kcmodule.h"
#include <memory>
class QQuickItem;
class QQmlEngine;
class KCModuleQmlPrivate;
class KQuickConfigModule;
class KCModuleQml : public KCModule
{
Q_OBJECT
public:
KCModuleQml(KQuickConfigModule *configModule, QWidget *parent);
~KCModuleQml() override;
public Q_SLOTS:
void load() override;
void save() override;
void defaults() override;
QWidget *widget() override;
private:
friend class QmlConfigModuleWidget;
const std::unique_ptr<KCModuleQmlPrivate> d;
Q_PRIVATE_SLOT(d, void syncCurrentIndex())
};
#endif
@@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-2-Clause
add_executable(kcmshell6 main.cpp)
target_compile_definitions(kcmshell6 PRIVATE -DPROJECT_VERSION="${KF_VERSION}")
ecm_mark_nongui_executable(kcmshell6)
target_link_libraries(kcmshell6
KF6KCMUtils
KF6::I18n
Qt6::Qml
kcmutils_logging_STATIC
)
if (HAVE_QTDBUS)
target_link_libraries(kcmshell6 Qt6::DBus)
target_compile_definitions(kcmshell6 PRIVATE -DHAVE_QTDBUS=1)
else()
target_compile_definitions(kcmshell6 PRIVATE -DHAVE_QTDBUS=0)
endif()
install(TARGETS kcmshell6 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
@@ -0,0 +1,5 @@
#! /usr/bin/env bash
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
$XGETTEXT *.cpp -o $podir/kcmshell6.pot
@@ -0,0 +1,238 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QDebug>
#include <QIcon>
#include <QQmlEngine>
#include <QRegularExpression>
#include <QStandardPaths>
#include <KAboutData>
#include <KAuthorized>
#include <KCModule>
#include <KCMultiDialog>
#include <KLocalizedString>
#include <KPageDialog>
#include <KPluginMetaData>
#include <KShell>
#include <kcmutils_debug.h>
#if HAVE_QTDBUS
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#endif
#include <algorithm>
#include <iostream>
inline QList<KPluginMetaData> findKCMsMetaData()
{
QList<KPluginMetaData> metaDataList = KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms"));
metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings"));
metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/systemsettings_qwidgets"));
metaDataList << KPluginMetaData::findPlugins(QStringLiteral("plasma/kcms/kinfocenter"));
return metaDataList;
}
class KCMShellMultiDialog : public KCMultiDialog
{
Q_OBJECT
public:
/**
* Constructor. Parameter @p dialogFace is passed to KCMultiDialog
* unchanged.
*/
explicit KCMShellMultiDialog(KPageDialog::FaceType dialogFace)
: KCMultiDialog()
{
setFaceType(dialogFace);
setModal(false);
#if HAVE_QTDBUS
connect(this, &KCMShellMultiDialog::currentPageChanged, this, [](KPageWidgetItem *newPage) {
if (KCModule *activeModule = newPage->widget()->findChild<KCModule *>()) {
if (QDBusConnection::sessionBus().isConnected()
&& QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.ActivityManager"))) {
QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.ActivityManager"),
QStringLiteral("/ActivityManager/Resources"),
QStringLiteral("org.kde.ActivityManager.Resources"),
QStringLiteral("RegisterResourceEvent"));
const QString appId = QStringLiteral("org.kde.systemsettings");
const uint winId = 0;
const QString url = QLatin1String("kcm:") + activeModule->metaData().pluginId();
const uint eventType = 0; // Accessed
msg.setArguments({appId, winId, url, eventType});
QDBusConnection::sessionBus().asyncCall(msg);
}
}
});
#endif
}
};
int main(int argc, char *argv[])
{
const bool qpaVariable = qEnvironmentVariableIsSet("QT_QPA_PLATFORM");
QApplication app(argc, argv);
if (!qpaVariable) {
// don't leak the env variable to processes we start
qunsetenv("QT_QPA_PLATFORM");
}
KLocalizedString::setApplicationDomain("kcmshell6");
KAboutData aboutData(QStringLiteral("kcmshell6"),
QString(),
QLatin1String(PROJECT_VERSION),
i18n("A tool to start single system settings modules"),
KAboutLicense::GPL,
i18n("(c) 1999-2023, The KDE Developers"));
aboutData.addAuthor(i18n("Frans Englich"), i18n("Maintainer"), QStringLiteral("frans.englich@kde.org"));
aboutData.addAuthor(i18n("Daniel Molkentin"), QString(), QStringLiteral("molkentin@kde.org"));
aboutData.addAuthor(i18n("Matthias Hoelzer-Kluepfel"), QString(), QStringLiteral("hoelzer@kde.org"));
aboutData.addAuthor(i18n("Matthias Elter"), QString(), QStringLiteral("elter@kde.org"));
aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org"));
aboutData.addAuthor(i18n("Waldo Bastian"), QString(), QStringLiteral("bastian@kde.org"));
KAboutData::setApplicationData(aboutData);
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
parser.addOption(QCommandLineOption(QStringLiteral("list"), i18n("List all possible modules")));
parser.addPositionalArgument(QStringLiteral("module"), i18n("Configuration module to open"));
parser.addOption(QCommandLineOption(QStringLiteral("args"), i18n("Space separated arguments for the module"), QLatin1String("arguments")));
parser.addOption(QCommandLineOption(QStringLiteral("icon"), i18n("Use a specific icon for the window"), QLatin1String("icon")));
parser.addOption(QCommandLineOption(QStringLiteral("caption"), i18n("Use a specific caption for the window"), QLatin1String("caption")));
parser.addOption(QCommandLineOption(QStringLiteral("highlight"), i18n("Show an indicator when settings have changed from their default value")));
parser.parse(app.arguments());
aboutData.processCommandLine(&parser);
parser.process(app);
if (parser.isSet(QStringLiteral("list"))) {
std::cout << i18n("The following modules are available:").toLocal8Bit().constData() << '\n';
QList<KPluginMetaData> plugins = findKCMsMetaData();
int maxLen = 0;
for (const auto &plugin : plugins) {
const int len = plugin.pluginId().size();
maxLen = std::max(maxLen, len);
}
for (const auto &plugin : plugins) {
QString comment = plugin.description();
if (comment.isEmpty()) {
comment = i18n("No description available");
}
const QString entry = QStringLiteral("%1 - %2").arg(plugin.pluginId().leftJustified(maxLen, QLatin1Char(' ')), comment);
std::cout << entry.toLocal8Bit().constData() << '\n';
}
std::cout << std::endl;
return 0;
}
if (parser.positionalArguments().isEmpty()) {
parser.showHelp();
return -1;
}
QList<KPluginMetaData> metaDataList;
QStringList args = parser.positionalArguments();
args.removeDuplicates();
for (const QString &arg : args) {
if (KPluginMetaData data(arg, KPluginMetaData::AllowEmptyMetaData); data.isValid()) {
metaDataList << data;
} else {
// Look in the namespaces for systemsettings/kinfocenter
const static auto knownKCMs = findKCMsMetaData();
const QStringList possibleIds{arg, QStringLiteral("kcm_") + arg, QStringLiteral("kcm") + arg};
bool found = std::any_of(knownKCMs.begin(), knownKCMs.end(), [&possibleIds, &metaDataList](const KPluginMetaData &data) {
bool idMatches = possibleIds.contains(data.pluginId());
if (idMatches) {
metaDataList << data;
}
return idMatches;
});
if (!found) {
metaDataList << KPluginMetaData(arg); // So that we show an error message in the dialog
qCWarning(KCMUTILS_LOG) << "Could not find KCM with given Id" << arg;
}
}
}
if (metaDataList.isEmpty()) {
return -1;
}
// This ensures if there are multiple QML-based kcms loaded, they use a shared engine instance
app.setProperty("__qmlEngine", QVariant::fromValue(new QQmlEngine));
const bool multipleKCMs = metaDataList.size() > 1;
KPageDialog::FaceType ftype = multipleKCMs ? KPageDialog::List : KPageDialog::Plain;
auto dlg = new KCMShellMultiDialog(ftype);
dlg->setAttribute(Qt::WA_DeleteOnClose);
if (parser.isSet(QStringLiteral("caption"))) {
dlg->setWindowTitle(parser.value(QStringLiteral("caption")));
} else if (!multipleKCMs) { // We will have the "Configure" window title set by KCMultiDialog
dlg->setWindowTitle(metaDataList.constFirst().name());
}
const QStringList argSplit = KShell::splitArgs(parser.value(QStringLiteral("args")));
QVariantList pluginArgs(argSplit.begin(), argSplit.end());
if (metaDataList.size() == 1) {
KPageWidgetItem *item = dlg->addModule(*metaDataList.cbegin(), pluginArgs);
// This makes sure the content area is focused by default
item->widget()->setFocus(Qt::MouseFocusReason);
} else {
for (const KPluginMetaData &m : std::as_const(metaDataList)) {
dlg->addModule(m, pluginArgs);
}
}
if (parser.isSet(QStringLiteral("icon"))) {
dlg->setWindowIcon(QIcon::fromTheme(parser.value(QStringLiteral("icon"))));
} else {
dlg->setWindowIcon(QIcon::fromTheme(metaDataList.constFirst().iconName()));
}
if (parser.isSet(QStringLiteral("highlight"))) {
dlg->setDefaultsIndicatorsVisible(true);
}
if (app.desktopFileName() == QLatin1String("org.kde.kcmshell6")) {
const QString path = metaDataList.constFirst().fileName();
if (path.endsWith(QLatin1String(".desktop"))) {
app.setDesktopFileName(path);
} else {
app.setDesktopFileName(metaDataList.constFirst().pluginId());
}
}
dlg->show();
app.exec();
return 0;
}
#include "main.moc"
@@ -0,0 +1,447 @@
/*
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org>
SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcmultidialog.h"
#include "kcmoduleloader.h"
#include "kcmultidialog_p.h"
#include <kcmutils_debug.h>
#include <QApplication>
#include <QDesktopServices>
#include <QJsonArray>
#include <QLayout>
#include <QProcess>
#include <QPushButton>
#include <QScreen>
#include <QStandardPaths>
#include <QStringList>
#include <QStyle>
#include <QTimer>
#include <QUrl>
#include <KGuiItem>
#include <KIconUtils>
#include <KLocalizedString>
#include <KMessageBox>
#include <KPageWidgetModel>
bool KCMultiDialogPrivate::resolveChanges(KCModule *module)
{
if (!module || !module->needsSave()) {
return true;
}
// Let the user decide
const int queryUser = KMessageBox::warningTwoActionsCancel(q,
i18n("The settings of the current module have changed.\n"
"Do you want to apply the changes or discard them?"),
i18n("Apply Settings"),
KStandardGuiItem::apply(),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
switch (queryUser) {
case KMessageBox::PrimaryAction:
return moduleSave(module);
case KMessageBox::SecondaryAction:
module->load();
return true;
case KMessageBox::Cancel:
return false;
default:
Q_ASSERT(false);
return false;
}
}
void KCMultiDialogPrivate::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
{
KCModule *previousModule = nullptr;
for (int i = 0; i < modules.count(); ++i) {
if (modules[i].item == previous) {
previousModule = modules[i].kcm;
}
}
// Delete global margins and spacing, since we want the contents to
// be able to touch the edges of the window
q->layout()->setContentsMargins(0, 0, 0, 0);
const KPageWidget *pageWidget = q->pageWidget();
pageWidget->layout()->setSpacing(0);
// Then, we set the margins for the title header and the buttonBox footer
const QStyle *style = q->style();
const QMargins layoutMargins = QMargins(style->pixelMetric(QStyle::PM_LayoutLeftMargin),
style->pixelMetric(QStyle::PM_LayoutTopMargin),
style->pixelMetric(QStyle::PM_LayoutRightMargin),
style->pixelMetric(QStyle::PM_LayoutBottomMargin));
if (pageWidget->pageHeader()) {
pageWidget->pageHeader()->setContentsMargins(layoutMargins);
}
q->buttonBox()->setContentsMargins(layoutMargins.left(), layoutMargins.top(), layoutMargins.right(), layoutMargins.bottom());
q->blockSignals(true);
q->setCurrentPage(previous);
if (resolveChanges(previousModule)) {
q->setCurrentPage(current);
}
q->blockSignals(false);
// We need to get the state of the now active module
clientChanged();
}
void KCMultiDialogPrivate::clientChanged()
{
// Get the current module
KCModule *activeModule = nullptr;
bool scheduleFirstShow = false;
for (int i = 0; i < modules.count(); ++i) {
if (modules[i].item == q->currentPage()) {
activeModule = modules[i].kcm;
scheduleFirstShow = activeModule && modules[i].firstShow;
break;
}
}
// When we first show a module, we call the load method
// Just in case we have multiple loadModule calls in a row, the current module could change
// Meaning we wait for the next tick, check the active module and call load if needed
if (scheduleFirstShow) {
QTimer::singleShot(0, q, [this]() {
for (int i = 0; i < modules.count(); ++i) {
if (modules[i].firstShow && modules[i].kcm && modules[i].item == q->currentPage()) {
modules[i].firstShow = false;
modules[i].kcm->load();
}
}
});
}
const bool change = activeModule && activeModule->needsSave();
const bool defaulted = activeModule && activeModule->representsDefaults();
const auto buttons = activeModule ? activeModule->buttons() : KCModule::NoAdditionalButton;
QPushButton *resetButton = q->buttonBox()->button(QDialogButtonBox::Reset);
if (resetButton) {
resetButton->setVisible(buttons & KCModule::Apply);
resetButton->setEnabled(change);
}
QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
if (applyButton) {
applyButton->setVisible(buttons & KCModule::Apply);
applyButton->setEnabled(change);
}
QPushButton *cancelButton = q->buttonBox()->button(QDialogButtonBox::Cancel);
if (cancelButton) {
cancelButton->setVisible(buttons & KCModule::Apply);
}
QPushButton *okButton = q->buttonBox()->button(QDialogButtonBox::Ok);
if (okButton) {
okButton->setVisible(buttons & KCModule::Apply);
}
QPushButton *closeButton = q->buttonBox()->button(QDialogButtonBox::Close);
if (closeButton) {
closeButton->setHidden(buttons & KCModule::Apply);
}
QPushButton *helpButton = q->buttonBox()->button(QDialogButtonBox::Help);
if (helpButton) {
helpButton->setVisible(buttons & KCModule::Help);
}
QPushButton *defaultButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults);
if (defaultButton) {
defaultButton->setVisible(buttons & KCModule::Default);
defaultButton->setEnabled(!defaulted);
}
}
void KCMultiDialogPrivate::updateHeader(bool use, const QString &message)
{
KPageWidgetItem *item = q->currentPage();
const auto findIt = std::find_if(modules.cbegin(), modules.cend(), [item](const CreatedModule &module) {
return module.item == item;
});
Q_ASSERT(findIt != modules.cend());
KCModule *kcm = findIt->kcm;
const QString moduleName = kcm->metaData().name();
const QString icon = kcm->metaData().iconName();
if (use) {
item->setHeader(QStringLiteral("<b>") + moduleName + QStringLiteral("</b><br><i>") + message + QStringLiteral("</i>"));
item->setIcon(KIconUtils::addOverlay(QIcon::fromTheme(icon), QIcon::fromTheme(QStringLiteral("dialog-warning")), Qt::BottomRightCorner));
} else {
item->setHeader(moduleName);
item->setIcon(QIcon::fromTheme(icon));
}
}
void KCMultiDialogPrivate::init()
{
q->setFaceType(KPageDialog::Auto);
q->setWindowTitle(i18n("Configure"));
q->setModal(false);
QDialogButtonBox *buttonBox = new QDialogButtonBox(q);
buttonBox->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Cancel | QDialogButtonBox::Apply
| QDialogButtonBox::Close | QDialogButtonBox::Ok | QDialogButtonBox::Reset);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Close), KStandardGuiItem::close());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help());
buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
q->connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KCMultiDialog::slotApplyClicked);
q->connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KCMultiDialog::slotOkClicked);
q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KCMultiDialog::slotDefaultClicked);
q->connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KCMultiDialog::slotHelpClicked);
q->connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, q, &KCMultiDialog::slotUser1Clicked);
q->setButtonBox(buttonBox);
q->connect(q, &KPageDialog::currentPageChanged, q, [this](KPageWidgetItem *current, KPageWidgetItem *before) {
slotCurrentPageChanged(current, before);
});
}
KCMultiDialog::KCMultiDialog(QWidget *parent)
: KPageDialog(parent)
, d(new KCMultiDialogPrivate(this))
{
d->init();
}
KCMultiDialog::~KCMultiDialog() = default;
void KCMultiDialog::showEvent(QShowEvent *ev)
{
KPageDialog::showEvent(ev);
adjustSize();
/**
* adjustSize() relies on sizeHint but is limited to 2/3 of the desktop size
* Workaround for https://bugreports.qt.io/browse/QTBUG-3459
*
* We adjust the size after passing the show event
* because otherwise window pos is set to (0,0)
*/
const QSize maxSize = screen()->availableGeometry().size();
resize(qMin(sizeHint().width(), maxSize.width()), qMin(sizeHint().height(), maxSize.height()));
}
void KCMultiDialog::slotDefaultClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
for (int i = 0; i < d->modules.count(); ++i) {
if (d->modules[i].item == item) {
d->modules[i].kcm->defaults();
d->clientChanged();
return;
}
}
}
void KCMultiDialog::slotUser1Clicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
for (int i = 0; i < d->modules.count(); ++i) {
if (d->modules[i].item == item) {
d->modules[i].kcm->load();
d->clientChanged();
return;
}
}
}
bool KCMultiDialogPrivate::moduleSave(KCModule *module)
{
if (!module) {
return false;
}
module->save();
return true;
}
void KCMultiDialogPrivate::apply()
{
for (const CreatedModule &module : std::as_const(modules)) {
KCModule *kcm = module.kcm;
if (kcm->needsSave()) {
kcm->save();
}
}
Q_EMIT q->configCommitted();
}
void KCMultiDialog::slotApplyClicked()
{
QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
applyButton->setFocus();
d->apply();
}
void KCMultiDialog::slotOkClicked()
{
QPushButton *okButton = buttonBox()->button(QDialogButtonBox::Ok);
okButton->setFocus();
d->apply();
accept();
}
void KCMultiDialog::slotHelpClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
QString docPath;
for (int i = 0; i < d->modules.count(); ++i) {
if (d->modules[i].item == item) {
if (docPath.isEmpty()) {
docPath = d->modules[i].kcm->metaData().value(QStringLiteral("X-DocPath"));
}
break;
}
}
const QUrl docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp
const QString docUrlScheme = docUrl.scheme();
const QString helpExec = QStandardPaths::findExecutable(QStringLiteral("khelpcenter"));
const bool foundExec = !helpExec.isEmpty();
if (!foundExec) {
qCDebug(KCMUTILS_LOG) << "Couldn't find khelpcenter executable in PATH.";
}
if (foundExec && (docUrlScheme == QLatin1String("man") || docUrlScheme == QLatin1String("info"))) {
QProcess::startDetached(helpExec, QStringList() << docUrl.toString());
} else {
QDesktopServices::openUrl(docUrl);
}
}
void KCMultiDialog::closeEvent(QCloseEvent *event)
{
KPageDialog::closeEvent(event);
for (auto &module : d->modules) {
delete module.kcm;
module.kcm = nullptr;
}
}
KPageWidgetItem *KCMultiDialog::addModule(const KPluginMetaData &metaData, const QVariantList &args)
{
// Create the scroller
auto *moduleScroll = new UnboundScrollArea(this);
// Prepare the scroll area
moduleScroll->setWidgetResizable(true);
moduleScroll->setFrameStyle(QFrame::NoFrame);
moduleScroll->viewport()->setAutoFillBackground(false);
KCModule *kcm = KCModuleLoader::loadModule(metaData, moduleScroll, args);
moduleScroll->setWidget(kcm->widget());
KPageWidgetItem *item = new KPageWidgetItem(moduleScroll, metaData.name());
KCMultiDialogPrivate::CreatedModule createdModule;
createdModule.kcm = kcm;
createdModule.item = item;
d->modules.append(createdModule);
item->setHeader(metaData.name());
item->setIcon(QIcon::fromTheme(metaData.iconName()));
const int weight = metaData.rawData().value(QStringLiteral("X-KDE-Weight")).toInt();
item->setProperty("_k_weight", weight);
bool updateCurrentPage = false;
const KPageWidgetModel *model = qobject_cast<const KPageWidgetModel *>(pageWidget()->model());
Q_ASSERT(model);
const int siblingCount = model->rowCount();
int row = 0;
for (; row < siblingCount; ++row) {
KPageWidgetItem *siblingItem = model->item(model->index(row, 0));
if (siblingItem->property("_k_weight").toInt() > weight) {
// the item we found is heavier than the new module
// qDebug() << "adding KCM " << item->name() << " before " << siblingItem->name();
insertPage(siblingItem, item);
if (siblingItem == currentPage()) {
updateCurrentPage = true;
}
break;
}
}
if (row == siblingCount) {
// the new module is either the first or the heaviest item
// qDebug() << "adding KCM " << item->name() << " at the top level";
addPage(item);
}
connect(kcm, &KCModule::needsSaveChanged, this, [this]() {
d->clientChanged();
});
if (d->modules.count() == 1 || updateCurrentPage) {
setCurrentPage(item);
d->clientChanged();
}
return item;
}
void KCMultiDialog::clear()
{
for (int i = 0; i < d->modules.count(); ++i) {
removePage(d->modules[i].item);
}
d->modules.clear();
d->clientChanged();
}
void KCMultiDialog::setDefaultsIndicatorsVisible(bool show)
{
for (const auto &module : std::as_const(d->modules)) {
module.kcm->setDefaultsIndicatorsVisible(show);
}
}
#include "moc_kcmultidialog.cpp"
@@ -0,0 +1,161 @@
/*
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org>
SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCMULTIDIALOG_H
#define KCMULTIDIALOG_H
#include <QScrollArea>
#include <QScrollBar>
#include <KPageDialog>
#include <KPluginMetaData>
#include "kcmutils_export.h"
class KCMultiDialogPrivate;
/**
* @short A class that offers a KPageDialog containing config modules
*
* @author Matthias Elter <elter@kde.org>, Daniel Molkentin <molkentin@kde.org>
*/
class KCMUTILS_EXPORT KCMultiDialog : public KPageDialog
{
Q_OBJECT
public:
/**
* Constructs a new KCMultiDialog
*
* @param parent The parent widget
**/
explicit KCMultiDialog(QWidget *parent = nullptr);
/**
* Destructor
**/
~KCMultiDialog() override;
/**
* Add a module to the dialog. Its position will be determined based on the @c X-KDE-Weight value.
* @param metaData KPluginMetaData that will be used to load the plugin
* @param args The arguments that should be given to the KCModule when it is created
*/
KPageWidgetItem *addModule(const KPluginMetaData &metaData, const QVariantList &args = {});
/**
* Removes all modules from the dialog.
*/
void clear();
/**
* Show or hide an indicator when settings have changed from their default value
*
* @since 6.0
*/
void setDefaultsIndicatorsVisible(bool show);
Q_SIGNALS:
/**
* Emitted after all KCModules have been told to save their configuration.
*
* The applyClicked and okClicked signals are emitted before the
* configuration is saved.
*/
void configCommitted();
protected:
void closeEvent(QCloseEvent *event) override;
void showEvent(QShowEvent *event) override;
protected Q_SLOTS:
/**
* This slot is called when the user presses the "Default" Button.
* You can reimplement it if needed.
*
* @note Make sure you call the original implementation.
**/
void slotDefaultClicked();
/**
* This slot is called when the user presses the "Reset" Button.
* You can reimplement it if needed.
*
* @note Make sure you call the original implementation.
*/
void slotUser1Clicked();
/**
* This slot is called when the user presses the "Apply" Button.
* You can reimplement it if needed.
*
* @note Make sure you call the original implementation.
**/
void slotApplyClicked();
/**
* This slot is called when the user presses the "OK" Button.
* You can reimplement it if needed.
*
* @note Make sure you call the original implementation.
**/
void slotOkClicked();
/**
* This slot is called when the user presses the "Help" Button.
* It reads the X-DocPath field of the currently selected KControl
* module's .desktop file to find the path to the documentation,
* which it then attempts to load.
*
* You can reimplement this slot if needed.
*
* @note Make sure you call the original implementation.
**/
void slotHelpClicked();
private:
friend KCMultiDialogPrivate;
const std::unique_ptr<KCMultiDialogPrivate> d;
};
/**
* @brief Custom QScrollArea class that doesn't limit its size hint
*
* See original QScrollArea::sizeHint() function,
* where the size hint is bound by 36*24 font heights
*
* Workaround for https://bugreports.qt.io/browse/QTBUG-10459
*/
class UnboundScrollArea : public QScrollArea
{
Q_OBJECT
public:
QSize sizeHint() const override
{
if (widget()) {
// Try to avoid horizontal scrollbar, which just scrolls a scrollbar width.
// We always need to reserve space for the vertical scroll bar,
// because we cant know here whether vertical scrolling will be used.
QSize withScrollbar = widget()->sizeHint();
withScrollbar.rwidth() += verticalScrollBar()->sizeHint().width() + 4;
return withScrollbar;
} else {
return QScrollArea::sizeHint();
}
}
explicit UnboundScrollArea(QWidget *w)
: QScrollArea(w)
{
}
~UnboundScrollArea() override = default;
};
#endif
@@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCMULTIDIALOG_P_H
#define KCMULTIDIALOG_P_H
#include "kcmodule.h"
#include "kcmultidialog.h"
#include <QList>
#include <QStringList>
class KCModuleProxy;
class KPageWidgetItem;
class KCMultiDialogPrivate
{
public:
KCMultiDialogPrivate(KCMultiDialog *parent)
: currentModule(nullptr)
, q(parent)
{
}
KCModule *currentModule;
struct CreatedModule {
KCModule *kcm;
KPageWidgetItem *item;
QStringList componentNames;
bool firstShow = true;
};
typedef QList<CreatedModule> ModuleList;
ModuleList modules;
void slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous);
void clientChanged();
void dialogClosed();
void updateHeader(bool use, const QString &message);
KCMultiDialog *q;
void init();
void apply();
bool resolveChanges(KCModule *currentProxy);
bool moduleSave(KCModule *module);
};
#endif // KCMULTIDIALOG_P_H
@@ -0,0 +1,20 @@
// This file was generated by kcmutils_generate_module_data(): DO NOT EDIT!
#include "@HEADER_NAME@"
@INCLUDES_SETTINGS@
@OPEN_NAMESPACE@
@MODULE_DATA_CLASS_NAME@::@MODULE_DATA_CLASS_NAME@(QObject *parent)
: KCModuleData(parent)
@SETTINGS_CONSTRUCTOR_INITIALIZATION@
{
autoRegisterSkeletons();
}
@SETTINGS_METHOD_DEFINITION@
@CLOSE_NAMESPACE@
#include "moc_@CPP_FILENAME@"
@@ -0,0 +1,28 @@
// This file was generated by kcmutils_generate_module_data(): DO NOT EDIT!
#ifndef @GUARD_NAME@
#define @GUARD_NAME@
#include <QObject>
#include "kcmoduledata.h"
@OPEN_NAMESPACE@
@SETTINGS_FORWARD_DECLARATION@
class @MODULE_DATA_CLASS_NAME@ : public KCModuleData
{
Q_OBJECT
public:
explicit @MODULE_DATA_CLASS_NAME@(QObject *parent = nullptr);
@SETTINGS_METHOD_DECLARATION@
private:
@SETTINGS_ATTRIBUTE_DECLARATION@
};
@CLOSE_NAMESPACE@
#endif
@@ -0,0 +1,499 @@
/*
SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kpluginwidget.h"
#include "kcmoduleloader.h"
#include "kpluginproxymodel.h"
#include "kpluginwidget_p.h"
#include <kcmutils_debug.h>
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
#include <QLineEdit>
#include <QPainter>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStandardPaths>
#include <QStyle>
#include <QStyleOptionViewItem>
#include <QVBoxLayout>
#include <KAboutPluginDialog>
#include <KCategorizedSortFilterProxyModel>
#include <KCategorizedView>
#include <KCategoryDrawer>
#include <KLocalizedString>
#include <KPluginMetaData>
#include <KStandardGuiItem>
#include <utility>
static constexpr int s_margin = 5;
int KPluginWidgetPrivate::dependantLayoutValue(int value, int width, int totalWidth) const
{
if (listView->layoutDirection() == Qt::LeftToRight) {
return value;
}
return totalWidth - width - value;
}
KPluginWidget::KPluginWidget(QWidget *parent)
: QWidget(parent)
, d(new KPluginWidgetPrivate)
{
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
// Adding content margins on a QLineEdit breaks inline actions
auto lineEditWrapper = new QWidget(this);
auto lineEditWrapperLayout = new QVBoxLayout(lineEditWrapper);
lineEditWrapperLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
style()->pixelMetric(QStyle::PM_LayoutTopMargin),
style()->pixelMetric(QStyle::PM_LayoutRightMargin),
style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
d->lineEdit = new QLineEdit(lineEditWrapper);
d->lineEdit->setClearButtonEnabled(true);
d->lineEdit->setPlaceholderText(i18n("Search…"));
lineEditWrapperLayout->addWidget(d->lineEdit);
d->listView = new KCategorizedView(this);
d->listView->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge}));
d->categoryDrawer = new KCategoryDrawer(d->listView);
d->listView->setVerticalScrollMode(QListView::ScrollPerPixel);
d->listView->setAlternatingRowColors(true);
d->listView->setCategoryDrawer(d->categoryDrawer);
d->pluginModel = new KPluginModel(this);
connect(d->pluginModel, &KPluginModel::defaulted, this, &KPluginWidget::defaulted);
connect(d->pluginModel,
&QAbstractItemModel::dataChanged,
this,
[this](const QModelIndex &topLeft, const QModelIndex & /*bottomRight*/, const QList<int> &roles) {
if (roles.contains(KPluginModel::EnabledRole)) {
Q_EMIT pluginEnabledChanged(topLeft.data(KPluginModel::IdRole).toString(), topLeft.data(KPluginModel::EnabledRole).toBool());
Q_EMIT changed(d->pluginModel->isSaveNeeded());
}
});
d->proxyModel = new KPluginProxyModel(this);
d->proxyModel->setModel(d->pluginModel);
d->listView->setModel(d->proxyModel);
d->listView->setAlternatingRowColors(true);
auto pluginDelegate = new PluginDelegate(d.get(), this);
d->listView->setItemDelegate(pluginDelegate);
d->listView->setMouseTracking(true);
d->listView->viewport()->setAttribute(Qt::WA_Hover);
connect(d->lineEdit, &QLineEdit::textChanged, d->proxyModel, [this](const QString &query) {
d->proxyModel->setProperty("query", query);
d->proxyModel->invalidate();
});
connect(pluginDelegate, &PluginDelegate::configCommitted, this, &KPluginWidget::pluginConfigSaved);
connect(pluginDelegate, &PluginDelegate::changed, this, &KPluginWidget::pluginEnabledChanged);
layout->addWidget(lineEditWrapper);
layout->addWidget(d->listView);
// When a KPluginWidget instance gets focus,
// it should pass over the focus to its child searchbar.
setFocusProxy(d->lineEdit);
}
KPluginWidget::~KPluginWidget()
{
delete d->listView->itemDelegate();
delete d->listView; // depends on some other things in d, make sure this dies first.
}
void KPluginWidget::addPlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel)
{
d->pluginModel->addPlugins(plugins, categoryLabel);
d->proxyModel->sort(0);
}
void KPluginWidget::setConfig(const KConfigGroup &config)
{
d->pluginModel->setConfig(config);
}
void KPluginWidget::clear()
{
d->pluginModel->clear();
}
void KPluginWidget::save()
{
d->pluginModel->save();
}
void KPluginWidget::load()
{
d->pluginModel->load();
}
void KPluginWidget::defaults()
{
d->pluginModel->defaults();
}
bool KPluginWidget::isDefault() const
{
for (int i = 0, count = d->pluginModel->rowCount(); i < count; ++i) {
const QModelIndex index = d->pluginModel->index(i, 0);
if (d->pluginModel->data(index, Qt::CheckStateRole).toBool() != d->pluginModel->data(index, KPluginModel::EnabledByDefaultRole).toBool()) {
return false;
}
}
return true;
}
bool KPluginWidget::isSaveNeeded() const
{
return d->pluginModel->isSaveNeeded();
}
void KPluginWidget::setConfigurationArguments(const QVariantList &arguments)
{
d->kcmArguments = arguments;
}
QVariantList KPluginWidget::configurationArguments() const
{
return d->kcmArguments;
}
void KPluginWidget::showConfiguration(const QString &pluginId)
{
QModelIndex idx;
for (int i = 0, c = d->proxyModel->rowCount(); i < c; ++i) {
const auto currentIndex = d->proxyModel->index(i, 0);
const QString id = currentIndex.data(KPluginModel::IdRole).toString();
if (id == pluginId) {
idx = currentIndex;
break;
}
}
if (idx.isValid()) {
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->configure(idx);
} else {
qCWarning(KCMUTILS_LOG) << "Could not find plugin" << pluginId;
}
}
void KPluginWidget::setDefaultsIndicatorsVisible(bool isVisible)
{
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->resetModel();
d->showDefaultIndicator = isVisible;
}
void KPluginWidget::setAdditionalButtonHandler(const std::function<QPushButton *(const KPluginMetaData &)> &handler)
{
auto delegate = static_cast<PluginDelegate *>(d->listView->itemDelegate());
delegate->handler = handler;
}
PluginDelegate::PluginDelegate(KPluginWidgetPrivate *pluginSelector_d_ptr, QObject *parent)
: KWidgetItemDelegate(pluginSelector_d_ptr->listView, parent)
, checkBox(new QCheckBox)
, pushButton(new QPushButton)
, pluginSelector_d(pluginSelector_d_ptr)
{
// set the icon to make sure the size can be properly calculated
pushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
}
PluginDelegate::~PluginDelegate()
{
delete checkBox;
delete pushButton;
}
void PluginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (!index.isValid()) {
return;
}
const int xOffset = checkBox->sizeHint().width();
const bool disabled = !index.model()->data(index, KPluginModel::IsChangeableRole).toBool();
painter->save();
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
const int iconSize = option.rect.height() - (s_margin * 2);
QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString());
icon.paint(painter,
QRect(pluginSelector_d->dependantLayoutValue(s_margin + option.rect.left() + xOffset, iconSize, option.rect.width()),
s_margin + option.rect.top(),
iconSize,
iconSize));
QRect contentsRect(pluginSelector_d->dependantLayoutValue(s_margin * 2 + iconSize + option.rect.left() + xOffset,
option.rect.width() - (s_margin * 3) - iconSize - xOffset,
option.rect.width()),
s_margin + option.rect.top(),
option.rect.width() - (s_margin * 3) - iconSize - xOffset,
option.rect.height() - (s_margin * 2));
int lessHorizontalSpace = s_margin * 2 + pushButton->sizeHint().width();
if (index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) {
lessHorizontalSpace += s_margin + pushButton->sizeHint().width();
}
// Reserve space for extra button
if (handler) {
lessHorizontalSpace += s_margin + pushButton->sizeHint().width();
}
contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace);
if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.highlightedText().color());
}
if (pluginSelector_d->listView->layoutDirection() == Qt::RightToLeft) {
contentsRect.translate(lessHorizontalSpace, 0);
}
painter->save();
if (disabled) {
QPalette pal(option.palette);
pal.setCurrentColorGroup(QPalette::Disabled);
painter->setPen(pal.text().color());
}
painter->save();
QFont font = titleFont(option.font);
QFontMetrics fmTitle(font);
painter->setFont(font);
painter->drawText(contentsRect,
Qt::AlignLeft | Qt::AlignTop,
fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width()));
painter->restore();
painter->drawText(
contentsRect,
Qt::AlignLeft | Qt::AlignBottom,
option.fontMetrics.elidedText(index.model()->data(index, KPluginModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width()));
painter->restore();
painter->restore();
}
QSize PluginDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
int i = 5;
int j = 1;
if (index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid()) {
i = 6;
j = 2;
}
// Reserve space for extra button
if (handler) {
++j;
}
const QFont font = titleFont(option.font);
const QFontMetrics fmTitle(font);
const QString text = index.model()->data(index, Qt::DisplayRole).toString();
const QString comment = index.model()->data(index, KPluginModel::DescriptionRole).toString();
const int maxTextWidth = qMax(fmTitle.boundingRect(text).width(), option.fontMetrics.boundingRect(comment).width());
const auto iconSize = pluginSelector_d->listView->style()->pixelMetric(QStyle::PM_IconViewIconSize);
return QSize(maxTextWidth + iconSize + s_margin * i + pushButton->sizeHint().width() * j,
qMax(iconSize + s_margin * 2, fmTitle.height() + option.fontMetrics.height() + s_margin * 2));
}
QList<QWidget *> PluginDelegate::createItemWidgets(const QModelIndex &index) const
{
Q_UNUSED(index);
QList<QWidget *> widgetList;
auto enabledCheckBox = new QCheckBox;
connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::slotStateChanged);
auto aboutPushButton = new QPushButton;
aboutPushButton->setIcon(QIcon::fromTheme(QStringLiteral("help-about-symbolic")));
aboutPushButton->setToolTip(i18n("About"));
connect(aboutPushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotAboutClicked);
auto configurePushButton = new QPushButton;
configurePushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
configurePushButton->setToolTip(i18n("Configure"));
connect(configurePushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotConfigureClicked);
const static QList<QEvent::Type> blockedEvents{
QEvent::MouseButtonPress,
QEvent::MouseButtonRelease,
QEvent::MouseButtonDblClick,
QEvent::KeyPress,
QEvent::KeyRelease,
};
setBlockedEventTypes(enabledCheckBox, blockedEvents);
setBlockedEventTypes(aboutPushButton, blockedEvents);
setBlockedEventTypes(configurePushButton, blockedEvents);
widgetList << enabledCheckBox << aboutPushButton << configurePushButton;
if (handler) {
QPushButton *btn = handler(pluginSelector_d->pluginModel->data(index, KPluginModel::MetaDataRole).value<KPluginMetaData>());
if (btn) {
widgetList << btn;
}
}
return widgetList;
}
void PluginDelegate::updateItemWidgets(const QList<QWidget *> &widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const
{
int extraButtonWidth = 0;
QPushButton *extraButton = nullptr;
if (widgets.count() == 4) {
extraButton = static_cast<QPushButton *>(widgets[3]);
extraButtonWidth = extraButton->sizeHint().width() + s_margin;
}
auto checkBox = static_cast<QCheckBox *>(widgets[0]);
checkBox->resize(checkBox->sizeHint());
checkBox->move(pluginSelector_d->dependantLayoutValue(s_margin, checkBox->sizeHint().width(), option.rect.width()),
option.rect.height() / 2 - checkBox->sizeHint().height() / 2);
auto aboutPushButton = static_cast<QPushButton *>(widgets[1]);
const QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint();
aboutPushButton->resize(aboutPushButtonSizeHint);
aboutPushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - s_margin - aboutPushButtonSizeHint.width() - extraButtonWidth,
aboutPushButtonSizeHint.width(),
option.rect.width()),
option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2);
auto configurePushButton = static_cast<QPushButton *>(widgets[2]);
const QSize configurePushButtonSizeHint = configurePushButton->sizeHint();
configurePushButton->resize(configurePushButtonSizeHint);
configurePushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - s_margin * 2 - configurePushButtonSizeHint.width()
- aboutPushButtonSizeHint.width() - extraButtonWidth,
configurePushButtonSizeHint.width(),
option.rect.width()),
option.rect.height() / 2 - configurePushButtonSizeHint.height() / 2);
if (extraButton) {
const QSize extraPushButtonSizeHint = extraButton->sizeHint();
extraButton->resize(extraPushButtonSizeHint);
extraButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - extraButtonWidth, extraPushButtonSizeHint.width(), option.rect.width()),
option.rect.height() / 2 - extraPushButtonSizeHint.height() / 2);
}
if (!index.isValid() || !index.internalPointer()) {
checkBox->setVisible(false);
aboutPushButton->setVisible(false);
configurePushButton->setVisible(false);
if (extraButton) {
extraButton->setVisible(false);
}
} else {
const bool enabledByDefault = index.model()->data(index, KPluginModel::EnabledByDefaultRole).toBool();
const bool enabled = index.model()->data(index, KPluginModel::EnabledRole).toBool();
checkBox->setProperty("_kde_highlight_neutral", pluginSelector_d->showDefaultIndicator && enabledByDefault != enabled);
checkBox->setChecked(index.model()->data(index, Qt::CheckStateRole).toBool());
checkBox->setEnabled(index.model()->data(index, KPluginModel::IsChangeableRole).toBool());
configurePushButton->setVisible(index.model()->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>().isValid());
configurePushButton->setEnabled(index.model()->data(index, Qt::CheckStateRole).toBool());
}
}
void PluginDelegate::slotStateChanged(bool state)
{
if (!focusedIndex().isValid()) {
return;
}
QModelIndex index = focusedIndex();
const_cast<QAbstractItemModel *>(index.model())->setData(index, state, Qt::CheckStateRole);
}
void PluginDelegate::slotAboutClicked()
{
const QModelIndex index = focusedIndex();
auto pluginMetaData = index.data(KPluginModel::MetaDataRole).value<KPluginMetaData>();
auto *aboutPlugin = new KAboutPluginDialog(pluginMetaData, itemView());
aboutPlugin->setAttribute(Qt::WA_DeleteOnClose);
aboutPlugin->show();
}
void PluginDelegate::slotConfigureClicked()
{
configure(focusedIndex());
}
void PluginDelegate::configure(const QModelIndex &index)
{
const QAbstractItemModel *model = index.model();
const auto kcm = model->data(index, KPluginModel::ConfigRole).value<KPluginMetaData>();
auto configDialog = new QDialog(itemView());
configDialog->setAttribute(Qt::WA_DeleteOnClose);
configDialog->setModal(true);
configDialog->setWindowTitle(model->data(index, KPluginModel::NameRole).toString());
QWidget *kcmWrapper = new QWidget;
auto kcmInstance = KCModuleLoader::loadModule(kcm, kcmWrapper, pluginSelector_d->kcmArguments);
auto layout = new QVBoxLayout(configDialog);
layout->addWidget(kcmWrapper);
auto buttonBox = new QDialogButtonBox(configDialog);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
connect(buttonBox, &QDialogButtonBox::accepted, configDialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, configDialog, &QDialog::reject);
connect(configDialog, &QDialog::accepted, this, [kcmInstance, this, model, index]() {
Q_EMIT configCommitted(model->data(index, KPluginModel::IdRole).toString());
kcmInstance->save();
});
connect(configDialog, &QDialog::rejected, this, [kcmInstance]() {
kcmInstance->load();
});
connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, [kcmInstance] {
kcmInstance->defaults();
});
layout->addWidget(buttonBox);
// Load KCM right before showing it
kcmInstance->load();
configDialog->show();
}
QFont PluginDelegate::titleFont(const QFont &baseFont) const
{
QFont retFont(baseFont);
retFont.setBold(true);
return retFont;
}
#include "moc_kpluginwidget.cpp"
#include "moc_kpluginwidget_p.cpp"
@@ -0,0 +1,151 @@
/*
SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KPLUGINWIDGET_H
#define KPLUGINWIDGET_H
#include <QList>
#include <QWidget>
#include <KPluginMetaData>
#include <KSharedConfig>
#include <kcmutils_export.h>
#include <memory>
class QPushButton;
class KPluginWidgetPrivate;
/**
* @class KPluginWidget kpluginwidget.h KPluginWidget
* A widget that shows a list of available plugins and allows to disable/enable them and open their configuration UI.
*
* Plugins that get added to the KPluginWidget need to define the @c X-KDE-ConfigModule property.
* The value for this property is the namespace and file name of the KCM for the plugin.
* An example value is "kf6/krunner/kcms/kcm_krunner_charrunner", "kf6/krunner/kcms" is the namespace
* and "kcm_krunner_charrunner" the file name. The loaded KCMs don't need any embedded json metadata.
* @since 5.89
*/
class KCMUTILS_EXPORT KPluginWidget : public QWidget
{
Q_OBJECT
public:
explicit KPluginWidget(QWidget *parent = nullptr);
~KPluginWidget();
/**
* Adds the plugins with the given label to the widget
*/
void addPlugins(const QList<KPluginMetaData> &plugins, const QString &categoryLabel);
/**
* Set the config object that will be used to store the enabled state of the plugins.
* When porting away from KPluginSelector, the "Plugins" group from the config root should
* be used. For example:
* @code
* KSharedConfig::openConfig(QStringLiteral("krunnerrc"))->group("Plugins")
* @endcode
*/
void setConfig(const KConfigGroup &config);
/**
* Clears all the added plugins and any unsaved changes.
*/
void clear();
/**
* Saves the changes to the config set by @ref setConfig.
*/
void save();
/**
* Loads the enabled state of the plugins from the config set by setConfig()
* and clears any changes by the user.
* @since 5.91
*/
void load();
/**
* Resets the enabled state of the plugins to their defaults
* @see KPluginMetaData::isEnabledByDefault
*/
void defaults();
/**
* Returns @c true if the enabled state of each plugin is the same as that plugin's default state.
*/
bool isDefault() const;
/**
* Returns true if the plugin selector has any changes that are not yet saved to configuration.
* @see save()
*/
bool isSaveNeeded() const;
/**
* Sets the @p arguments with which the configuration modules will be initialized
*/
void setConfigurationArguments(const QVariantList &arguments);
/**
* Returns the configuration arguments that will be used
*/
QVariantList configurationArguments() const;
/**
* Shows the configuration dialog for the plugin @p pluginId if it's available
*/
void showConfiguration(const QString &pluginId);
/**
* Shows an indicator when a plugin status is different from default
*/
void setDefaultsIndicatorsVisible(bool isVisible);
/**
* Add additional widgets to each row of the plugin selector
* @param handler returns the additional button that should be displayed in the row;
* the handler can return a null pointer if no button should be displayed
*/
void setAdditionalButtonHandler(const std::function<QPushButton *(const KPluginMetaData &)> &handler);
Q_SIGNALS:
/**
* Emitted when any of the plugins are changed.
* @param pluginId id of the changed plugin
* @param enabled if the given plugin is currently enabled or disabled
*/
void pluginEnabledChanged(const QString &pluginId, bool enabled);
/**
* Emitted when any of the plugins are changed.
* @param changed if the KPluginWidget object contains changes
* @see needsSave
*/
void changed(bool enabled);
/**
* Emitted after the config of an embedded KCM has been saved. The
* argument is the name of the parent component that needs to reload
* its config.
*/
void pluginConfigSaved(const QString &pluginId);
/**
* Emitted after configuration is changed.
*
* @p isDefault @c true if the configuration state is the default, @c false otherwise
*/
void defaulted(bool isDefault);
private:
std::unique_ptr<KPluginWidgetPrivate> const d;
};
#endif
@@ -0,0 +1,87 @@
/*
SPDX-FileCopyrightText: 2007, 2006 Rafael Fernández López <ereslibre@kde.org>
SPDX-FileCopyrightText: 2002-2003 Matthias Kretz <kretz@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KPLUGINWIDGET_P_H
#define KPLUGINWIDGET_P_H
#include <QAbstractListModel>
#include <KCategorizedSortFilterProxyModel>
#include <KConfigGroup>
#include <kwidgetitemdelegate.h>
#include <kcmutils_export.h>
#include "kpluginmodel.h"
class QAbstractItemView;
class QCheckBox;
class QLabel;
class QLineEdit;
class QPushButton;
class KCategorizedView;
class KCategoryDrawer;
class KCModuleProxy;
class KPluginProxyModel;
class PluginDelegate;
class PluginEntry;
class KPluginWidgetPrivate
{
public:
int dependantLayoutValue(int value, int width, int totalWidth) const;
public:
QLineEdit *lineEdit = nullptr;
KCategorizedView *listView = nullptr;
KCategoryDrawer *categoryDrawer = nullptr;
KPluginModel *pluginModel = nullptr;
KPluginProxyModel *proxyModel = nullptr;
QVariantList kcmArguments;
bool showDefaultIndicator = false;
};
class PluginDelegate : public KWidgetItemDelegate
{
Q_OBJECT
public:
explicit PluginDelegate(KPluginWidgetPrivate *pluginSelector_d, QObject *parent = nullptr);
~PluginDelegate();
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void configure(const QModelIndex &idx);
Q_SIGNALS:
void changed(const QString &pluginId, bool enabled);
void configCommitted(const QString &pluginId);
protected:
QList<QWidget *> createItemWidgets(const QModelIndex &index) const override;
void updateItemWidgets(const QList<QWidget *> &widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override;
private Q_SLOTS:
void slotStateChanged(bool state);
void slotAboutClicked();
void slotConfigureClicked();
private:
QFont titleFont(const QFont &baseFont) const;
QCheckBox *checkBox;
QPushButton *pushButton;
KPluginWidgetPrivate *pluginSelector_d;
public:
std::function<QPushButton *(const KPluginMetaData &)> handler;
};
#endif // KPLUGINSELECTOR_P_H
@@ -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
@@ -0,0 +1,58 @@
# SPDX-FileCopyrightText: 2022 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-3-Clause
add_library(KF6KCMUtilsQuick
kquickconfigmodule.cpp
kquickmanagedconfigmodule.cpp
sharedqmlengine.cpp
kquickconfigmoduleloader.cpp
)
qt_extract_metatypes(KF6KCMUtilsQuick)
ecm_generate_export_header(KF6KCMUtilsQuick
BASE_NAME KCMUtilsQuick
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
DEPRECATED_BASE_VERSION 0
)
target_include_directories(KF6KCMUtilsQuick INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtilsQuick>")
target_link_libraries(KF6KCMUtilsQuick
PUBLIC
KF6::CoreAddons
KF6::ConfigCore
Qt6::Qml
KF6KCMUtilsCore
PRIVATE
KF6::I18n
KF6::ConfigGui
Qt6::Quick
kcmutils_logging_STATIC
)
if(TARGET KF6::I18nQml)
target_link_libraries(KF6KCMUtilsQuick PRIVATE
KF6::I18nQml
)
endif()
set_target_properties(KF6KCMUtilsQuick PROPERTIES
VERSION ${KCMUTILS_VERSION}
SOVERSION ${KCMUTILS_SOVERSION}
EXPORT_NAME KCMUtilsQuick)
ecm_generate_headers(KCMUtilsQuick_HEADERS
HEADER_NAMES
KQuickConfigModule
KQuickManagedConfigModule
KQuickConfigModuleLoader
REQUIRED_HEADERS KCMUtilsQuick_HEADERS
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kcmutilsquick_export.h
${KCMUtilsQuick_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KCMUtilsQuick COMPONENT Devel
)
install(TARGETS KF6KCMUtilsQuick EXPORT KF6KCMUtilsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
@@ -0,0 +1,251 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2001 Michael Goffioul <kdeprint@swing.be>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kquickconfigmodule.h"
#include "kabstractconfigmodule.h"
#include "kcmutils_debug.h"
#include "sharedqmlengine_p.h"
#include <QDebug>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlFileSelector>
#include <QQuickItem>
#include <QResource>
#include <QUrl>
#include <KLocalizedContext>
#include <KLocalizedString>
#include <memory>
class KQuickConfigModulePrivate
{
public:
KQuickConfigModulePrivate(KQuickConfigModule *module)
: q(module)
{
}
KQuickConfigModule *q;
SharedQmlEngine *engine = nullptr;
std::shared_ptr<QQmlEngine> passedInEngine;
QList<QQuickItem *> subPages;
int columnWidth = -1;
int currentIndex = 0;
QString errorString;
static QHash<QQmlContext *, KQuickConfigModule *> rootObjects;
QString getResourcePath(const QString &file)
{
return QLatin1String("/kcm/") + q->metaData().pluginId() + QLatin1String("/") + file;
}
QUrl getResourceUrl(const QString &resourcePath)
{
return QUrl(QLatin1String("qrc:") + resourcePath);
}
};
QHash<QQmlContext *, KQuickConfigModule *> KQuickConfigModulePrivate::rootObjects = QHash<QQmlContext *, KQuickConfigModule *>();
KQuickConfigModule::KQuickConfigModule(QObject *parent, const KPluginMetaData &metaData)
: KAbstractConfigModule(parent, metaData)
, d(new KQuickConfigModulePrivate(this))
{
}
void KQuickConfigModule::setInternalEngine(const std::shared_ptr<QQmlEngine> &engine)
{
d->passedInEngine = engine;
}
KQuickConfigModule::~KQuickConfigModule()
{
// in case mainUi was never called
if (d->engine) {
// delete the mainUi before removing the root object.
// Otherwise, we get lots of console errors about trying to read properties of null objects
delete d->engine->rootObject();
KQuickConfigModulePrivate::rootObjects.remove(d->engine->rootContext());
}
}
KQuickConfigModule *KQuickConfigModule::qmlAttachedProperties(QObject *object)
{
// at the moment of the attached object creation, the root item is the only one that hasn't a parent
// only way to avoid creation of this attached for everybody but the root item
const QQmlEngine *engine = qmlEngine(object);
QQmlContext *ctx = qmlContext(object);
// Search the qml context that is the "root" for the sharedqmlobject,
// which is an ancestor of qmlContext(object) and the direct child of the
// engine's root context: we can do this assumption on the internals as
// we are distributed on the same repo.
while (ctx->parentContext() && ctx->parentContext() != engine->rootContext()) {
ctx = ctx->parentContext();
}
if (!object->parent() && KQuickConfigModulePrivate::rootObjects.contains(ctx)) {
return KQuickConfigModulePrivate::rootObjects.value(ctx);
} else {
return nullptr;
}
}
QQuickItem *KQuickConfigModule::mainUi()
{
Q_ASSERT(d->passedInEngine);
if (d->engine) {
return qobject_cast<QQuickItem *>(d->engine->rootObject());
}
d->errorString.clear();
d->engine = new SharedQmlEngine(d->passedInEngine, this);
const QString componentName = metaData().pluginId();
KQuickConfigModulePrivate::rootObjects[d->engine->rootContext()] = this;
d->engine->setTranslationDomain(componentName);
d->engine->setInitializationDelayed(true);
const QString resourcePath = d->getResourcePath(QStringLiteral("main.qml"));
if (QResource r(resourcePath); !r.isValid()) {
d->errorString = i18n("Could not find resource '%1'", resourcePath);
qCWarning(KCMUTILS_LOG) << "Could not find resource" << resourcePath;
return nullptr;
}
new QQmlFileSelector(d->engine->engine().get(), this);
d->engine->setSource(d->getResourceUrl(resourcePath));
d->engine->rootContext()->setContextProperty(QStringLiteral("kcm"), this);
d->engine->completeInitialization();
if (d->engine->isError()) {
d->errorString = d->engine->errorString();
return nullptr;
}
Q_EMIT mainUiReady();
return qobject_cast<QQuickItem *>(d->engine->rootObject());
}
void KQuickConfigModule::push(const QString &fileName, const QVariantMap &initialProperties)
{
// ensure main ui is created
if (!mainUi()) {
return;
}
const QString resourcePath = d->getResourcePath(fileName);
if (QResource r(resourcePath); !r.isValid()) {
qCWarning(KCMUTILS_LOG) << "Requested resource" << resourcePath << "does not exist";
}
QObject *object = d->engine->createObjectFromSource(d->getResourceUrl(resourcePath), d->engine->rootContext(), initialProperties);
QQuickItem *item = qobject_cast<QQuickItem *>(object);
if (!item) {
if (object) {
object->deleteLater();
}
return;
}
d->subPages << item;
Q_EMIT pagePushed(item);
Q_EMIT depthChanged(depth());
setCurrentIndex(d->currentIndex + 1);
}
void KQuickConfigModule::push(QQuickItem *item)
{
// ensure main ui is created
if (!mainUi()) {
return;
}
d->subPages << item;
Q_EMIT pagePushed(item);
Q_EMIT depthChanged(depth());
setCurrentIndex(d->currentIndex + 1);
}
void KQuickConfigModule::pop()
{
if (QQuickItem *page = takeLast()) {
page->deleteLater();
}
}
QQuickItem *KQuickConfigModule::takeLast()
{
if (d->subPages.isEmpty()) {
return nullptr;
}
QQuickItem *page = d->subPages.takeLast();
Q_EMIT pageRemoved();
Q_EMIT depthChanged(depth());
setCurrentIndex(qMin(d->currentIndex, depth() - 1));
return page;
}
int KQuickConfigModule::columnWidth() const
{
return d->columnWidth;
}
void KQuickConfigModule::setColumnWidth(int width)
{
if (d->columnWidth == width) {
return;
}
d->columnWidth = width;
Q_EMIT columnWidthChanged(width);
}
int KQuickConfigModule::depth() const
{
return d->subPages.count() + 1;
}
void KQuickConfigModule::setCurrentIndex(int index)
{
if (index < 0 || index > d->subPages.count() || index == d->currentIndex) {
return;
}
d->currentIndex = index;
Q_EMIT currentIndexChanged(index);
}
int KQuickConfigModule::currentIndex() const
{
return d->currentIndex;
}
std::shared_ptr<QQmlEngine> KQuickConfigModule::engine() const
{
return d->engine->engine();
}
QString KQuickConfigModule::errorString() const
{
return d->errorString;
}
QQuickItem *KQuickConfigModule::subPage(int index) const
{
return d->subPages[index];
}
#include "moc_kquickconfigmodule.cpp"
@@ -0,0 +1,254 @@
/*
SPDX-FileCopyrightText: 1999 Matthias Hoelzer-Kluepfel <hoelzer@kde.org>
SPDX-FileCopyrightText: 2001 Michael Goffioul <kdeprint@swing.be>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2009 Dario Freddi <drf@kde.org>
SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KQUICKCONFIGMODULE_H
#define KQUICKCONFIGMODULE_H
#include "kcmutilsquick_export.h"
#include <QObject>
#include <QQmlComponent>
#include <QStringList>
#include <QVariant>
#include <KPluginFactory>
#include <KPluginMetaData>
#include <memory>
#include <qqmlintegration.h>
#include "kabstractconfigmodule.h"
#include "kquickconfigmoduleloader.h"
class QQuickItem;
class QQmlEngine;
class KQuickConfigModulePrivate;
/**
* @class KQuickConfigModule kquickconfigmodule.h KQuickConfigModule
*
* The base class for QtQuick configuration modules.
* Configuration modules are realized as plugins that are dynamically loaded.
*
* All the necessary glue logic and the GUI bells and whistles
* are provided by the control center and must not concern
* the module author.
*
* To write a config module, you have to create a C++ plugin
* and an accompaning QML user interface.
*
* To allow KCMUtils to load your ConfigModule subclass, you must create a KPluginFactory implementation.
*
* \code
* #include <KPluginFactory>
*
* K_PLUGIN_CLASS_WITH_JSON(MyConfigModule, "yourmetadata.json")
* \endcode
*
* The constructor of the ConfigModule then looks like this:
* \code
* YourConfigModule::YourConfigModule(QObject *parent, const KPluginMetaData &metaData)
* : KQuickConfigModule(parent, metaData)
* {
* }
* \endcode
*
* The QML part must be in the KPackage format, installed under share/kpackage/kcms.
* @see KPackage::Package
*
* The package must have the same name as the plugin filename, to be installed
* by CMake with the command:
* \code
* kpackage_install_package(packagedir kcm_yourconfigmodule kcms)
* \endcode
* The "packagedir" is the subdirectory in the source tree where the package sources are
* located, and "kcm_yourconfigmodule" is id of the plugin.
* Finally "kcms" is the literal string "kcms", so that the package is
* installed as a configuration module (and not some other kind of package).
*
* The QML part can access all the properties of ConfigModule (together with the properties
* defined in its subclass) by accessing to the global object "kcm", or with the
* import of "org.kde.kcmutils" the ConfigModule attached property.
*
* \code
* import QtQuick
* import QtQuick.Controls as QQC2
* import org.kde.kcmutils as KCMUtils
* import org.kde.kirigami as Kirigami
*
* Item {
* // implicit size will be used as initial size when loaded in kcmshell6
* implicitWidth: Kirigami.Units.gridUnit * 30
* implicitHeight: Kirigami.Units.gridUnit * 30
*
* KCMUtils.ConfigModule.buttons: KCMUtils.ConfigModule.Help | KCMUtils.ConfigModule.Apply
*
* QQC2.Label {
* // The following two bindings are equivalent:
* text: kcm.needsSave
* enabled: KCMUtils.ConfigModule.needsSave
* }
* }
* \endcode
*
* See https://develop.kde.org/docs/extend/kcm/ for more detailed documentation.
* @since 6.0
*/
class KCMUTILSQUICK_EXPORT KQuickConfigModule : public KAbstractConfigModule
{
Q_OBJECT
Q_PROPERTY(QQuickItem *mainUi READ mainUi CONSTANT)
Q_PROPERTY(int columnWidth READ columnWidth WRITE setColumnWidth NOTIFY columnWidthChanged)
Q_PROPERTY(int depth READ depth NOTIFY depthChanged)
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
QML_NAMED_ELEMENT(ConfigModule)
QML_ATTACHED(KQuickConfigModule)
public:
/**
* Destroys the module.
*/
~KQuickConfigModule() override;
/**
* @return the qml engine that built the main config UI
*/
std::shared_ptr<QQmlEngine> engine() const;
/**
* The error string in case the mainUi failed to load.
*/
QString errorString() const;
// QML property accessors
/**
* @return The main UI for this configuration module. It's a QQuickItem coming from
* the QML package named the same as the KAboutData's component name for
* this config module
*/
QQuickItem *mainUi();
/*
* @return a subpage at a given depth
* @note This does not include the mainUi. i.e a depth of 2 is a mainUi and one subPage
* at index 0
*/
QQuickItem *subPage(int index) const;
/**
* returns the width the kcm wants in column mode.
* If a columnWidth is valid ( > 0 ) and less than the systemsettings' view width,
* more than one will be visible at once, and the first page will be a sidebar to the last page pushed.
* As default, this is -1 which will make the shell always show only one page at a time.
*/
int columnWidth() const;
/**
* Sets the column width we want.
*/
void setColumnWidth(int width);
/**
* @returns how many pages this kcm has.
* It is guaranteed to be at least 1 (the main ui) plus how many times a new page has been pushed without pop
*/
int depth() const;
/**
* Sets the current page index this kcm should display
*/
void setCurrentIndex(int index);
/**
* @returns the index of the page this kcm should display
*/
int currentIndex() const;
static KQuickConfigModule *qmlAttachedProperties(QObject *object);
public Q_SLOTS:
/**
* Push a new sub page in the KCM hierarchy: pages will be seen as a Kirigami PageRow
*/
void push(const QString &fileName, const QVariantMap &initialProperties = QVariantMap());
/**
*
*/
void push(QQuickItem *item);
/**
* pop the last page of the KCM hierarchy, the page is destroyed
*/
void pop();
/**
* remove and return the last page of the KCM hierarchy:
* the popped page won't be deleted, it's the caller's responsibility to manage the lifetime of the returned item
* @returns the last page if any, nullptr otherwise
*/
QQuickItem *takeLast();
Q_SIGNALS:
// QML NOTIFY signaling
/**
* Emitted when a new sub page is pushed
*/
void pagePushed(QQuickItem *page);
/**
* Emitted when a sub page is popped
*/
// RFC: page argument?
void pageRemoved();
/**
* Emitted when the wanted column width of the kcm changes
*/
void columnWidthChanged(int width);
/**
* Emitted when the current page changed
*/
void currentIndexChanged(int index);
/**
* Emitted when the number of pages changed
*/
void depthChanged(int index);
/**
* Emitted when the main Ui has loaded successfully and `mainUi()` is available
*/
void mainUiReady();
protected:
/**
* Base class for all QtQuick config modules.
* Use KQuickConfigModuleLoader to instantiate this class
*
* @note do not emit changed signals here, since they are not yet connected to any slot.
*/
explicit KQuickConfigModule(QObject *parent, const KPluginMetaData &metaData);
private:
void setInternalEngine(const std::shared_ptr<QQmlEngine> &engine);
friend KPluginFactory::Result<KQuickConfigModule>
KQuickConfigModuleLoader::loadModule(const KPluginMetaData &metaData, QObject *parent, const QVariantList &args, const std::shared_ptr<QQmlEngine> &engine);
const std::unique_ptr<KQuickConfigModulePrivate> d;
};
#endif // KQUICKCONFIGMODULE_H
@@ -0,0 +1,50 @@
/*
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kquickconfigmoduleloader.h"
#include "kcmutils_debug.h"
#include <KPluginFactory>
#include <QJsonArray>
#include <QQmlEngine>
#include "kquickconfigmodule.h"
std::weak_ptr<QQmlEngine> s_kcmutilsCreatedEngine;
KPluginFactory::Result<KQuickConfigModule>
KQuickConfigModuleLoader::loadModule(const KPluginMetaData &metaData, QObject *parent, const QVariantList &args, const std::shared_ptr<QQmlEngine> &engineArg)
{
const auto factoryResult = KPluginFactory::loadFactory(metaData);
KPluginFactory::Result<KQuickConfigModule> result;
if (!factoryResult) {
result.errorReason = factoryResult.errorReason;
result.errorString = factoryResult.errorString;
result.errorText = factoryResult.errorText;
return result;
}
KPluginFactory *factory = factoryResult.plugin;
factory->setMetaData(KPluginMetaData(metaData));
const QVariantList pluginArgs = QVariantList(args) << metaData.rawData().value(QLatin1String("X-KDE-KCM-Args")).toArray().toVariantList();
if (const auto kcm = factory->create<KQuickConfigModule>(parent, pluginArgs)) {
const std::shared_ptr<QQmlEngine> engine =
engineArg ? engineArg : (s_kcmutilsCreatedEngine.expired() ? std::make_shared<QQmlEngine>() : s_kcmutilsCreatedEngine.lock());
if (!engineArg && s_kcmutilsCreatedEngine.expired()) {
s_kcmutilsCreatedEngine = engine;
}
kcm->setInternalEngine(engine);
result.plugin = kcm;
qCDebug(KCMUTILS_LOG) << "loaded QML KCM" << metaData.fileName();
} else {
result.errorReason = KPluginFactory::INVALID_KPLUGINFACTORY_INSTANTIATION;
}
return result;
}
@@ -0,0 +1,26 @@
/*
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KQUICKCONFIGMODULELOADER_H
#define KQUICKCONFIGMODULELOADER_H
#include "kcmutilsquick_export.h"
#include <KPluginFactory>
#include <memory>
class QQmlEngine;
class KQuickConfigModule;
namespace KQuickConfigModuleLoader
{
/**
* Loads a QML KCM from the given plugin metadata.
* @param engine The QQmlEngine to use, if not set, an internal engine will be created. If your application has an exisiting engine, this must be passed in.
*/
KCMUTILSQUICK_EXPORT KPluginFactory::Result<KQuickConfigModule>
loadModule(const KPluginMetaData &metaData, QObject *parent = nullptr, const QVariantList &args = {}, const std::shared_ptr<QQmlEngine> &engine = {});
}
#endif
@@ -0,0 +1,140 @@
/*
SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kquickmanagedconfigmodule.h"
#include <KCoreConfigSkeleton>
#include <QPointer>
#include <QTimer>
class KQuickManagedConfigModulePrivate
{
public:
KQuickManagedConfigModulePrivate(KQuickManagedConfigModule *mod)
{
QTimer::singleShot(0, mod, [mod]() {
const auto skeletons = mod->findChildren<KCoreConfigSkeleton *>();
for (auto *skeleton : skeletons) {
mod->registerSettings(skeleton);
}
});
}
QList<QPointer<KCoreConfigSkeleton>> _skeletons;
};
KQuickManagedConfigModule::KQuickManagedConfigModule(QObject *parent, const KPluginMetaData &metaData)
: KQuickConfigModule(parent, metaData)
, d(new KQuickManagedConfigModulePrivate(this))
{
}
KQuickManagedConfigModule::~KQuickManagedConfigModule() = default;
void KQuickManagedConfigModule::load()
{
for (const auto &skeleton : std::as_const(d->_skeletons)) {
if (skeleton) {
skeleton->load();
}
}
}
void KQuickManagedConfigModule::save()
{
for (const auto &skeleton : std::as_const(d->_skeletons)) {
if (skeleton) {
skeleton->save();
}
}
}
void KQuickManagedConfigModule::defaults()
{
for (const auto &skeleton : std::as_const(d->_skeletons)) {
if (skeleton) {
skeleton->setDefaults();
}
}
}
bool KQuickManagedConfigModule::isSaveNeeded() const
{
return false;
}
bool KQuickManagedConfigModule::isDefaults() const
{
return true;
}
void KQuickManagedConfigModule::settingsChanged()
{
bool needsSave = false;
bool representsDefaults = true;
for (const auto &skeleton : std::as_const(d->_skeletons)) {
if (skeleton) {
needsSave |= skeleton->isSaveNeeded();
representsDefaults &= skeleton->isDefaults();
}
}
if (!needsSave) {
needsSave = isSaveNeeded();
}
if (representsDefaults) {
representsDefaults = isDefaults();
}
setRepresentsDefaults(representsDefaults);
setNeedsSave(needsSave);
}
void KQuickManagedConfigModule::registerSettings(KCoreConfigSkeleton *skeleton)
{
if (!skeleton || d->_skeletons.contains(skeleton)) {
return;
}
d->_skeletons.append(skeleton);
auto settingsChangedSlotIndex = metaObject()->indexOfMethod("settingsChanged()");
auto settingsChangedSlot = metaObject()->method(settingsChangedSlotIndex);
QObject::connect(skeleton, &KCoreConfigSkeleton::configChanged, this, &KQuickManagedConfigModule::settingsChanged);
const auto items = skeleton->items();
for (auto item : items) {
const auto itemHasSignals = dynamic_cast<KConfigCompilerSignallingItem *>(item) || dynamic_cast<KPropertySkeletonItem *>(item);
if (!itemHasSignals) {
continue;
}
auto name = item->name();
if (name.at(0).isUpper()) {
name[0] = name[0].toLower();
}
const auto metaObject = skeleton->metaObject();
const auto propertyIndex = metaObject->indexOfProperty(name.toUtf8().constData());
const auto property = metaObject->property(propertyIndex);
if (!property.hasNotifySignal()) {
continue;
}
const auto changedSignal = property.notifySignal();
QObject::connect(skeleton, changedSignal, this, settingsChangedSlot);
}
auto toRemove = std::remove_if(d->_skeletons.begin(), d->_skeletons.end(), [](const QPointer<KCoreConfigSkeleton> &value) {
return value.isNull();
});
d->_skeletons.erase(toRemove, d->_skeletons.end());
QMetaObject::invokeMethod(this, "settingsChanged", Qt::QueuedConnection);
}
#include "moc_kquickmanagedconfigmodule.cpp"
@@ -0,0 +1,137 @@
/*
SPDX-FileCopyrightText: 2019 Kevin Ottens <kevin.ottens@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef MANAGEDCONFIGMODULE_H
#define MANAGEDCONFIGMODULE_H
#include "kquickconfigmodule.h"
#include <memory>
class KCoreConfigSkeleton;
class KQuickManagedConfigModulePrivate;
/**
* @class KQuickManagedConfigModule managedconfigmodule.h KQuickAddons/ManagedConfigModule
*
* The base class for configuration modules using KConfigXT settings.
*
* We are assuming here that SettingsObject is a class generated from a kcfg file
* and that it will be somehow exposed as a constant property to be used from the QML side.
* It will be automatically discovered by ManagedConfigModule which will update
* the saveNeeded and defaults inherited properties by itself. Thus by inheriting from
* this class you shall not try to manage those properties yourselves.
* By passing in "this" as a parent, we prevent memory leaks and allow KQuickManagedConfigModule to
* automatically find the created settings object.
*
* The constructor of the ConfigModule then looks like this:
* \code
* YourConfigModule::YourConfigModule(QObject *parent, const KPluginMetaData &metaData)
* : ManagedConfigModule(parent, metaData)
* , m_settingsObject(new SettingsObject(this))
* {
* }
* \endcode
*
* @since 6.0
*/
class KCMUTILSQUICK_EXPORT KQuickManagedConfigModule : public KQuickConfigModule
{
Q_OBJECT
public:
/**
* Destroys the module.
*/
~KQuickManagedConfigModule() override;
public Q_SLOTS:
/**
* Load the configuration data into the module.
*
* This method is invoked whenever the module should read its configuration
* (most of the times from a config file) and update the user interface.
* This happens when the user clicks the "Reset" button in the control
* center, to undo all of his changes and restore the currently valid
* settings. It is also called right after construction.
*
* By default this will load the settings from the child setting objects
* of this module.
*/
void load() override;
/**
* Save the configuration data.
*
* The save method stores the config information as shown
* in the user interface in the config files.
* It is called when the user clicks "Apply" or "Ok".
*
* By default this will save the child setting objects
* of this module.
*/
void save() override;
/**
* Sets the configuration to sensible default values.
*
* This method is called when the user clicks the "Default"
* button. It should set the display to useful values.
*
* By default this will reset to defaults the child setting objects
* of this module.
*/
void defaults() override;
protected Q_SLOTS:
/**
* Forces the module to reevaluate the saveNeeded and
* representsDefault state.
*
* This is required for some modules which might have
* some settings managed outside of KConfigXT objects.
*/
void settingsChanged();
/**
* Allow to register manually settings class generated from a kcfg file.
* Used by derived class when automatic discovery is not possible.
* After skeleton is registered it will automatically call settingsChanged().
*/
void registerSettings(KCoreConfigSkeleton *skeleton);
protected:
/**
* Base class for all KControlModules.
* Use KQuickConfigModuleLoader to instantiate this class
*
* @note do not emit changed signals here, since they are not yet connected to any slot.
*/
explicit KQuickManagedConfigModule(QObject *parent, const KPluginMetaData &metaData);
private:
/**
* Allows to indicate if the module requires saving.
*
* By default this returns false, it needs to be overridden only
* if the module has state outside of the settings declared in
* the KConfigXT classes it uses.
*/
virtual bool isSaveNeeded() const;
/**
* Allows to indicate if the module state is representing its defaults.
*
* By default this returns true, it needs to be overridden only
* if the module has state outside of the settings declared in
* the KConfigXT classes it uses.
*/
virtual bool isDefaults() const;
const std::unique_ptr<KQuickManagedConfigModulePrivate> d;
friend class KQuickManagedConfigModulePrivate;
};
#endif // MANAGEDCONFIGMODULE_H
@@ -0,0 +1,286 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "sharedqmlengine_p.h"
#include <KLocalizedQmlContext>
#include <QDebug>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlIncubator>
#include <QQmlNetworkAccessManagerFactory>
#include <QQuickItem>
#include <QResource>
#include <QTimer>
#include "kcmutils_debug.h"
using namespace Qt::StringLiterals;
class SharedQmlEnginePrivate
{
public:
SharedQmlEnginePrivate(const std::shared_ptr<QQmlEngine> &engine, SharedQmlEngine *parent)
: q(parent)
, component(nullptr)
, delay(false)
, m_engine(engine)
{
executionEndTimer.setInterval(0);
executionEndTimer.setSingleShot(true);
QObject::connect(&executionEndTimer, &QTimer::timeout, q, [this]() {
scheduleExecutionEnd();
});
}
~SharedQmlEnginePrivate()
{
delete incubator.object();
}
void errorPrint(QQmlComponent *component, QQmlIncubator *incubator = nullptr);
void execute(const QUrl &source);
void scheduleExecutionEnd();
void minimumWidthChanged();
void minimumHeightChanged();
void maximumWidthChanged();
void maximumHeightChanged();
void preferredWidthChanged();
void preferredHeightChanged();
void checkInitializationCompleted();
SharedQmlEngine *q;
QUrl source;
QQmlIncubator incubator;
QQmlComponent *component;
QTimer executionEndTimer;
KLocalizedQmlContext *context{nullptr};
QQmlContext *rootContext;
bool delay;
std::shared_ptr<QQmlEngine> m_engine;
};
void SharedQmlEnginePrivate::errorPrint(QQmlComponent *component, QQmlIncubator *incubator)
{
QList<QQmlError> errors;
if (component && component->isError()) {
errors = component->errors();
} else if (incubator && incubator->isError()) {
errors = incubator->errors();
} else {
return;
}
qCWarning(KCMUTILS_LOG).noquote() << "Error loading QML file" << component->url().toString();
for (const auto &error : errors) {
constexpr const QLatin1String indent(" ");
qCWarning(KCMUTILS_LOG).noquote().nospace() << indent << error;
}
}
void SharedQmlEnginePrivate::execute(const QUrl &source)
{
Q_ASSERT(!source.isEmpty());
delete component;
component = new QQmlComponent(m_engine.get(), q);
delete incubator.object();
m_engine->addImportPath(QStringLiteral("qrc:/"));
component->loadUrl(source);
if (delay) {
executionEndTimer.start(0);
} else {
scheduleExecutionEnd();
}
}
void SharedQmlEnginePrivate::scheduleExecutionEnd()
{
if (component->isReady() || component->isError()) {
q->completeInitialization();
} else {
QObject::connect(component, &QQmlComponent::statusChanged, q, [this]() {
q->completeInitialization();
});
}
}
SharedQmlEngine::SharedQmlEngine(const std::shared_ptr<QQmlEngine> &engine, QObject *parent)
: QObject(parent)
, d(new SharedQmlEnginePrivate(engine, this))
{
d->rootContext = new QQmlContext(engine.get());
d->rootContext->setParent(this); // Delete the context when deleting the shared engine
d->context = new KLocalizedQmlContext(d->rootContext);
d->rootContext->setContextObject(d->context);
}
SharedQmlEngine::~SharedQmlEngine() = default;
void SharedQmlEngine::setTranslationDomain(const QString &translationDomain)
{
d->context->setTranslationDomain(translationDomain);
}
QString SharedQmlEngine::translationDomain() const
{
return d->context->translationDomain();
}
void SharedQmlEngine::setSource(const QUrl &source)
{
d->source = source;
d->execute(source);
}
QUrl SharedQmlEngine::source() const
{
return d->source;
}
void SharedQmlEngine::setInitializationDelayed(const bool delay)
{
d->delay = delay;
}
bool SharedQmlEngine::isInitializationDelayed() const
{
return d->delay;
}
std::shared_ptr<QQmlEngine> SharedQmlEngine::engine()
{
return d->m_engine;
}
QObject *SharedQmlEngine::rootObject() const
{
if (d->incubator.isLoading()) {
qCWarning(KCMUTILS_LOG) << "Trying to use rootObject before initialization is completed, whilst using setInitializationDelayed. Forcing completion";
d->incubator.forceCompletion();
}
return d->incubator.object();
}
QQmlComponent *SharedQmlEngine::mainComponent() const
{
return d->component;
}
QQmlContext *SharedQmlEngine::rootContext() const
{
return d->rootContext;
}
bool SharedQmlEngine::isError() const
{
return !d->m_engine || !d->component || d->component->isError() || d->incubator.isError();
}
static QString qmlErrorsToString(const QList<QQmlError> &errors)
{
QString ret;
for (const auto &e : errors) {
ret += e.url().toString() + QLatin1Char(':') + QString::number(e.line()) + QLatin1Char(' ') + e.description() + QLatin1Char('\n');
}
return ret;
}
QString SharedQmlEngine::errorString() const
{
if (d->component && d->component->isError()) {
return d->component->errorString();
} else if (d->incubator.isError()) {
return qmlErrorsToString(d->incubator.errors());
} else {
return {};
}
}
void SharedQmlEnginePrivate::checkInitializationCompleted()
{
if (!incubator.isReady() && !incubator.isError()) {
QTimer::singleShot(0, q, [this]() {
checkInitializationCompleted();
});
return;
}
if (!incubator.object()) {
errorPrint(component, &incubator);
}
Q_EMIT q->finished();
}
void SharedQmlEngine::completeInitialization(const QVariantMap &initialProperties)
{
d->executionEndTimer.stop();
if (d->incubator.object()) {
return;
}
if (!d->component) {
qCWarning(KCMUTILS_LOG) << "No component for" << source();
return;
}
if (!d->component->isReady()) {
d->errorPrint(d->component);
return;
}
d->incubator.setInitialProperties(initialProperties);
d->component->create(d->incubator, d->rootContext);
if (d->delay) {
d->checkInitializationCompleted();
} else {
d->incubator.forceCompletion();
if (!d->incubator.object()) {
d->errorPrint(d->component, &d->incubator);
}
Q_EMIT finished();
}
}
QObject *SharedQmlEngine::createObjectFromSource(const QUrl &source, QQmlContext *context, const QVariantMap &initialProperties)
{
QQmlComponent *component = new QQmlComponent(d->m_engine.get(), this);
component->loadUrl(source);
return createObjectFromComponent(component, context, initialProperties);
}
QObject *SharedQmlEngine::createObjectFromComponent(QQmlComponent *component, QQmlContext *context, const QVariantMap &initialProperties)
{
QObject *object = component->createWithInitialProperties(initialProperties, context ? context : d->rootContext);
if (!component->isError() && object) {
// memory management
const auto root = rootObject();
object->setParent(root);
component->setParent(object);
// visually reparent to root object if wasn't specified otherwise by initialProperties
if (!initialProperties.contains(QLatin1String("parent")) && root && root->isQuickItemType()) {
object->setProperty("parent", QVariant::fromValue(root));
}
return object;
} else {
d->errorPrint(component);
delete object;
return nullptr;
}
}
#include "moc_sharedqmlengine_p.cpp"
@@ -0,0 +1,182 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText:
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef PLASMA_SHAREDQMLENGINE_H
#define PLASMA_SHAREDQMLENGINE_H
#include "ki18n_version.h"
#include <QObject>
#include <QQmlComponent>
#include <QQmlContext>
#include <memory>
class QQmlComponent;
class QQmlEngine;
class KLocalizedQmlContext;
class SharedQmlEnginePrivate;
/**
* @class Plasma::SharedQmlEngine Plasma/sharedqmlengine.h Plasma/SharedQmlEngine
*
* @short An object that instantiates an entire QML context, with its own declarative engine
*
* Plasma::SharedQmlEngine provides a class to conveniently use QML based
* declarative user interfaces.
* A SharedQmlEngine corresponds to one QML file (which can include others).
* It will a shared QQmlEngine with a single root object, described in the QML file.
*/
class SharedQmlEngine : public QObject
{
Q_OBJECT
Q_PROPERTY(QUrl source READ source WRITE setSource)
Q_PROPERTY(QString translationDomain READ translationDomain WRITE setTranslationDomain)
Q_PROPERTY(bool initializationDelayed READ isInitializationDelayed WRITE setInitializationDelayed)
Q_PROPERTY(QObject *rootObject READ rootObject)
public:
/**
* Construct a new Plasma::SharedQmlEngine
*
* @param parent The QObject parent for this object.
*/
explicit SharedQmlEngine(const std::shared_ptr<QQmlEngine> &engine, QObject *parent = nullptr);
~SharedQmlEngine() override;
/**
* Call this method before calling setupBindings to install a translation domain for all
* i18n global functions. If a translation domain is set all i18n calls delegate to the
* matching i18nd calls with the provided translation domain.
*
* The translationDomain affects all i18n calls including those from imports. Because of
* that modules intended to be used as imports should prefer the i18nd variants and set
* the translation domain explicitly in each call.
*
* This method is only required if your declarative usage is inside a library. If it's
* in an application there is no need to set the translation domain as the application's
* domain can be used.
*
* @param translationDomain The translation domain to be used for i18n calls.
*/
void setTranslationDomain(const QString &translationDomain);
/**
* @return the translation domain for the i18n calls done in this QML engine
*/
QString translationDomain() const;
/**
* Sets the path of the QML file to parse and execute
*
* @param path the absolute path of a QML file
*/
void setSource(const QUrl &source);
/**
* @return the absolute path of the current QML file
*/
QUrl source() const;
/**
* Sets whether the execution of the QML file has to be delayed later in the event loop. It has to be called before setQmlPath().
* In this case it will be possible to assign new objects in the main engine context
* before the main component gets initialized.
* In that case it will be possible to access it immediately from the QML code.
* The initialization will either be completed automatically asynchronously
* or explicitly by calling completeInitialization()
*
* @param delay if true the initialization of the QML file will be delayed
* at the end of the event loop
*/
void setInitializationDelayed(const bool delay);
/**
* @return true if the initialization of the QML file will be delayed
* at the end of the event loop
*/
bool isInitializationDelayed() const;
/**
* @return the declarative engine that runs the qml file assigned to this widget.
*/
std::shared_ptr<QQmlEngine> engine();
/**
* @return the root object of the declarative object tree
*/
QObject *rootObject() const;
/**
* @return the main QQmlComponent of the engine
*/
QQmlComponent *mainComponent() const;
/**
* The component's creation context.
*/
QQmlContext *rootContext() const;
/*
* The component's or incubator's error status.
*/
bool isError() const;
/*
* The component's or incubator's error string.
*/
QString errorString() const;
/**
* Creates and returns an object based on the provided url to a Qml file
* with the same QQmlEngine and the same root context as the main object,
* that will be the parent of the newly created object
* @param source url where the QML file is located
* @param context The QQmlContext in which we will create the object,
* if 0 it will use the engine's root context
* @param initialProperties optional properties that will be set on
* the object when created (and before Component.onCompleted
* gets emitted
*/
QObject *createObjectFromSource(const QUrl &source, QQmlContext *context = nullptr, const QVariantMap &initialProperties = QVariantMap());
/**
* Creates and returns an object based on the provided QQmlComponent
* with the same QQmlEngine and the same root context as the admin object,
* that will be the parent of the newly created object
* @param component the component we want to instantiate
* @param context The QQmlContext in which we will create the object,
* if 0 it will use the engine's root context
* @param initialProperties optional properties that will be set on
* the object when created (and before Component.onCompleted
* gets emitted
*/
QObject *createObjectFromComponent(QQmlComponent *component, QQmlContext *context = nullptr, const QVariantMap &initialProperties = QVariantMap());
public Q_SLOTS:
/**
* Finishes the process of initialization.
* If isInitializationDelayed() is false, calling this will have no effect.
* @param initialProperties optional properties that will be set on
* the object when created (and before Component.onCompleted
* gets emitted
*/
void completeInitialization(const QVariantMap &initialProperties = QVariantMap());
Q_SIGNALS:
/**
* Emitted when the parsing and execution of the QML file is terminated
*/
void finished();
private:
const std::unique_ptr<SharedQmlEnginePrivate> d;
};
#endif // multiple inclusion guard