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