|
3 | 3 | #include <QDir> |
4 | 4 | #include <QDirIterator> |
5 | 5 | #include <QProxyStyle> |
| 6 | +#include <QTextStream> |
6 | 7 |
|
7 | | -#include <log.h> |
8 | | -#include <utility.h> |
| 8 | +#include <uibase/log.h> |
| 9 | +#include <uibase/utility.h> |
9 | 10 |
|
10 | 11 | #include "shared/appconfig.h" |
11 | 12 |
|
@@ -60,15 +61,95 @@ class ProxyStyle : public QProxyStyle |
60 | 61 | } |
61 | 62 | }; |
62 | 63 |
|
| 64 | +namespace |
| 65 | +{ |
| 66 | +QString readWholeFile(std::filesystem::path const& path) |
| 67 | +{ |
| 68 | + QFile file(path); |
| 69 | + if (!file.open(QFile::ReadOnly | QFile::Text)) { |
| 70 | + return {}; |
| 71 | + } |
| 72 | + |
| 73 | + return QTextStream(&file).readAll(); |
| 74 | +} |
| 75 | + |
| 76 | +QStringList extractTopStyleSheetComments(QString const& stylesheet) |
| 77 | +{ |
| 78 | + QTextStream stream(stylesheet.toUtf8()); |
| 79 | + QStringList topComments; |
| 80 | + |
| 81 | + while (true) { |
| 82 | + const auto byteLine = stream.readLine(); |
| 83 | + if (byteLine.isNull()) { |
| 84 | + break; |
| 85 | + } |
| 86 | + |
| 87 | + const auto line = QString(byteLine).trimmed(); |
| 88 | + |
| 89 | + // skip empty lines |
| 90 | + if (line.isEmpty()) { |
| 91 | + continue; |
| 92 | + } |
| 93 | + |
| 94 | + // only handle single line comments |
| 95 | + if (!line.startsWith("/*")) { |
| 96 | + break; |
| 97 | + } |
| 98 | + |
| 99 | + topComments.push_back(line.mid(2, line.size() - 4).trimmed()); |
| 100 | + } |
| 101 | + |
| 102 | + return topComments; |
| 103 | +} |
| 104 | + |
| 105 | +QString extractBaseStyleFromStyleSheet(std::filesystem::path const& path, |
| 106 | + QString const& stylesheet, |
| 107 | + const QString& defaultStyle) |
| 108 | +{ |
| 109 | + // read the first line of the files that are either empty or comments |
| 110 | + // |
| 111 | + const auto topLines = extractTopStyleSheetComments(stylesheet); |
| 112 | + |
| 113 | + const auto factoryStyles = QStyleFactory::keys(); |
| 114 | + |
| 115 | + QString style = defaultStyle; |
| 116 | + |
| 117 | + for (const auto& line : topLines) { |
| 118 | + if (!line.startsWith("mo2-base-style")) { |
| 119 | + continue; |
| 120 | + } |
| 121 | + |
| 122 | + const auto parts = line.split(":"); |
| 123 | + if (parts.size() != 2) { |
| 124 | + log::warn("found invalid top-comment for mo2 in {}: {}", path, line); |
| 125 | + continue; |
| 126 | + } |
| 127 | + |
| 128 | + const auto tmpStyle = parts[1].trimmed(); |
| 129 | + const auto index = factoryStyles.indexOf(tmpStyle, 0, Qt::CaseInsensitive); |
| 130 | + if (index == -1) { |
| 131 | + log::warn("base style '{}' from style '{}' not found", tmpStyle, path, line); |
| 132 | + continue; |
| 133 | + } |
| 134 | + |
| 135 | + style = factoryStyles[index]; |
| 136 | + log::info("found base style '{}' for style '{}'", style, path); |
| 137 | + break; |
| 138 | + } |
| 139 | + |
| 140 | + return style; |
| 141 | +} |
| 142 | + |
| 143 | +} // namespace |
| 144 | + |
63 | 145 | ThemeManager::ThemeManager(QApplication* application) : m_app{application} |
64 | 146 | { |
65 | 147 | // add built-in themes |
66 | 148 | addQtThemes(); |
67 | 149 |
|
68 | 150 | // find the default theme - this might be a built-in Qt theme, or null, in which case |
69 | 151 | // we just create a default theme |
70 | | - if (auto it = |
71 | | - m_baseThemesByIdentifier.find(m_app->style()->objectName().toStdString()); |
| 152 | + if (auto it = m_baseThemesByIdentifier.find("windowsvista"); |
72 | 153 | it != m_baseThemesByIdentifier.end()) { |
73 | 154 | m_defaultTheme = it->second; |
74 | 155 | } else { |
@@ -148,12 +229,14 @@ void ThemeManager::loadQtTheme(std::string_view themeIdentifier) |
148 | 229 |
|
149 | 230 | void ThemeManager::loadExtensionTheme(std::shared_ptr<const Theme> const& theme) |
150 | 231 | { |
| 232 | + auto baseTheme = ToQString(m_defaultTheme->identifier()); |
| 233 | + const auto stylesheet = buildStyleSheet(theme, baseTheme); |
| 234 | + |
151 | 235 | // load the default theme |
152 | | - m_app->setStyle( |
153 | | - new ProxyStyle(QStyleFactory::create(ToQString(m_defaultTheme->identifier())))); |
| 236 | + m_app->setStyle(new ProxyStyle(QStyleFactory::create(baseTheme))); |
154 | 237 |
|
155 | 238 | // build the stylesheet and set it |
156 | | - m_app->setStyleSheet(buildStyleSheet(theme)); |
| 239 | + m_app->setStyleSheet(stylesheet); |
157 | 240 | } |
158 | 241 |
|
159 | 242 | void ThemeManager::unload() |
@@ -198,24 +281,19 @@ void ThemeManager::addQtThemes() |
198 | 281 | } |
199 | 282 | } |
200 | 283 |
|
201 | | -namespace |
| 284 | +QString ThemeManager::buildStyleSheet(std::shared_ptr<const Theme> const& theme, |
| 285 | + QString& baseTheme) const |
202 | 286 | { |
203 | | -QString readWholeFile(std::filesystem::path const& path) |
204 | | -{ |
205 | | - QFile file(path); |
206 | | - if (!file.open(QFile::ReadOnly | QFile::Text)) { |
207 | | - return {}; |
208 | | - } |
| 287 | + // read the file |
| 288 | + const auto stylesheetContent = readWholeFile(theme->stylesheet()); |
209 | 289 |
|
210 | | - return QTextStream(&file).readAll(); |
211 | | -} |
212 | | -} // namespace |
| 290 | + // check for base theme override |
| 291 | + baseTheme = |
| 292 | + extractBaseStyleFromStyleSheet(theme->stylesheet(), stylesheetContent, baseTheme); |
213 | 293 |
|
214 | | -QString ThemeManager::buildStyleSheet(std::shared_ptr<const Theme> const& theme) const |
215 | | -{ |
216 | | - // create the base stylesheet |
217 | | - QString stylesheet = patchStyleSheet(readWholeFile(theme->stylesheet()), |
218 | | - theme->stylesheet().parent_path()); |
| 294 | + // patch the file |
| 295 | + QString stylesheet = |
| 296 | + patchStyleSheet(stylesheetContent, theme->stylesheet().parent_path()); |
219 | 297 |
|
220 | 298 | for (auto&& themeAddition : m_additions) { |
221 | 299 | if (themeAddition->isAdditionFor(*theme)) { |
|
0 commit comments