File indexing completed on 2024-05-19 05:38:25

0001 /*
0002     KCMStyle
0003     SPDX-FileCopyrightText: 2002 Karol Szwed <gallium@kde.org>
0004     SPDX-FileCopyrightText: 2002 Daniel Molkentin <molkentin@kde.org>
0005     SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
0006     SPDX-FileCopyrightText: 2009 Davide Bettio <davide.bettio@kdemail.net>
0007     SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@broulik.de>
0008     SPDX-FileCopyrightText: 2019 Cyril Rossi <cyril.rossi@enioka.com>
0009 
0010     SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>
0011     SPDX-FileCopyrightText: 2007 Ivan Cukic <ivan.cukic+kde@gmail.com>
0012     SPDX-FileCopyrightText: 2008 Petri Damsten <damu@iki.fi>
0013     SPDX-FileCopyrightText: 2000 TrollTech AS.
0014 
0015     SPDX-License-Identifier: GPL-2.0-only
0016 */
0017 
0018 #include "kcmstyle.h"
0019 #include "kcm_style_debug.h"
0020 
0021 #include "../kcms-common_p.h"
0022 #include "styleconfdialog.h"
0023 
0024 #include <KConfigGroup>
0025 #include <KLocalizedString>
0026 #include <KPluginFactory>
0027 #include <KToolBar>
0028 
0029 #include <QDBusPendingCallWatcher>
0030 #include <QDBusPendingReply>
0031 #include <QLibrary>
0032 #include <QMetaEnum>
0033 #include <QQuickItem>
0034 #include <QQuickRenderControl>
0035 #include <QQuickWindow>
0036 #include <QStyleFactory>
0037 #include <QWidget>
0038 #include <QWindow>
0039 
0040 #include "krdb.h"
0041 
0042 #include "kded_interface.h"
0043 
0044 #include "previewitem.h"
0045 #include "styledata.h"
0046 
0047 using namespace Qt::StringLiterals;
0048 
0049 K_PLUGIN_FACTORY_WITH_JSON(KCMStyleFactory, "kcm_style.json", registerPlugin<KCMStyle>(); registerPlugin<StyleData>();)
0050 
0051 extern "C" {
0052 Q_DECL_EXPORT void kcminit()
0053 {
0054     uint flags = KRdbExportQtSettings | KRdbExportQtColors | KRdbExportXftSettings | KRdbExportGtkTheme;
0055     KConfig _config(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals);
0056     KConfigGroup config(&_config, QStringLiteral("X11"));
0057 
0058     // This key is written by the "colors" module.
0059     bool exportKDEColors = config.readEntry("exportKDEColors", true);
0060     if (exportKDEColors) {
0061         flags |= KRdbExportColors;
0062     }
0063     runRdb(flags);
0064 }
0065 }
0066 
0067 KCMStyle::KCMStyle(QObject *parent, const KPluginMetaData &data)
0068     : KQuickManagedConfigModule(parent, data)
0069     , m_data(new StyleData(this))
0070     , m_model(new StylesModel(this))
0071 {
0072     const char *uri{"org.kde.private.kcms.style"};
0073 
0074     qmlRegisterUncreatableType<KCMStyle>(uri, 1, 0, "KCM", QStringLiteral("Cannot create instances of KCM"));
0075     qmlRegisterAnonymousType<StyleSettings>(uri, 1);
0076     qmlRegisterAnonymousType<StylesModel>(uri, 1);
0077     qmlRegisterType<PreviewItem>(uri, 1, 0, "PreviewItem");
0078 
0079     connect(m_model, &StylesModel::selectedStyleChanged, this, [this](const QString &style) {
0080         styleSettings()->setWidgetStyle(style);
0081     });
0082     connect(styleSettings(), &StyleSettings::widgetStyleChanged, this, [this] {
0083         m_model->setSelectedStyle(styleSettings()->widgetStyle());
0084     });
0085     connect(styleSettings(), &StyleSettings::iconsOnButtonsChanged, this, [this] {
0086         m_effectsDirty = true;
0087     });
0088     connect(styleSettings(), &StyleSettings::iconsInMenusChanged, this, [this] {
0089         m_effectsDirty = true;
0090     });
0091 
0092     m_gtkPage = new GtkPage(this);
0093     connect(m_gtkPage, &GtkPage::gtkThemeSettingsChanged, this, [this]() {
0094         settingsChanged();
0095     });
0096 }
0097 
0098 KCMStyle::~KCMStyle() = default;
0099 
0100 GtkPage *KCMStyle::gtkPage() const
0101 {
0102     return m_gtkPage;
0103 }
0104 
0105 StylesModel *KCMStyle::model() const
0106 {
0107     return m_model;
0108 }
0109 
0110 StyleSettings *KCMStyle::styleSettings() const
0111 {
0112     return m_data->settings();
0113 }
0114 
0115 KCMStyle::ToolBarStyle KCMStyle::mainToolBarStyle() const
0116 {
0117     return m_mainToolBarStyle;
0118 }
0119 
0120 void KCMStyle::setMainToolBarStyle(ToolBarStyle style)
0121 {
0122     if (m_mainToolBarStyle != style) {
0123         m_mainToolBarStyle = style;
0124         Q_EMIT mainToolBarStyleChanged();
0125 
0126         const QMetaEnum toolBarStyleEnum = QMetaEnum::fromType<ToolBarStyle>();
0127         styleSettings()->setToolButtonStyle(toolBarStyleEnum.valueToKey(m_mainToolBarStyle));
0128         m_effectsDirty = true;
0129     }
0130 }
0131 
0132 KCMStyle::ToolBarStyle KCMStyle::otherToolBarStyle() const
0133 {
0134     return m_otherToolBarStyle;
0135 }
0136 
0137 void KCMStyle::setOtherToolBarStyle(ToolBarStyle style)
0138 {
0139     if (m_otherToolBarStyle != style) {
0140         m_otherToolBarStyle = style;
0141         Q_EMIT otherToolBarStyleChanged();
0142 
0143         const QMetaEnum toolBarStyleEnum = QMetaEnum::fromType<ToolBarStyle>();
0144         styleSettings()->setToolButtonStyleOtherToolbars(toolBarStyleEnum.valueToKey(m_otherToolBarStyle));
0145         m_effectsDirty = true;
0146     }
0147 }
0148 
0149 void KCMStyle::configure(const QString &title, const QString &styleName, QQuickItem *ctx)
0150 {
0151     if (m_styleConfigDialog) {
0152         return;
0153     }
0154 
0155     const QString configPage = m_model->styleConfigPage(styleName);
0156     if (configPage.isEmpty()) {
0157         return;
0158     }
0159 
0160     QLibrary library(QPluginLoader(configPage).fileName());
0161     if (!library.load()) {
0162         qCWarning(KCM_STYLE_DEBUG) << "Failed to load style config page" << configPage << library.errorString();
0163         Q_EMIT showErrorMessage(i18n("There was an error loading the configuration dialog for this style."));
0164         return;
0165     }
0166 
0167     auto allocPtr = library.resolve("allocate_kstyle_config");
0168     if (!allocPtr) {
0169         qCWarning(KCM_STYLE_DEBUG) << "Failed to resolve allocate_kstyle_config in" << configPage;
0170         Q_EMIT showErrorMessage(i18n("There was an error loading the configuration dialog for this style."));
0171         return;
0172     }
0173 
0174     m_styleConfigDialog = new StyleConfigDialog(nullptr /*this*/, title);
0175     m_styleConfigDialog->setAttribute(Qt::WA_DeleteOnClose);
0176     m_styleConfigDialog->setWindowModality(Qt::WindowModal);
0177     m_styleConfigDialog->winId(); // so it creates windowHandle
0178 
0179     if (ctx && ctx->window()) {
0180         if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(ctx->window())) {
0181             m_styleConfigDialog->windowHandle()->setTransientParent(actualWindow);
0182         }
0183     }
0184 
0185     typedef QWidget *(*factoryRoutine)(QWidget *parent);
0186 
0187     // Get the factory, and make the widget.
0188     factoryRoutine factory = (factoryRoutine)(allocPtr); // Grmbl. So here I am on my
0189     //"never use C casts" moralizing streak, and I find that one can't go void* -> function ptr
0190     // even with a reinterpret_cast.
0191 
0192     QWidget *pluginConfig = factory(m_styleConfigDialog.data());
0193 
0194     // Insert it in...
0195     m_styleConfigDialog->setMainWidget(pluginConfig);
0196 
0197     //..and connect it to the wrapper
0198     connect(pluginConfig, SIGNAL(changed(bool)), m_styleConfigDialog.data(), SLOT(setDirty(bool)));
0199     connect(m_styleConfigDialog.data(), SIGNAL(defaults()), pluginConfig, SLOT(defaults()));
0200     connect(m_styleConfigDialog.data(), SIGNAL(save()), pluginConfig, SLOT(save()));
0201 
0202     connect(m_styleConfigDialog.data(), &QDialog::accepted, this, [this, styleName] {
0203         if (!m_styleConfigDialog->isDirty()) {
0204             return;
0205         }
0206 
0207         // Force re-rendering of the preview, to apply settings
0208         Q_EMIT styleReconfigured(styleName);
0209 
0210         // For now, ask all KDE apps to recreate their styles to apply the setitngs
0211         notifyKcmChange(GlobalChangeType::StyleChanged);
0212 
0213         // When user edited a style, assume they want to use it, too
0214         styleSettings()->setWidgetStyle(styleName);
0215     });
0216 
0217     m_styleConfigDialog->show();
0218 }
0219 
0220 bool KCMStyle::gtkConfigKdedModuleLoaded() const
0221 {
0222     return m_gtkConfigKdedModuleLoaded;
0223 }
0224 
0225 void KCMStyle::checkGtkConfigKdedModuleLoaded()
0226 {
0227     org::kde::kded6 kdedInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QDBusConnection::sessionBus());
0228     auto call = kdedInterface.loadedModules();
0229     auto *watcher = new QDBusPendingCallWatcher(call, this);
0230     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0231         QDBusPendingReply<QStringList> reply = *watcher;
0232         watcher->deleteLater();
0233 
0234         if (reply.isError()) {
0235             qCWarning(KCM_STYLE_DEBUG) << "Failed to check whether GTK Config KDED module is loaded" << reply.error().message();
0236             return;
0237         }
0238 
0239         const bool isLoaded = reply.value().contains(QLatin1String("gtkconfig"));
0240         if (m_gtkConfigKdedModuleLoaded != isLoaded) {
0241             m_gtkConfigKdedModuleLoaded = isLoaded;
0242             Q_EMIT gtkConfigKdedModuleLoadedChanged();
0243         }
0244     });
0245 }
0246 
0247 void KCMStyle::load()
0248 {
0249     checkGtkConfigKdedModuleLoaded();
0250 
0251     m_gtkPage->load();
0252 
0253     KQuickManagedConfigModule::load();
0254     m_model->load();
0255     m_previousStyle = styleSettings()->widgetStyle();
0256 
0257     loadSettingsToModel();
0258 
0259     m_effectsDirty = false;
0260 }
0261 
0262 void KCMStyle::save()
0263 {
0264     m_gtkPage->save();
0265 
0266     // Check whether the new style can actually be loaded before saving it.
0267     // Otherwise apps will use the default style despite something else having been written to the config
0268     bool newStyleLoaded = false;
0269     if (styleSettings()->widgetStyle() != m_previousStyle) {
0270         std::unique_ptr<QStyle> newStyle(QStyleFactory::create(styleSettings()->widgetStyle()));
0271         if (newStyle) {
0272             newStyleLoaded = true;
0273             m_previousStyle = styleSettings()->widgetStyle();
0274         } else {
0275             const QString styleDisplay = m_model->data(m_model->index(m_model->indexOfStyle(styleSettings()->widgetStyle()), 0), Qt::DisplayRole).toString();
0276             Q_EMIT showErrorMessage(i18n("Failed to apply selected style '%1'.", styleDisplay));
0277 
0278             // Reset selected style back to current in case of failure
0279             styleSettings()->setWidgetStyle(m_previousStyle);
0280         }
0281     }
0282 
0283     KQuickManagedConfigModule::save();
0284 
0285     // Export the changes we made to qtrc, and update all qt-only
0286     // applications on the fly, ensuring that we still follow the user's
0287     // export fonts/colors settings.
0288     uint flags = KRdbExportQtSettings | KRdbExportGtkTheme;
0289     KConfig _kconfig(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals);
0290     KConfigGroup kconfig(&_kconfig, u"X11"_s);
0291     bool exportKDEColors = kconfig.readEntry("exportKDEColors", true);
0292     if (exportKDEColors) {
0293         flags |= KRdbExportColors;
0294     }
0295     runRdb(flags);
0296 
0297     // Now allow KDE apps to reconfigure themselves.
0298     if (newStyleLoaded) {
0299         notifyKcmChange(GlobalChangeType::StyleChanged);
0300     }
0301 
0302     if (m_effectsDirty) {
0303         // This notifies listeners about:
0304         //  - GraphicEffectsLevel' config entry, (e.g. to set QPlatformTheme::ThemeHint::UiEffects)
0305         //  - ShowIconsOnPushButtons config entry, (e.g. to set QPlatformTheme::DialogButtonBoxButtonsHaveIcons)
0306         notifyKcmChange(GlobalChangeType::SettingsChanged, GlobalSettingsCategory::SETTINGS_STYLE);
0307 
0308         // FIXME - Doesn't apply all settings correctly due to bugs in KApplication/KToolbar.
0309         // Is this ^ still an issue?
0310         KToolBar::emitToolbarStyleChanged();
0311     }
0312 
0313     m_effectsDirty = false;
0314 }
0315 
0316 void KCMStyle::defaults()
0317 {
0318     m_gtkPage->defaults();
0319 
0320     // TODO the old code had a fallback chain but do we actually support not having Breeze for Plasma?
0321     // defaultStyle() -> oxygen -> plastique -> windows -> platinum -> motif
0322 
0323     KQuickManagedConfigModule::defaults();
0324 
0325     loadSettingsToModel();
0326 }
0327 
0328 void KCMStyle::loadSettingsToModel()
0329 {
0330     Q_EMIT styleSettings()->widgetStyleChanged();
0331 
0332     const QMetaEnum toolBarStyleEnum = QMetaEnum::fromType<ToolBarStyle>();
0333     setMainToolBarStyle(static_cast<ToolBarStyle>(toolBarStyleEnum.keyToValue(qUtf8Printable(styleSettings()->toolButtonStyle()))));
0334     setOtherToolBarStyle(static_cast<ToolBarStyle>(toolBarStyleEnum.keyToValue(qUtf8Printable(styleSettings()->toolButtonStyleOtherToolbars()))));
0335 }
0336 
0337 bool KCMStyle::isDefaults() const
0338 {
0339     return m_gtkPage->isDefaults();
0340 }
0341 
0342 bool KCMStyle::isSaveNeeded() const
0343 {
0344     return m_gtkPage->isSaveNeeded();
0345 }
0346 
0347 #include "kcmstyle.moc"