File indexing completed on 2024-05-19 04:27:04

0001 /*
0002  * This file is part of the KDE project
0003  * SPDX-FileCopyrightText: 2014 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "Theme.h"
0009 
0010 #include <QStringList>
0011 #include <QUrl>
0012 #include <kis_debug.h>
0013 #include <QFile>
0014 #include <QDir>
0015 #include <QColor>
0016 #include <QFont>
0017 #include <QFontDatabase>
0018 #include <QApplication>
0019 #include <QWidget>
0020 #include <QQmlComponent>
0021 #include <QStandardPaths>
0022 #include <KoResourcePaths.h>
0023 
0024 #include "QmlGlobalEngine.h"
0025 
0026 class Q_DECL_HIDDEN Theme::Private
0027 {
0028 public:
0029     Private()
0030         : iconPath("icons/")
0031         , imagePath("images/")
0032         , fontPath("fonts/")
0033         , fontsAdded(false)
0034         , lineCountLandscape(40)
0035         , lineCountPortrait(70)
0036     { }
0037 
0038     void loadFonts();
0039     void rebuildFontCache();
0040 
0041     QString id;
0042     QString name;
0043 
0044     QVariantMap colors;
0045     QVariantMap sizes;
0046     QVariantMap fonts;
0047 
0048     QString basePath;
0049     QString iconPath;
0050     QString imagePath;
0051     QString fontPath;
0052 
0053     QHash<QString, QColor> colorCache;
0054     QHash<QString, QFont> fontMap;
0055 
0056     bool fontsAdded;
0057     QList<int> addedFonts;
0058     int lineCountLandscape;
0059     int lineCountPortrait;
0060 };
0061 
0062 Theme::Theme(QObject* parent)
0063     : QObject(parent), d(new Private)
0064 {
0065     qApp->installEventFilter(this);
0066 }
0067 
0068 Theme::~Theme()
0069 {
0070     QFontDatabase db;
0071     Q_FOREACH(int id, d->addedFonts) {
0072         db.removeApplicationFont(id);
0073     }
0074 
0075     delete d;
0076 }
0077 
0078 QString Theme::id() const
0079 {
0080     return d->id;
0081 }
0082 
0083 void Theme::setId(const QString& newValue)
0084 {
0085     if (newValue != d->id) {
0086         d->id = newValue;
0087         const QString themeQmlPath = themePath(d->id);
0088         d->basePath = QFileInfo(themeQmlPath).dir().absolutePath();
0089         emit idChanged();
0090     }
0091 }
0092 
0093 QString Theme::name() const
0094 {
0095     return d->name;
0096 }
0097 
0098 void Theme::setName(const QString& newValue)
0099 {
0100     if(newValue != d->name) {
0101         d->name = newValue;
0102         emit nameChanged();
0103     }
0104 }
0105 
0106 QVariantMap Theme::colors() const
0107 {
0108     return d->colors;
0109 }
0110 
0111 void Theme::setColors(const QVariantMap& newValue)
0112 {
0113     if(newValue != d->colors) {
0114         d->colors = newValue;
0115         emit colorsChanged();
0116     }
0117 }
0118 
0119 QColor Theme::color(const QString& name)
0120 {
0121     if(d->colorCache.contains(name))
0122         return d->colorCache.value(name);
0123 
0124     QStringList parts = name.split('/');
0125     QColor result;
0126 
0127     if(!parts.isEmpty())
0128     {
0129         QVariantMap map = d->colors;
0130         QString current = parts.takeFirst();
0131 
0132         while(map.contains(current))
0133         {
0134             QVariant value = map.value(current);
0135             if(value.type() == QVariant::Map)
0136             {
0137                 if(parts.isEmpty())
0138                     break;
0139 
0140                 map = value.toMap();
0141                 current = parts.takeFirst();
0142             }
0143             else
0144             {
0145                 result = value.value<QColor>();
0146                 map = QVariantMap();
0147             }
0148         }
0149     }
0150 
0151     if(!result.isValid()) {
0152         warnKrita << "Unable to find color" << name;
0153     } else {
0154         d->colorCache.insert(name, result);
0155     }
0156 
0157     return result;
0158 }
0159 
0160 QVariantMap Theme::sizes() const
0161 {
0162     return d->sizes;
0163 }
0164 
0165 void Theme::setSizes(const QVariantMap& newValue)
0166 {
0167     if(newValue != d->sizes) {
0168         d->sizes = newValue;
0169         emit sizesChanged();
0170     }
0171 }
0172 
0173 float Theme::size(const QString& name)
0174 {
0175     Q_UNUSED(name);
0176     return 0.f;
0177 }
0178 
0179 QVariantMap Theme::fonts() const
0180 {
0181     return d->fonts;
0182 }
0183 
0184 void Theme::setFonts(const QVariantMap& newValue)
0185 {
0186     if(newValue != d->fonts)
0187     {
0188         d->fonts = newValue;
0189 
0190         d->fontMap.clear();
0191 
0192         emit fontsChanged();
0193     }
0194 }
0195 
0196 void Theme::Private::loadFonts()
0197 {
0198     if (!fontsAdded) {
0199         QDir fontDir(basePath + '/' + fontPath);
0200         QStringList entries = fontDir.entryList(QDir::Files);
0201         QFontDatabase db;
0202         Q_FOREACH(QString entry, entries) {
0203             addedFonts.append(db.addApplicationFont(fontDir.absoluteFilePath(entry)));
0204         }
0205         fontsAdded = true;
0206     }
0207 }
0208 
0209 QFont Theme::font(const QString& name)
0210 {
0211     d->loadFonts();
0212 
0213     if (d->fontMap.isEmpty()) {
0214         d->rebuildFontCache();
0215     }
0216 
0217     if (d->fontMap.contains(name))
0218         return d->fontMap.value(name);
0219 
0220     warnKrita << "Unable to find font" << name;
0221     return QFont();
0222 }
0223 
0224 QString Theme::fontPath() const
0225 {
0226     return d->fontPath;
0227 }
0228 
0229 void Theme::setFontPath(const QString& newValue)
0230 {
0231     if(newValue != d->fontPath) {
0232         if(!d->addedFonts.isEmpty()) {
0233             QFontDatabase db;
0234             Q_FOREACH(int id, d->addedFonts) {
0235                 db.removeApplicationFont(id);
0236             }
0237             d->addedFonts.clear();
0238         }
0239 
0240         d->fontPath = newValue;
0241         d->fontsAdded = false;
0242 
0243         emit fontPathChanged();
0244     }
0245 }
0246 
0247 
0248 QString Theme::iconPath() const
0249 {
0250     return d->iconPath;
0251 }
0252 
0253 void Theme::setIconPath(const QString& newValue)
0254 {
0255     if(newValue != d->iconPath) {
0256         d->iconPath = newValue;
0257         emit iconPathChanged();
0258     }
0259 }
0260 
0261 QUrl Theme::icon(const QString& name)
0262 {
0263     QString url = QString("%1/%2/%3.svg").arg(d->basePath, d->iconPath, name);
0264     if(!QFile::exists(url)) {
0265         warnKrita << "Unable to find icon" << url;
0266     }
0267 
0268     return QUrl::fromLocalFile(url);
0269 }
0270 
0271 QString Theme::imagePath() const
0272 {
0273     return d->imagePath;
0274 }
0275 
0276 void Theme::setImagePath(const QString& newValue)
0277 {
0278     if(newValue != d->imagePath) {
0279         d->imagePath = newValue;
0280         emit imagePathChanged();
0281     }
0282 }
0283 
0284 QUrl Theme::image(const QString& name)
0285 {
0286     QString url = QString("%1/%2/%3").arg(d->basePath, d->imagePath, name);
0287     if(!QFile::exists(url)) {
0288         warnKrita << "Unable to find image" << url;
0289     }
0290 
0291     return QUrl::fromLocalFile(url);
0292 }
0293 
0294 Theme* Theme::load(const QString& id, QQmlEngine *engine)
0295 {
0296     
0297     QString qml = themePath(id);
0298     
0299     QQmlComponent themeComponent(engine, 0);
0300     themeComponent.loadUrl(QUrl::fromLocalFile(qml), QQmlComponent::PreferSynchronous);
0301 
0302     if (themeComponent.isError()) {
0303         warnKrita << themeComponent.errorString();
0304         return 0;
0305     }
0306 
0307     Theme* theme = qobject_cast<Theme*>(themeComponent.create());
0308     if(!theme) {
0309         warnKrita << "Failed to create theme instance!";
0310         return 0;
0311     }
0312 
0313     return theme;
0314 }
0315 
0316 bool Theme::eventFilter(QObject* target, QEvent* event)
0317 {
0318     if(target == qApp->activeWindow() && target->inherits("QMainWindow") && event->type() == QEvent::Resize) {
0319         d->rebuildFontCache();
0320         emit fontCacheRebuilt();
0321     }
0322 
0323     return QObject::eventFilter(target, event);
0324 }
0325 
0326 void Theme::Private::rebuildFontCache()
0327 {
0328     loadFonts();
0329     fontMap.clear();
0330     QFontDatabase db;
0331     for(QVariantMap::iterator itr = fonts.begin(); itr != fonts.end(); ++itr)
0332     {
0333         QVariantMap map = itr->toMap();
0334         if(map.isEmpty())
0335             continue;
0336 
0337         QFont font = db.font(map.value("family").toString(), map.value("style", "Regular").toString(), 10);
0338 
0339         if(font.isCopyOf(qApp->font()))
0340             warnKrita << "Could not find font" << map.value("family") << "with style" << map.value("style", "Regular");
0341 
0342         if (qApp->activeWindow()) {
0343             float lineCount = qApp->activeWindow()->height() > qApp->activeWindow()->width() ? lineCountPortrait : lineCountLandscape;
0344             float lineHeight = qApp->activeWindow()->height() / lineCount;
0345             font.setPixelSize(lineHeight * map.value("size", 1).toFloat());
0346         }
0347 
0348         fontMap.insert(itr.key(), font);
0349     }
0350 }
0351 
0352 QString Theme::themePath(const QString &id)
0353 {
0354     QString qml = QStandardPaths::locate(QStandardPaths::AppDataLocation,
0355                                          QString("krita/qmlthemes/%1/theme.qml").arg(id));
0356     if (qml.isEmpty()) {
0357         qml = QStandardPaths::locate(QStandardPaths::AppDataLocation, QString("qmlthemes/%1/theme.qml").arg(id));
0358     }
0359     if (qml.isEmpty()) {
0360             qml = KoResourcePaths::getApplicationRoot() + QString("/share/krita/qmlthemes/%1/theme.qml").arg(id);
0361     }
0362     return qml;
0363 }