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 }