File indexing completed on 2024-05-12 11:54:57

0001 /*
0002  * SPDX-FileCopyrightText: 2017 by Marco Martin <mart@kde.org>
0003  * SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "basictheme_p.h"
0009 
0010 #include "styleselector_p.h"
0011 #include <QFile>
0012 #include <QGuiApplication>
0013 
0014 #include "loggingcategory.h"
0015 
0016 namespace Kirigami
0017 {
0018 class CompatibilityThemeDefinition : public BasicThemeDefinition
0019 {
0020     Q_OBJECT
0021 public:
0022     CompatibilityThemeDefinition(QObject *component, QObject *parent = nullptr)
0023         : BasicThemeDefinition(parent)
0024     {
0025         m_object = component;
0026 
0027         connect(m_object, SIGNAL(textColorChanged()), this, SLOT(syncFromQml()));
0028         connect(m_object, SIGNAL(disabledTextColorChanged()), this, SLOT(syncFromQml()));
0029         connect(m_object, SIGNAL(highlightColorChanged()), this, SLOT(syncFromQml()));
0030         connect(m_object, SIGNAL(highlightedTextColorChanged()), this, SLOT(syncFromQml()));
0031         connect(m_object, SIGNAL(backgroundColorChanged()), this, SLOT(syncFromQml()));
0032         connect(m_object, SIGNAL(alternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
0033         connect(m_object, SIGNAL(linkColorChanged()), this, SLOT(syncFromQml()));
0034         connect(m_object, SIGNAL(visitedLinkColorChanged()), this, SLOT(syncFromQml()));
0035         connect(m_object, SIGNAL(buttonTextColorChanged()), this, SLOT(syncFromQml()));
0036         connect(m_object, SIGNAL(buttonBackgroundColorChanged()), this, SLOT(syncFromQml()));
0037         connect(m_object, SIGNAL(buttonAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
0038         connect(m_object, SIGNAL(buttonHoverColorChanged()), this, SLOT(syncFromQml()));
0039         connect(m_object, SIGNAL(buttonFocusColorChanged()), this, SLOT(syncFromQml()));
0040         connect(m_object, SIGNAL(viewTextColorChanged()), this, SLOT(syncFromQml()));
0041         connect(m_object, SIGNAL(viewBackgroundColorChanged()), this, SLOT(syncFromQml()));
0042         connect(m_object, SIGNAL(viewAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
0043         connect(m_object, SIGNAL(viewHoverColorChanged()), this, SLOT(syncFromQml()));
0044         connect(m_object, SIGNAL(viewFocusColorChanged()), this, SLOT(syncFromQml()));
0045         connect(m_object, SIGNAL(complementaryTextColorChanged()), this, SLOT(syncFromQml()));
0046         connect(m_object, SIGNAL(complementaryBackgroundColorChanged()), this, SLOT(syncFromQml()));
0047         connect(m_object, SIGNAL(complementaryAlternateBackgroundColorChanged()), this, SLOT(syncFromQml()));
0048         connect(m_object, SIGNAL(complementaryHoverColorChanged()), this, SLOT(syncFromQml()));
0049         connect(m_object, SIGNAL(complementaryFocusColorChanged()), this, SLOT(syncFromQml()));
0050     }
0051 
0052     void syncToQml(PlatformTheme *object) override
0053     {
0054         BasicThemeDefinition::syncToQml(object);
0055 
0056         QMetaObject::invokeMethod(m_object, "__propagateColorSet", Q_ARG(QVariant, QVariant::fromValue(object->parent())), Q_ARG(QVariant, object->colorSet()));
0057         QMetaObject::invokeMethod(m_object,
0058                                   "__propagateTextColor",
0059                                   Q_ARG(QVariant, QVariant::fromValue(object->parent())),
0060                                   Q_ARG(QVariant, object->textColor()));
0061         QMetaObject::invokeMethod(m_object,
0062                                   "__propagateBackgroundColor",
0063                                   Q_ARG(QVariant, QVariant::fromValue(object->parent())),
0064                                   Q_ARG(QVariant, object->backgroundColor()));
0065         QMetaObject::invokeMethod(m_object,
0066                                   "__propagatePrimaryColor",
0067                                   Q_ARG(QVariant, QVariant::fromValue(object->parent())),
0068                                   Q_ARG(QVariant, object->highlightColor()));
0069         QMetaObject::invokeMethod(m_object,
0070                                   "__propagateAccentColor",
0071                                   Q_ARG(QVariant, QVariant::fromValue(object->parent())),
0072                                   Q_ARG(QVariant, object->highlightColor()));
0073     }
0074 
0075     Q_SLOT void syncFromQml()
0076     {
0077         textColor = m_object->property("textColor").value<QColor>();
0078         disabledTextColor = m_object->property("disabledTextColor").value<QColor>();
0079         highlightColor = m_object->property("highlightColor").value<QColor>();
0080         highlightedTextColor = m_object->property("highlightedTextColor").value<QColor>();
0081         backgroundColor = m_object->property("backgroundColor").value<QColor>();
0082         alternateBackgroundColor = m_object->property("alternateBackgroundColor").value<QColor>();
0083         linkColor = m_object->property("linkColor").value<QColor>();
0084         visitedLinkColor = m_object->property("visitedLinkColor").value<QColor>();
0085         buttonTextColor = m_object->property("buttonTextColor").value<QColor>();
0086         buttonBackgroundColor = m_object->property("buttonBackgroundColor").value<QColor>();
0087         buttonAlternateBackgroundColor = m_object->property("buttonAlternateBackgroundColor").value<QColor>();
0088         buttonHoverColor = m_object->property("buttonHoverColor").value<QColor>();
0089         buttonFocusColor = m_object->property("buttonFocusColor").value<QColor>();
0090         viewTextColor = m_object->property("viewTextColor").value<QColor>();
0091         viewBackgroundColor = m_object->property("viewBackgroundColor").value<QColor>();
0092         viewAlternateBackgroundColor = m_object->property("viewAlternateBackgroundColor").value<QColor>();
0093         viewHoverColor = m_object->property("viewHoverColor").value<QColor>();
0094         viewFocusColor = m_object->property("viewFocusColor").value<QColor>();
0095         complementaryTextColor = m_object->property("complementaryTextColor").value<QColor>();
0096         complementaryBackgroundColor = m_object->property("complementaryBackgroundColor").value<QColor>();
0097         complementaryAlternateBackgroundColor = m_object->property("complementaryAlternateBackgroundColor").value<QColor>();
0098         complementaryHoverColor = m_object->property("complementaryHoverColor").value<QColor>();
0099         complementaryFocusColor = m_object->property("complementaryFocusColor").value<QColor>();
0100 
0101         Q_EMIT changed();
0102     }
0103 
0104 private:
0105     QObject *m_object;
0106 };
0107 
0108 BasicThemeDefinition::BasicThemeDefinition(QObject *parent)
0109     : QObject(parent)
0110 {
0111     defaultFont = qGuiApp->font();
0112 
0113     smallFont = qGuiApp->font();
0114     smallFont.setPointSize(smallFont.pointSize() - 2);
0115 }
0116 
0117 void BasicThemeDefinition::syncToQml(PlatformTheme *object)
0118 {
0119     auto item = qobject_cast<QQuickItem *>(object->parent());
0120     if (item && qmlAttachedPropertiesObject<PlatformTheme>(item, false) == object) {
0121         Q_EMIT sync(item);
0122     }
0123 }
0124 
0125 BasicThemeInstance::BasicThemeInstance(QObject *parent)
0126     : QObject(parent)
0127 {
0128 }
0129 
0130 BasicThemeDefinition &BasicThemeInstance::themeDefinition(QQmlEngine *engine)
0131 {
0132     if (m_themeDefinition) {
0133         return *m_themeDefinition;
0134     }
0135 
0136     auto componentUrl = StyleSelector::componentUrl(QStringLiteral("Theme.qml"));
0137     QString path{componentUrl.toLocalFile()};
0138     if (path.isEmpty() && componentUrl.scheme() == QLatin1String("qrc")) {
0139         path = QLatin1Char(':') + componentUrl.path();
0140     }
0141     QFile themeFile{path};
0142     if (themeFile.open(QIODevice::ReadOnly)) {
0143         auto data = themeFile.readAll();
0144 
0145         // Before Kirigami 5.80, custom Theme files would be registered as a
0146         // "Theme" singleton that would then be proxied by BasicTheme. This has
0147         // changed with the Theme singleton being an instance of BasicTheme.
0148         // However, this means that old theme files fail to load because
0149         // QQmlComponent complains about "pragma Singleton". To workaround this,
0150         // we remove the pragma here, as everything else should still work
0151         // correctly.
0152         // TODO KF6: Remove this and rely on all Theme files not being singletons.
0153         data.replace("\npragma Singleton\n", "");
0154 
0155         QQmlComponent component(engine);
0156         component.setData(data, componentUrl);
0157         auto result = component.create();
0158 
0159         if (!result) {
0160             const auto errors = component.errors();
0161             for (auto error : errors) {
0162                 qCWarning(KirigamiLog) << error.toString();
0163             }
0164 
0165             qCWarning(KirigamiLog) << "Invalid Theme file, using default Basic theme.";
0166             m_themeDefinition = std::make_unique<BasicThemeDefinition>();
0167         } else if (qobject_cast<BasicThemeDefinition *>(result)) {
0168             m_themeDefinition.reset(qobject_cast<BasicThemeDefinition *>(result));
0169         } else {
0170             qCWarning(KirigamiLog) << "Warning: Theme implementations should use Kirigami.BasicThemeDefinition for its root item";
0171             m_themeDefinition = std::make_unique<CompatibilityThemeDefinition>(result);
0172         }
0173     } else {
0174         qCDebug(KirigamiLog) << "No Theme file found, using default Basic theme";
0175         m_themeDefinition = std::make_unique<BasicThemeDefinition>();
0176     }
0177 
0178     connect(m_themeDefinition.get(), &BasicThemeDefinition::changed, this, &BasicThemeInstance::onDefinitionChanged);
0179 
0180     return *m_themeDefinition;
0181 }
0182 
0183 void BasicThemeInstance::onDefinitionChanged()
0184 {
0185     for (auto watcher : std::as_const(watchers)) {
0186         watcher->sync();
0187     }
0188 }
0189 
0190 Q_GLOBAL_STATIC(BasicThemeInstance, basicThemeInstance)
0191 
0192 BasicTheme::BasicTheme(QObject *parent)
0193     : PlatformTheme(parent)
0194 {
0195     basicThemeInstance()->watchers.append(this);
0196 
0197     sync();
0198 }
0199 
0200 BasicTheme::~BasicTheme()
0201 {
0202     basicThemeInstance()->watchers.removeOne(this);
0203 }
0204 
0205 void BasicTheme::sync()
0206 {
0207     auto &definition = basicThemeInstance()->themeDefinition(qmlEngine(parent()));
0208 
0209     switch (colorSet()) {
0210     case BasicTheme::Button:
0211         setTextColor(tint(definition.buttonTextColor));
0212         setBackgroundColor(tint(definition.buttonBackgroundColor));
0213         setAlternateBackgroundColor(tint(definition.buttonAlternateBackgroundColor));
0214         setHoverColor(tint(definition.buttonHoverColor));
0215         setFocusColor(tint(definition.buttonFocusColor));
0216         break;
0217     case BasicTheme::View:
0218         setTextColor(tint(definition.viewTextColor));
0219         setBackgroundColor(tint(definition.viewBackgroundColor));
0220         setAlternateBackgroundColor(tint(definition.viewAlternateBackgroundColor));
0221         setHoverColor(tint(definition.viewHoverColor));
0222         setFocusColor(tint(definition.viewFocusColor));
0223         break;
0224     case BasicTheme::Selection:
0225         setTextColor(tint(definition.selectionTextColor));
0226         setBackgroundColor(tint(definition.selectionBackgroundColor));
0227         setAlternateBackgroundColor(tint(definition.selectionAlternateBackgroundColor));
0228         setHoverColor(tint(definition.selectionHoverColor));
0229         setFocusColor(tint(definition.selectionFocusColor));
0230         break;
0231     case BasicTheme::Tooltip:
0232         setTextColor(tint(definition.tooltipTextColor));
0233         setBackgroundColor(tint(definition.tooltipBackgroundColor));
0234         setAlternateBackgroundColor(tint(definition.tooltipAlternateBackgroundColor));
0235         setHoverColor(tint(definition.tooltipHoverColor));
0236         setFocusColor(tint(definition.tooltipFocusColor));
0237         break;
0238     case BasicTheme::Complementary:
0239         setTextColor(tint(definition.complementaryTextColor));
0240         setBackgroundColor(tint(definition.complementaryBackgroundColor));
0241         setAlternateBackgroundColor(tint(definition.complementaryAlternateBackgroundColor));
0242         setHoverColor(tint(definition.complementaryHoverColor));
0243         setFocusColor(tint(definition.complementaryFocusColor));
0244         break;
0245     case BasicTheme::Window:
0246     default:
0247         setTextColor(tint(definition.textColor));
0248         setBackgroundColor(tint(definition.backgroundColor));
0249         setAlternateBackgroundColor(tint(definition.alternateBackgroundColor));
0250         setHoverColor(tint(definition.hoverColor));
0251         setFocusColor(tint(definition.focusColor));
0252         break;
0253     }
0254 
0255     setDisabledTextColor(tint(definition.disabledTextColor));
0256     setHighlightColor(tint(definition.highlightColor));
0257     setHighlightedTextColor(tint(definition.highlightedTextColor));
0258     setActiveTextColor(tint(definition.activeTextColor));
0259     setActiveBackgroundColor(tint(definition.activeBackgroundColor));
0260     setLinkColor(tint(definition.linkColor));
0261     setLinkBackgroundColor(tint(definition.linkBackgroundColor));
0262     setVisitedLinkColor(tint(definition.visitedLinkColor));
0263     setVisitedLinkBackgroundColor(tint(definition.visitedLinkBackgroundColor));
0264     setNegativeTextColor(tint(definition.negativeTextColor));
0265     setNegativeBackgroundColor(tint(definition.negativeBackgroundColor));
0266     setNeutralTextColor(tint(definition.neutralTextColor));
0267     setNeutralBackgroundColor(tint(definition.neutralBackgroundColor));
0268     setPositiveTextColor(tint(definition.positiveTextColor));
0269     setPositiveBackgroundColor(tint(definition.positiveBackgroundColor));
0270 
0271     setDefaultFont(definition.defaultFont);
0272     setSmallFont(definition.smallFont);
0273 }
0274 
0275 bool BasicTheme::event(QEvent *event)
0276 {
0277     if (event->type() == PlatformThemeEvents::DataChangedEvent::type) {
0278         sync();
0279     }
0280 
0281     if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) {
0282         sync();
0283     }
0284 
0285     if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) {
0286         sync();
0287     }
0288 
0289     if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) {
0290         basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this);
0291     }
0292 
0293     if (event->type() == PlatformThemeEvents::FontChangedEvent::type) {
0294         basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this);
0295     }
0296 
0297     return PlatformTheme::event(event);
0298 }
0299 
0300 QColor BasicTheme::tint(const QColor &color)
0301 {
0302     switch (colorGroup()) {
0303     case PlatformTheme::Inactive:
0304         return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF());
0305     case PlatformTheme::Disabled:
0306         return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF() * 0.8);
0307     default:
0308         return color;
0309     }
0310 }
0311 
0312 }
0313 
0314 #include "basictheme.moc"
0315 #include "moc_basictheme_p.cpp"