30 #include <QRegularExpression>
32 #include <QStandardPaths>
33 #include <QStringBuilder>
35 #include <QSvgRenderer>
67 const QLatin1String ThemeSubFolder{
"themes/"};
68 const QLatin1String BuiltinThemeDefaultPath{
":themes/default/"};
69 const QLatin1String BuiltinThemeDarkPath{
":themes/dark/"};
76 font.setPixelSize(pixelSize);
77 font.setWeight(weight);
83 return QString(
"%1 %2px \"%3\"").arg(font.weight() * 8).arg(font.pixelSize()).arg(font.family());
86 static QMap<Style::ColorPalette, QColor> palette;
88 static QMap<QString, QString> dictColor;
89 static QMap<QString, QString> dictFont;
90 static QMap<QString, QString> dictTheme;
97 {
Style::Light, QObject::tr(
"Violet"), QColor(
"#4617b5")},
99 {
Style::Dark, QObject::tr(
"Dark blue"), QColor(
"#00336d")},
100 {
Style::Dark, QObject::tr(
"Dark olive"), QColor(
"#4d5f00")},
101 {
Style::Dark, QObject::tr(
"Dark red"), QColor(
"#7a210d")},
102 {
Style::Dark, QObject::tr(
"Dark violet"), QColor(
"#280d6c")}
109 for (
auto t : themeNameColors) {
119 const QString themeName =
"default";
120 return QStringLiteral(
"default");
126 const QString themeFolder = ThemeSubFolder % themeName;
127 const QString fullPath = QStandardPaths::locate(QStandardPaths::AppDataLocation,
128 themeFolder, QStandardPaths::LocateDirectory);
131 if(fullPath.isEmpty()) {
135 return fullPath % QDir::separator();
139 static const QMap<Style::ColorPalette, QString> aliasColors = {
163 static std::map<std::pair<const QString, const QFont>,
const QString> stylesheetsCache;
168 const std::pair<const QString, const QFont> cacheKey(fullPath, baseFont);
169 auto it = stylesheetsCache.find(cacheKey);
170 if (it != stylesheetsCache.end())
176 const QString newStylesheet =
resolve(filename, baseFont);
177 stylesheetsCache.insert(std::make_pair(cacheKey, newStylesheet));
178 return newStylesheet;
181 static QStringList existingImagesCache;
187 if (existingImagesCache.contains(fullPath)) {
192 if (QFileInfo::exists(fullPath)) {
193 existingImagesCache << fullPath;
196 qWarning() <<
"Failed to open file (using defaults):" << fullPath;
200 if (QFileInfo::exists(fullPath)) {
203 qWarning() <<
"Failed to open default file:" << fullPath;
211 return palette[entry];
219 static int defSize = QFontInfo(QFont()).pixelSize();
221 static QFont fonts[] = {
222 appFont(defSize + 3, QFont::Bold),
223 appFont(defSize + 1, QFont::Normal),
224 appFont(defSize + 1, QFont::Bold),
225 appFont(defSize, QFont::Normal),
227 appFont(defSize - 1, QFont::Normal),
228 appFont(defSize - 1, QFont::Light),
237 QString fullPath = themePath + filename;
240 QFile
file{fullPath};
241 if (
file.open(QFile::ReadOnly | QFile::Text)) {
242 qss =
file.readAll();
244 qWarning() <<
"Failed to open file (using defaults):" << fullPath;
247 QFile
file{fullPath};
249 if (
file.open(QFile::ReadOnly | QFile::Text)) {
250 qss =
file.readAll();
252 qWarning() <<
"Failed to open default file:" << fullPath;
257 if (palette.isEmpty()) {
261 if (dictColor.isEmpty()) {
265 if (dictFont.isEmpty()) {
268 QString::fromUtf8(
"'%1' %2px").arg(baseFont.family()).arg(QFontInfo(baseFont).pixelSize())},
278 for (
const QString& key : dictColor.keys()) {
279 qss.replace(QRegularExpression(key % QLatin1String{
"\\b"}), dictColor[key]);
282 for (
const QString& key : dictFont.keys()) {
283 qss.replace(QRegularExpression(key % QLatin1String{
"\\b"}), dictFont[key]);
286 for (
const QString& key : dictTheme.keys()) {
287 qss.replace(QRegularExpression(key % QLatin1String{
"\\b"}), dictTheme[key]);
291 const QRegularExpression re{QStringLiteral(R
"(@getImagePath\([^)\s]*\))")};
292 QRegularExpressionMatchIterator i = re.globalMatch(qss);
294 while (i.hasNext()) {
295 QRegularExpressionMatch match = i.next();
296 QString path = match.captured(0);
297 const QString phrase = path;
299 path.remove(QStringLiteral(
"@getImagePath("));
304 if (!existingImagesCache.contains(fullPath)) {
305 if (QFileInfo::exists(fullImagePath)) {
306 existingImagesCache << fullImagePath;
308 qWarning() <<
"Failed to open file (using defaults):" << fullImagePath;
313 qss.replace(phrase, fullImagePath);
321 w->style()->unpolish(w);
322 w->style()->polish(w);
324 for (QObject* o : w->children()) {
325 QWidget* c = qobject_cast<QWidget*>(o);
327 c->style()->unpolish(c);
328 c->style()->polish(c);
335 stylesheetsCache.clear();
340 if (color < 0 || color >= themeNameColors.size())
354 if (!color.isValid()) {
383 QSvgRenderer render(path);
384 QPixmap pixmap(width, height);
385 pixmap.fill(QColor(0, 0, 0, 0));
386 QPainter painter(&pixmap);
387 render.render(&painter, pixmap.rect());
393 QSettings settings(
getThemePath() %
"palette.ini", QSettings::IniFormat);
395 auto keys = aliasColors.keys();
397 settings.beginGroup(
"colors");
398 QMap<Style::ColorPalette, QString> c;
399 for (
auto k : keys) {
400 c[k] = settings.value(aliasColors[k],
"#000").toString();
401 palette[k] = QColor(settings.value(aliasColors[k],
"#000").toString());
431 if (themeNameColors[num].type ==
Dark) {
432 return BuiltinThemeDarkPath;
435 return BuiltinThemeDefaultPath;