File indexing completed on 2024-05-12 15:59:15

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