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:
@@ -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
@@ -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 can’t 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
|
||||
Reference in New Issue
Block a user