File indexing completed on 2024-05-05 17:04:27
0001 /* 0002 * This file is part of the KDE project 0003 * Copyright (C) 2014 Arjen Hiemstra <ahiemstra@heimr.nl> 0004 * 0005 * This program is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation; either version 2 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program; if not, write to the Free Software 0017 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "Theme.h" 0021 0022 #include <QStringList> 0023 #include <QUrl> 0024 #include <QDebug> 0025 #include <QFile> 0026 #include <QDir> 0027 #include <QColor> 0028 #include <QFont> 0029 #include <QFontDatabase> 0030 #include <QApplication> 0031 #include <QWidget> 0032 #include <QQmlComponent> 0033 #include <QStandardPaths> 0034 0035 #include <KIconLoader> 0036 0037 #include "QmlGlobalEngine.h" 0038 0039 #ifdef Q_OS_WIN 0040 #include <windows.h> 0041 #endif 0042 0043 class Theme::Private 0044 { 0045 public: 0046 Private() 0047 : inheritedTheme(0) 0048 , iconPath("icons/") 0049 , imagePath("images/") 0050 , fontPath("fonts/") 0051 , fontsAdded(false) 0052 , lineCountLandscape(40) 0053 , lineCountPortrait(70) 0054 { } 0055 0056 void rebuildFontCache(); 0057 0058 QString id; 0059 QString name; 0060 QString inherits; 0061 Theme* inheritedTheme; 0062 0063 QVariantMap colors; 0064 QVariantMap sizes; 0065 QVariantMap fonts; 0066 0067 QString basePath; 0068 QString iconPath; 0069 QString imagePath; 0070 QString fontPath; 0071 0072 QHash<QString, QColor> colorCache; 0073 QHash<QString, QFont> fontMap; 0074 0075 bool fontsAdded; 0076 QList<int> addedFonts; 0077 int lineCountLandscape; 0078 int lineCountPortrait; 0079 }; 0080 0081 Theme::Theme(QObject* parent) 0082 : QObject(parent), d(new Private) 0083 { 0084 qApp->installEventFilter(this); 0085 } 0086 0087 Theme::~Theme() 0088 { 0089 QFontDatabase db; 0090 Q_FOREACH(int id, d->addedFonts) { 0091 db.removeApplicationFont(id); 0092 } 0093 0094 delete d; 0095 } 0096 0097 QString Theme::id() const 0098 { 0099 return d->id; 0100 } 0101 0102 void Theme::setId(const QString& newValue) 0103 { 0104 if(newValue != d->id) { 0105 d->id = newValue; 0106 const QString qmlFileSubPath = QStringLiteral("calligragemini/themes/") + d->id + QStringLiteral("/theme.qml"); 0107 const QString qmlFileFullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, qmlFileSubPath); 0108 d->basePath = QFileInfo(qmlFileFullPath).dir().absolutePath(); 0109 emit idChanged(); 0110 } 0111 } 0112 0113 QString Theme::name() const 0114 { 0115 return d->name; 0116 } 0117 0118 void Theme::setName(const QString& newValue) 0119 { 0120 if(newValue != d->name) { 0121 d->name = newValue; 0122 emit nameChanged(); 0123 } 0124 } 0125 0126 QString Theme::inherits() const 0127 { 0128 return d->inherits; 0129 } 0130 0131 void Theme::setInherits(const QString& newValue) 0132 { 0133 if(newValue != d->inherits) { 0134 if(d->inheritedTheme) { 0135 delete d->inheritedTheme; 0136 d->inheritedTheme = 0; 0137 } 0138 d->inherits = newValue; 0139 0140 if(!d->inherits.isEmpty()) { 0141 d->inheritedTheme = Theme::load(d->inherits, this); 0142 connect(d->inheritedTheme, SIGNAL(fontCacheRebuilt()), SIGNAL(fontCacheRebuilt())); 0143 } 0144 0145 emit inheritsChanged(); 0146 } 0147 } 0148 0149 QVariantMap Theme::colors() const 0150 { 0151 return d->colors; 0152 } 0153 0154 void Theme::setColors(const QVariantMap& newValue) 0155 { 0156 if(newValue != d->colors) { 0157 d->colors = newValue; 0158 emit colorsChanged(); 0159 } 0160 } 0161 0162 QColor Theme::color(const QString& name) 0163 { 0164 if(d->colorCache.contains(name)) 0165 return d->colorCache.value(name); 0166 0167 QStringList parts = name.split('/'); 0168 QColor result; 0169 0170 if(!parts.isEmpty()) 0171 { 0172 QVariantMap map = d->colors; 0173 QString current = parts.takeFirst(); 0174 0175 while(map.contains(current)) 0176 { 0177 QVariant value = map.value(current); 0178 if(value.type() == QVariant::Map) 0179 { 0180 if(parts.isEmpty()) 0181 break; 0182 0183 map = value.toMap(); 0184 current = parts.takeFirst(); 0185 } 0186 else 0187 { 0188 result = value.value<QColor>(); 0189 map = QVariantMap(); 0190 } 0191 } 0192 } 0193 0194 if(!result.isValid() && d->inheritedTheme) { 0195 result = d->inheritedTheme->color(name); 0196 } 0197 0198 if(!result.isValid()) { 0199 qWarning() << "Unable to find color" << name; 0200 } else { 0201 d->colorCache.insert(name, result); 0202 } 0203 0204 return result; 0205 } 0206 0207 QVariantMap Theme::sizes() const 0208 { 0209 return d->sizes; 0210 } 0211 0212 void Theme::setSizes(const QVariantMap& newValue) 0213 { 0214 if(newValue != d->sizes) { 0215 d->sizes = newValue; 0216 emit sizesChanged(); 0217 } 0218 } 0219 0220 float Theme::size(const QString& name) 0221 { 0222 Q_UNUSED(name); 0223 return 0.f; 0224 } 0225 0226 QVariantMap Theme::fonts() const 0227 { 0228 return d->fonts; 0229 } 0230 0231 void Theme::setFonts(const QVariantMap& newValue) 0232 { 0233 if(newValue != d->fonts) 0234 { 0235 d->fonts = newValue; 0236 0237 d->fontMap.clear(); 0238 0239 emit fontsChanged(); 0240 } 0241 } 0242 0243 QFont Theme::font(const QString& name) 0244 { 0245 if(!d->fontsAdded) { 0246 QDir fontDir(d->basePath + '/' + d->fontPath); 0247 QStringList entries = fontDir.entryList(QDir::Files); 0248 QFontDatabase db; 0249 Q_FOREACH(const QString &entry, entries) { 0250 d->addedFonts.append(db.addApplicationFont(fontDir.absoluteFilePath(entry))); 0251 } 0252 d->fontsAdded = true; 0253 } 0254 0255 if(d->fontMap.isEmpty()) { 0256 d->rebuildFontCache(); 0257 } 0258 0259 if(d->fontMap.contains(name)) 0260 return d->fontMap.value(name); 0261 0262 if(d->inheritedTheme) 0263 return d->inheritedTheme->font(name); 0264 0265 qWarning() << "Unable to find font" << name; 0266 return QFont(); 0267 } 0268 0269 QString Theme::fontPath() const 0270 { 0271 return d->fontPath; 0272 } 0273 0274 void Theme::setFontPath(const QString& newValue) 0275 { 0276 if(newValue != d->fontPath) { 0277 if(!d->addedFonts.isEmpty()) { 0278 QFontDatabase db; 0279 Q_FOREACH(int id, d->addedFonts) { 0280 db.removeApplicationFont(id); 0281 } 0282 d->addedFonts.clear(); 0283 } 0284 0285 d->fontPath = newValue; 0286 d->fontsAdded = false; 0287 0288 emit fontPathChanged(); 0289 } 0290 } 0291 0292 0293 QString Theme::iconPath() const 0294 { 0295 return d->iconPath; 0296 } 0297 0298 void Theme::setIconPath(const QString& newValue) 0299 { 0300 if(newValue != d->iconPath) { 0301 d->iconPath = newValue; 0302 emit iconPathChanged(); 0303 } 0304 } 0305 0306 QUrl Theme::icon(const QString& name, bool useSystemFallback) 0307 { 0308 QString url = QString("%1/%2/%3.svg").arg(d->basePath, d->iconPath, name); 0309 if(!QFile::exists(url)) { 0310 if(d->inheritedTheme) { 0311 return d->inheritedTheme->icon(name); 0312 } else { 0313 if(useSystemFallback) { 0314 url = KIconLoader::global()->iconPath(name, -128); 0315 qWarning() << "Attempting to use a system fallback icon" << url; 0316 } else { 0317 qWarning() << "Unable to find icon" << url; 0318 } 0319 } 0320 } 0321 0322 return QUrl::fromLocalFile(url); 0323 } 0324 0325 QIcon Theme::iconActual(const QString& name) 0326 { 0327 return QIcon(icon(name).toLocalFile()); 0328 } 0329 0330 QString Theme::imagePath() const 0331 { 0332 return d->imagePath; 0333 } 0334 0335 void Theme::setImagePath(const QString& newValue) 0336 { 0337 if(newValue != d->imagePath) { 0338 d->imagePath = newValue; 0339 emit imagePathChanged(); 0340 } 0341 } 0342 0343 QUrl Theme::image(const QString& name) 0344 { 0345 QString url = QString("%1/%2/%3").arg(d->basePath, d->imagePath, name); 0346 if(!QFile::exists(url)) { 0347 if(d->inheritedTheme) { 0348 return d->inheritedTheme->image(name); 0349 } else { 0350 qWarning() << "Unable to find image" << url; 0351 } 0352 } 0353 0354 return QUrl::fromLocalFile(url); 0355 } 0356 0357 Theme* Theme::load(const QString& id, QObject* parent) 0358 { 0359 QString qml; 0360 0361 //Ugly hacky stuff for making things work on Windows 0362 #ifdef Q_OS_WIN 0363 QDir appdir(qApp->applicationDirPath()); 0364 0365 // Corrects for mismatched case errors in path (qtdeclarative fails to load) 0366 wchar_t buffer[1024]; 0367 QString absolute = appdir.absolutePath(); 0368 DWORD rv = ::GetShortPathName((wchar_t*)absolute.utf16(), buffer, 1024); 0369 rv = ::GetLongPathName(buffer, buffer, 1024); 0370 QString correctedPath((QChar *)buffer); 0371 appdir.setPath(correctedPath); 0372 0373 // for now, the app in bin/ and we still use the env.bat script 0374 appdir.cdUp(); 0375 qml = QString("%1/bin/data/calligragemini/themes/%2/theme.qml").arg(appdir.canonicalPath(), id); 0376 #else 0377 const QString qmlFileSubPath = QStringLiteral("calligragemini/themes/") + id + QStringLiteral("/theme.qml"); 0378 qml = QStandardPaths::locate(QStandardPaths::GenericDataLocation, qmlFileSubPath); 0379 #endif 0380 0381 QQmlComponent themeComponent(QmlGlobalEngine::instance()->engine(), parent); 0382 themeComponent.loadUrl(QUrl::fromLocalFile(qml)); 0383 0384 if(themeComponent.isError()) { 0385 qWarning() << themeComponent.errorString(); 0386 return 0; 0387 } 0388 0389 Theme* theme = qobject_cast<Theme*>(themeComponent.create()); 0390 if(!theme) { 0391 qWarning() << "Failed to create theme instance!"; 0392 return 0; 0393 } 0394 0395 return theme; 0396 } 0397 0398 bool Theme::eventFilter(QObject* target, QEvent* event) 0399 { 0400 if(target == qApp->activeWindow() && target->inherits("QMainWindow") && event->type() == QEvent::Resize) { 0401 d->rebuildFontCache(); 0402 emit fontCacheRebuilt(); 0403 } 0404 0405 return QObject::eventFilter(target, event); 0406 } 0407 0408 int Theme::adjustedPixel(const int& pixel) const 0409 { 0410 if(!qApp->activeWindow()) 0411 return 0; 0412 0413 // If we are in portrait mode, we still assume 1080p for font size purposes 0414 int width = qApp->activeWindow()->height() > qApp->activeWindow()->width() ? qApp->activeWindow()->height() : qApp->activeWindow()->width(); 0415 // The pixel size is based on a 1080p screen, and it is accepted that the window size 0416 // will vary slightly on there, depending on whether or not we are full screened (so 0417 // we accept up to 10 pixels less width) 0418 float sizeAdjustment = 1; 0419 if(width > 1920 || width < 1910) 0420 sizeAdjustment = width / 1920.f; 0421 return pixel * sizeAdjustment; 0422 } 0423 0424 void Theme::Private::rebuildFontCache() 0425 { 0426 fontMap.clear(); 0427 QFontDatabase db; 0428 for(QVariantMap::ConstIterator itr = fonts.constBegin(); itr != fonts.constEnd(); ++itr) 0429 { 0430 QVariantMap map = itr->toMap(); 0431 if(map.isEmpty()) 0432 continue; 0433 0434 QFont font = db.font(map.value("family").toString(), map.value("style", "Regular").toString(), 10); 0435 0436 if(font.isCopyOf(qApp->font())) 0437 qWarning() << "Could not find font" << map.value("family") << "with style" << map.value("style", "Regular"); 0438 0439 if(map.contains("pixelSize")) { 0440 // If we are in portrait mode, we still assume 1080p for font size purposes 0441 int width = qApp->activeWindow()->height() > qApp->activeWindow()->width() ? qApp->activeWindow()->height() : qApp->activeWindow()->width(); 0442 // The pixel size is based on a 1080p screen, and it is accepted that the window size 0443 // will vary slightly on there, depending on whether or not we are full screened (so 0444 // we accept up to 10 pixels less width) 0445 float sizeAdjustment = 1; 0446 if(width > 1920 || width < 1910) 0447 sizeAdjustment = width / 1920.f; 0448 font.setPixelSize(map.value("pixelSize").toInt() * sizeAdjustment); 0449 } 0450 else { 0451 float lineCount = qApp->activeWindow()->height() > qApp->activeWindow()->width() ? lineCountPortrait : lineCountLandscape; 0452 float lineHeight = qApp->activeWindow()->height() / lineCount; 0453 font.setPixelSize(lineHeight * map.value("size", 1).toFloat()); 0454 } 0455 0456 fontMap.insert(itr.key(), font); 0457 } 0458 }