1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2016 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 **************************************************************************/
19
20 #include "ThemesManager.h"
21 #include "Application.h"
22 #include "PlatformIntegration.h"
23 #include "SettingsManager.h"
24 #include "../ui/Style.h"
25
26 #ifdef Q_OS_WIN32
27 #include <QtCore/QAbstractEventDispatcher>
28 #endif
29 #include <QtCore/QDir>
30 #include <QtCore/QFile>
31 #include <QtCore/QJsonArray>
32 #include <QtCore/QJsonDocument>
33 #include <QtCore/QJsonObject>
34 #include <QtGui/QIcon>
35 #include <QtWidgets/QWidget>
36
37 #ifdef Q_OS_WIN32
38 #include <windows.h>
39 #endif
40
41 namespace Otter
42 {
43
44 int Palette::m_colorRoleEnumerator(-1);
45
Palette(const QString & path,QObject * parent)46 Palette::Palette(const QString &path, QObject *parent) : QObject(parent)
47 {
48 if (m_colorRoleEnumerator < 0)
49 {
50 m_colorRoleEnumerator = Palette::staticMetaObject.indexOfEnumerator(QLatin1String("ColorRole").data());
51 }
52
53 if (path.isEmpty() || !QFile::exists(path))
54 {
55 return;
56 }
57
58 QFile file(path);
59
60 if (file.open(QIODevice::ReadOnly))
61 {
62 const QJsonArray colorsArray(QJsonDocument::fromJson(file.readAll()).array());
63
64 for (int i = 0; i < colorsArray.count(); ++i)
65 {
66 const QJsonObject colorRoleObject(colorsArray.at(i).toObject());
67 ColorRoleInformation colorRoleInformation;
68 colorRoleInformation.active = QColor(colorRoleObject.value(QLatin1String("active")).toString());
69 colorRoleInformation.disabled = QColor(colorRoleObject.value(QLatin1String("disabled")).toString());
70 colorRoleInformation.inactive = QColor(colorRoleObject.value(QLatin1String("inactive")).toString());
71
72 m_colors[static_cast<ColorRole>(Palette::staticMetaObject.enumerator(m_colorRoleEnumerator).keyToValue(colorRoleObject.value(QLatin1String("role")).toString().toLatin1()))] = colorRoleInformation;
73 }
74
75 file.close();
76 }
77 }
78
getColor(ColorRole role) const79 Palette::ColorRoleInformation Palette::getColor(ColorRole role) const
80 {
81 if (!m_colors.contains(role))
82 {
83 switch (role)
84 {
85 case SidebarRole:
86 case TabRole:
87 role = WindowRole;
88
89 break;
90 case SidebarTextRole:
91 case TabTextRole:
92 role = WindowTextRole;
93
94 break;
95 default:
96 break;
97 }
98 }
99
100 return m_colors.value(role);
101 }
102
103 ThemesManager* ThemesManager::m_instance(nullptr);
104 Palette* ThemesManager::m_palette(nullptr);
105 QWidget* ThemesManager::m_probeWidget(nullptr);
106 QString ThemesManager::m_iconThemePath(QLatin1String(":/icons/theme/"));
107 bool ThemesManager::m_useSystemIconTheme(false);
108
ThemesManager(QObject * parent)109 ThemesManager::ThemesManager(QObject *parent) : QObject(parent)
110 {
111 m_useSystemIconTheme = SettingsManager::getOption(SettingsManager::Interface_UseSystemIconThemeOption).toBool();
112
113 handleOptionChanged(SettingsManager::Interface_IconThemePathOption, SettingsManager::getOption(SettingsManager::Interface_IconThemePathOption));
114
115 connect(SettingsManager::getInstance(), &SettingsManager::optionChanged, this, &ThemesManager::handleOptionChanged);
116 }
117
createInstance()118 void ThemesManager::createInstance()
119 {
120 if (!m_instance)
121 {
122 m_instance = new ThemesManager(QCoreApplication::instance());
123 m_palette = new Palette(SessionsManager::getWritableDataPath(QLatin1String("colors.json")), m_instance);
124 m_probeWidget = new QWidget();
125 m_probeWidget->hide();
126 m_probeWidget->setAttribute(Qt::WA_DontShowOnScreen, true);
127 m_probeWidget->installEventFilter(m_instance);
128
129 #ifdef Q_OS_WIN32
130 QAbstractEventDispatcher::instance()->installNativeEventFilter(m_instance);
131 #endif
132 }
133 }
134
handleOptionChanged(int identifier,const QVariant & value)135 void ThemesManager::handleOptionChanged(int identifier, const QVariant &value)
136 {
137 switch (identifier)
138 {
139 case SettingsManager::Interface_IconThemePathOption:
140 {
141 QString path(value.toString());
142
143 if (path.isEmpty())
144 {
145 path = QLatin1String(":/icons/theme/");
146 }
147 else if (!path.endsWith(QDir::separator()))
148 {
149 path.append(QDir::separator());
150 }
151
152 if (path != m_iconThemePath)
153 {
154 m_iconThemePath = path;
155
156 emit iconThemeChanged();
157 }
158 }
159
160 break;
161 case SettingsManager::Interface_UseSystemIconThemeOption:
162 if (value.toBool() != m_useSystemIconTheme)
163 {
164 m_useSystemIconTheme = value.toBool();
165
166 emit iconThemeChanged();
167 }
168 default:
169 break;
170 }
171 }
172
getInstance()173 ThemesManager* ThemesManager::getInstance()
174 {
175 return m_instance;
176 }
177
getPalette()178 Palette* ThemesManager::getPalette()
179 {
180 return m_palette;
181 }
182
createStyle(const QString & name)183 Style* ThemesManager::createStyle(const QString &name)
184 {
185 Style *style(nullptr);
186 const PlatformIntegration *integration(Application::getPlatformIntegration());
187
188 if (integration)
189 {
190 style = integration->createStyle(name);
191 }
192
193 if (!style)
194 {
195 style = new Style(name);
196 }
197
198 return style;
199 }
200
getAnimationPath(const QString & name)201 QString ThemesManager::getAnimationPath(const QString &name)
202 {
203 const QString iconPath(m_iconThemePath + name);
204 const QString svgPath(iconPath + QLatin1String(".svg"));
205
206 if (QFile::exists(svgPath))
207 {
208 return svgPath;
209 }
210
211 const QString gifPath(iconPath + QLatin1String(".gif"));
212
213 if (QFile::exists(gifPath))
214 {
215 return gifPath;
216 }
217
218 return {};
219 }
220
createIcon(const QString & name,bool fromTheme)221 QIcon ThemesManager::createIcon(const QString &name, bool fromTheme)
222 {
223 if (name.isEmpty())
224 {
225 return {};
226 }
227
228 if (name.startsWith(QLatin1String("data:image/")))
229 {
230 return QIcon(Utils::loadPixmapFromDataUri(name));
231 }
232
233 if (m_useSystemIconTheme && fromTheme && QIcon::hasThemeIcon(name))
234 {
235 return QIcon::fromTheme(name);
236 }
237
238 const QString iconPath((!fromTheme && name == QLatin1String("otter-browser")) ? QLatin1String(":/icons/otter-browser") : m_iconThemePath + name);
239 const QString svgPath(iconPath + QLatin1String(".svg"));
240 const QString rasterPath(iconPath + QLatin1String(".png"));
241
242 if (QFile::exists(svgPath))
243 {
244 return QIcon(svgPath);
245 }
246
247 if (QFile::exists(rasterPath))
248 {
249 return QIcon(rasterPath);
250 }
251
252 return {};
253 }
254
eventFilter(QObject * object,QEvent * event)255 bool ThemesManager::eventFilter(QObject *object, QEvent *event)
256 {
257 if (object == m_probeWidget && event->type() == QEvent::StyleChange)
258 {
259 if (!QApplication::style()->inherits("Otter::Style"))
260 {
261 const QList<QStyle*> children(QApplication::style()->findChildren<QStyle*>());
262 bool hasMatch(false);
263
264 for (int i = 0; i < children.count(); ++i)
265 {
266 if (children.at(i)->inherits("Otter::Style"))
267 {
268 hasMatch = true;
269
270 break;
271 }
272 }
273
274 if (!hasMatch)
275 {
276 QApplication::setStyle(createStyle(SettingsManager::getOption(SettingsManager::Interface_WidgetStyleOption).toString()));
277 }
278 }
279
280 emit widgetStyleChanged();
281 }
282
283 return QObject::eventFilter(object, event);
284 }
285
286 #ifdef Q_OS_WIN32
nativeEventFilter(const QByteArray & eventType,void * message,long * result)287 bool ThemesManager::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
288 {
289 Q_UNUSED(eventType)
290 Q_UNUSED(result)
291
292 const MSG *nativeMessage(static_cast<MSG*>(message));
293
294 if (nativeMessage && nativeMessage->message == WM_THEMECHANGED)
295 {
296 emit widgetStyleChanged();
297 }
298
299 return false;
300 }
301 #endif
302
303 }
304