1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | // |
5 | // W A R N I N G |
6 | // ------------- |
7 | // |
8 | // This file is not part of the Qt API. It exists purely as an |
9 | // implementation detail. This header file may change from version to |
10 | // version without notice, or even be removed. |
11 | // |
12 | // We mean it. |
13 | // |
14 | |
15 | #include "qgtk3json_p.h" |
16 | #include <QtCore/QFile> |
17 | #include <QMetaEnum> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | QLatin1String QGtk3Json::fromPalette(QPlatformTheme::Palette palette) |
22 | { |
23 | return QLatin1String(QMetaEnum::fromType<QPlatformTheme::Palette>().valueToKey(value: static_cast<int>(palette))); |
24 | } |
25 | |
26 | QLatin1String QGtk3Json::fromGtkState(GtkStateFlags state) |
27 | { |
28 | return QGtk3Interface::fromGtkState(state); |
29 | } |
30 | |
31 | QLatin1String fromColor(const QColor &color) |
32 | { |
33 | return QLatin1String(QByteArray(color.name(format: QColor::HexRgb).toLatin1())); |
34 | } |
35 | |
36 | QLatin1String QGtk3Json::fromColorRole(QPalette::ColorRole role) |
37 | { |
38 | return QLatin1String(QMetaEnum::fromType<QPalette::ColorRole>().valueToKey(value: static_cast<int>(role))); |
39 | } |
40 | |
41 | QLatin1String QGtk3Json::fromColorGroup(QPalette::ColorGroup group) |
42 | { |
43 | return QLatin1String(QMetaEnum::fromType<QPalette::ColorGroup>().valueToKey(value: static_cast<int>(group))); |
44 | } |
45 | |
46 | QLatin1String QGtk3Json::fromGdkSource(QGtk3Interface::QGtkColorSource source) |
47 | { |
48 | return QLatin1String(QMetaEnum::fromType<QGtk3Interface::QGtkColorSource>().valueToKey(value: static_cast<int>(source))); |
49 | } |
50 | |
51 | QLatin1String QGtk3Json::fromWidgetType(QGtk3Interface::QGtkWidget widgetType) |
52 | { |
53 | return QLatin1String(QMetaEnum::fromType<QGtk3Interface::QGtkWidget>().valueToKey(value: static_cast<int>(widgetType))); |
54 | } |
55 | |
56 | QLatin1String QGtk3Json::fromColorScheme(Qt::ColorScheme app) |
57 | { |
58 | return QLatin1String(QMetaEnum::fromType<Qt::ColorScheme>().valueToKey(value: static_cast<int>(app))); |
59 | } |
60 | |
61 | #define CONVERT(type, key, def)\ |
62 | bool ok;\ |
63 | const int intVal = QMetaEnum::fromType<type>().keyToValue(key.toLatin1().constData(), &ok);\ |
64 | return ok ? static_cast<type>(intVal) : type::def |
65 | |
66 | Qt::ColorScheme QGtk3Json::toColorScheme(const QString &colorScheme) |
67 | { |
68 | CONVERT(Qt::ColorScheme, colorScheme, Unknown); |
69 | } |
70 | |
71 | QPlatformTheme::Palette QGtk3Json::toPalette(const QString &palette) |
72 | { |
73 | CONVERT(QPlatformTheme::Palette, palette, NPalettes); |
74 | } |
75 | |
76 | GtkStateFlags QGtk3Json::toGtkState(const QString &type) |
77 | { |
78 | int i = QGtk3Interface::toGtkState(state: type); |
79 | if (i < 0) |
80 | return GTK_STATE_FLAG_NORMAL; |
81 | return static_cast<GtkStateFlags>(i); |
82 | } |
83 | |
84 | QColor toColor(const QStringView &color) |
85 | { |
86 | return QColor::fromString(name: color); |
87 | } |
88 | |
89 | QPalette::ColorRole QGtk3Json::toColorRole(const QString &role) |
90 | { |
91 | CONVERT(QPalette::ColorRole, role, NColorRoles); |
92 | } |
93 | |
94 | QPalette::ColorGroup QGtk3Json::toColorGroup(const QString &group) |
95 | { |
96 | CONVERT(QPalette::ColorGroup, group, NColorGroups); |
97 | } |
98 | |
99 | QGtk3Interface::QGtkColorSource QGtk3Json::toGdkSource(const QString &source) |
100 | { |
101 | CONVERT(QGtk3Interface::QGtkColorSource, source, Background); |
102 | } |
103 | |
104 | QLatin1String QGtk3Json::fromSourceType(QGtk3Storage::SourceType sourceType) |
105 | { |
106 | return QLatin1String(QMetaEnum::fromType<QGtk3Storage::SourceType>().valueToKey(value: static_cast<int>(sourceType))); |
107 | } |
108 | |
109 | QGtk3Storage::SourceType QGtk3Json::toSourceType(const QString &sourceType) |
110 | { |
111 | CONVERT(QGtk3Storage::SourceType, sourceType, Invalid); |
112 | } |
113 | |
114 | QGtk3Interface::QGtkWidget QGtk3Json::toWidgetType(const QString &widgetType) |
115 | { |
116 | CONVERT(QGtk3Interface::QGtkWidget, widgetType, gtk_offscreen_window); |
117 | } |
118 | |
119 | #undef CONVERT |
120 | |
121 | bool QGtk3Json::save(const QGtk3Storage::PaletteMap &map, const QString &fileName, |
122 | QJsonDocument::JsonFormat format) |
123 | { |
124 | QJsonDocument doc = save(map); |
125 | if (doc.isEmpty()) { |
126 | qWarning() << "Nothing to save to" << fileName; |
127 | return false; |
128 | } |
129 | |
130 | QFile file(fileName); |
131 | if (!file.open(flags: QIODevice::WriteOnly)) { |
132 | qWarning() << "Unable to open file" << fileName << "for writing." ; |
133 | return false; |
134 | } |
135 | |
136 | if (!file.write(data: doc.toJson(format))) { |
137 | qWarning() << "Unable to serialize Json document." ; |
138 | return false; |
139 | } |
140 | |
141 | file.close(); |
142 | qInfo() << "Saved mapping data to" << fileName; |
143 | return true; |
144 | } |
145 | |
146 | const QJsonDocument QGtk3Json::save(const QGtk3Storage::PaletteMap &map) |
147 | { |
148 | QJsonObject paletteObject; |
149 | for (auto paletteIterator = map.constBegin(); paletteIterator != map.constEnd(); |
150 | ++paletteIterator) { |
151 | const QGtk3Storage::BrushMap &bm = paletteIterator.value(); |
152 | QFlatMap<QPalette::ColorRole, QGtk3Storage::BrushMap> brushMaps; |
153 | for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd(); |
154 | ++brushIterator) { |
155 | const QPalette::ColorRole role = brushIterator.key().colorRole; |
156 | if (brushMaps.contains(key: role)) { |
157 | brushMaps.value(key: role).insert(key: brushIterator.key(), value: brushIterator.value()); |
158 | } else { |
159 | QGtk3Storage::BrushMap newMap; |
160 | newMap.insert(key: brushIterator.key(), value: brushIterator.value()); |
161 | brushMaps.insert(key: role, value: newMap); |
162 | } |
163 | } |
164 | |
165 | QJsonObject brushArrayObject; |
166 | for (auto brushMapIterator = brushMaps.constBegin(); |
167 | brushMapIterator != brushMaps.constEnd(); ++brushMapIterator) { |
168 | |
169 | QJsonArray brushArray; |
170 | int brushIndex = 0; |
171 | const QGtk3Storage::BrushMap &bm = brushMapIterator.value(); |
172 | for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd(); |
173 | ++brushIterator) { |
174 | QJsonObject brushObject; |
175 | const QGtk3Storage::TargetBrush tb = brushIterator.key(); |
176 | QGtk3Storage::Source s = brushIterator.value(); |
177 | brushObject.insert(key: ceColorGroup, value: fromColorGroup(group: tb.colorGroup)); |
178 | brushObject.insert(key: ceColorScheme, value: fromColorScheme(app: tb.colorScheme)); |
179 | brushObject.insert(key: ceSourceType, value: fromSourceType(sourceType: s.sourceType)); |
180 | |
181 | QJsonObject sourceObject; |
182 | switch (s.sourceType) { |
183 | case QGtk3Storage::SourceType::Gtk: { |
184 | sourceObject.insert(key: ceGtkWidget, value: fromWidgetType(widgetType: s.gtk3.gtkWidgetType)); |
185 | sourceObject.insert(key: ceGdkSource, value: fromGdkSource(source: s.gtk3.source)); |
186 | sourceObject.insert(key: ceGtkState, value: fromGtkState(state: s.gtk3.state)); |
187 | sourceObject.insert(key: ceWidth, value: s.gtk3.width); |
188 | sourceObject.insert(key: ceHeight, value: s.gtk3.height); |
189 | } |
190 | break; |
191 | |
192 | case QGtk3Storage::SourceType::Fixed: { |
193 | QJsonObject fixedObject; |
194 | fixedObject.insert(key: ceColor, value: s.fix.fixedBrush.color().name()); |
195 | fixedObject.insert(key: ceWidth, value: s.fix.fixedBrush.texture().width()); |
196 | fixedObject.insert(key: ceHeight, value: s.fix.fixedBrush.texture().height()); |
197 | sourceObject.insert(key: ceBrush, value: fixedObject); |
198 | } |
199 | break; |
200 | |
201 | case QGtk3Storage::SourceType::Modified:{ |
202 | sourceObject.insert(key: ceColorGroup, value: fromColorGroup(group: s.rec.colorGroup)); |
203 | sourceObject.insert(key: ceColorRole, value: fromColorRole(role: s.rec.colorRole)); |
204 | sourceObject.insert(key: ceColorScheme, value: fromColorScheme(app: s.rec.colorScheme)); |
205 | sourceObject.insert(key: ceRed, value: s.rec.deltaRed); |
206 | sourceObject.insert(key: ceGreen, value: s.rec.deltaGreen); |
207 | sourceObject.insert(key: ceBlue, value: s.rec.deltaBlue); |
208 | sourceObject.insert(key: ceWidth, value: s.rec.width); |
209 | sourceObject.insert(key: ceHeight, value: s.rec.height); |
210 | sourceObject.insert(key: ceLighter, value: s.rec.lighter); |
211 | } |
212 | break; |
213 | |
214 | case QGtk3Storage::SourceType::Invalid: |
215 | break; |
216 | } |
217 | |
218 | brushObject.insert(key: ceData, value: sourceObject); |
219 | brushArray.insert(i: brushIndex, value: brushObject); |
220 | ++brushIndex; |
221 | } |
222 | brushArrayObject.insert(key: fromColorRole(role: brushMapIterator.key()), value: brushArray); |
223 | } |
224 | paletteObject.insert(key: fromPalette(palette: paletteIterator.key()), value: brushArrayObject); |
225 | } |
226 | |
227 | QJsonObject top; |
228 | top.insert(key: cePalettes, value: paletteObject); |
229 | return paletteObject.keys().isEmpty() ? QJsonDocument() : QJsonDocument(top); |
230 | } |
231 | |
232 | bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QString &fileName) |
233 | { |
234 | QFile file(fileName); |
235 | if (!file.open(flags: QIODevice::ReadOnly)) { |
236 | qCWarning(lcQGtk3Interface) << "Unable to open file:" << fileName; |
237 | return false; |
238 | } |
239 | |
240 | QJsonParseError err; |
241 | QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll(), error: &err); |
242 | if (err.error != QJsonParseError::NoError) { |
243 | qWarning(catFunc: lcQGtk3Interface) << "Unable to parse Json document from" << fileName |
244 | << err.error << err.errorString(); |
245 | return false; |
246 | } |
247 | |
248 | if (Q_LIKELY(load(map, doc))) { |
249 | qInfo() << "GTK mapping successfully imported from" << fileName; |
250 | return true; |
251 | } |
252 | |
253 | qWarning() << "File" << fileName << "could not be loaded." ; |
254 | return false; |
255 | } |
256 | |
257 | bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) |
258 | { |
259 | #define GETSTR(obj, key)\ |
260 | if (!obj.contains(key)) {\ |
261 | qCInfo(lcQGtk3Interface) << key << "missing for palette" << paletteName\ |
262 | << ", Brush" << colorRoleName;\ |
263 | return false;\ |
264 | }\ |
265 | value = obj[key].toString() |
266 | |
267 | #define GETINT(obj, key, var) GETSTR(obj, key);\ |
268 | if (!obj[key].isDouble()) {\ |
269 | qCInfo(lcQGtk3Interface) << key << "type mismatch" << value\ |
270 | << "is not an integer!"\ |
271 | << "(Palette" << paletteName << "), Brush" << colorRoleName;\ |
272 | return false;\ |
273 | }\ |
274 | const int var = obj[key].toInt() |
275 | |
276 | map.clear(); |
277 | const QJsonObject top(doc.object()); |
278 | if (doc.isEmpty() || top.isEmpty() || !top.contains(key: cePalettes)) { |
279 | qCInfo(lcQGtk3Interface) << "Document does not contain Palettes." ; |
280 | return false; |
281 | } |
282 | |
283 | const QStringList &paletteList = top[cePalettes].toObject().keys(); |
284 | for (const QString &paletteName : paletteList) { |
285 | bool ok; |
286 | const int intVal = QMetaEnum::fromType<QPlatformTheme::Palette>().keyToValue(key: paletteName |
287 | .toLatin1().constData(), ok: &ok); |
288 | if (!ok) { |
289 | qCInfo(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; |
290 | return false; |
291 | } |
292 | const QJsonObject &paletteObject = top[cePalettes][paletteName].toObject(); |
293 | const QStringList &brushList = paletteObject.keys(); |
294 | if (brushList.isEmpty()) { |
295 | qCInfo(lcQGtk3Interface) << "Palette" << paletteName << "does not contain brushes" ; |
296 | return false; |
297 | } |
298 | |
299 | const QPlatformTheme::Palette paletteType = static_cast<QPlatformTheme::Palette>(intVal); |
300 | QGtk3Storage::BrushMap brushes; |
301 | const QStringList &colorRoles = paletteObject.keys(); |
302 | for (const QString &colorRoleName : colorRoles) { |
303 | const int intVal = QMetaEnum::fromType<QPalette::ColorRole>().keyToValue(key: colorRoleName |
304 | .toLatin1().constData(), ok: &ok); |
305 | if (!ok) { |
306 | qCInfo(lcQGtk3Interface) << "Palette" << paletteName |
307 | << "contains invalid color role" << colorRoleName; |
308 | return false; |
309 | } |
310 | const QPalette::ColorRole colorRole = static_cast<QPalette::ColorRole>(intVal); |
311 | const QJsonArray &brushArray = paletteObject[colorRoleName].toArray(); |
312 | for (int brushIndex = 0; brushIndex < brushArray.size(); ++brushIndex) { |
313 | const QJsonObject brushObject = brushArray.at(i: brushIndex).toObject(); |
314 | if (brushObject.isEmpty()) { |
315 | qCInfo(lcQGtk3Interface) << "Brush specification missing at for palette" |
316 | << paletteName << ", Brush" << colorRoleName; |
317 | return false; |
318 | } |
319 | |
320 | QString value; |
321 | GETSTR(brushObject, ceSourceType); |
322 | const QGtk3Storage::SourceType sourceType = toSourceType(sourceType: value); |
323 | GETSTR(brushObject, ceColorGroup); |
324 | const QPalette::ColorGroup colorGroup = toColorGroup(group: value); |
325 | GETSTR(brushObject, ceColorScheme); |
326 | const Qt::ColorScheme colorScheme = toColorScheme(colorScheme: value); |
327 | QGtk3Storage::TargetBrush tb(colorGroup, colorRole, colorScheme); |
328 | QGtk3Storage::Source s; |
329 | |
330 | if (!brushObject.contains(key: ceData) || !brushObject[ceData].isObject()) { |
331 | qCInfo(lcQGtk3Interface) << "Source specification missing for palette" << paletteName |
332 | << "Brush" << colorRoleName; |
333 | return false; |
334 | } |
335 | const QJsonObject &sourceObject = brushObject[ceData].toObject(); |
336 | |
337 | switch (sourceType) { |
338 | case QGtk3Storage::SourceType::Gtk: { |
339 | GETSTR(sourceObject, ceGdkSource); |
340 | const QGtk3Interface::QGtkColorSource gtkSource = toGdkSource(source: value); |
341 | GETSTR(sourceObject, ceGtkState); |
342 | const GtkStateFlags gtkState = toGtkState(type: value); |
343 | GETSTR(sourceObject, ceGtkWidget); |
344 | const QGtk3Interface::QGtkWidget widgetType = toWidgetType(widgetType: value); |
345 | GETINT(sourceObject, ceHeight, height); |
346 | GETINT(sourceObject, ceWidth, width); |
347 | s = QGtk3Storage::Source(widgetType, gtkSource, gtkState, width, height); |
348 | } |
349 | break; |
350 | |
351 | case QGtk3Storage::SourceType::Fixed: { |
352 | if (!sourceObject.contains(key: ceBrush)) { |
353 | qCInfo(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName |
354 | << "Brush" << colorRoleName; |
355 | return false; |
356 | } |
357 | const QJsonObject &fixedSource = sourceObject[ceBrush].toObject(); |
358 | GETINT(fixedSource, ceWidth, width); |
359 | GETINT(fixedSource, ceHeight, height); |
360 | GETSTR(fixedSource, ceColor); |
361 | const QColor color(value); |
362 | if (!color.isValid()) { |
363 | qCInfo(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName |
364 | << "Brush" << colorRoleName; |
365 | return false; |
366 | } |
367 | const QBrush fixedBrush = (width < 0 && height < 0) |
368 | ? QBrush(color, QPixmap(width, height)) |
369 | : QBrush(color); |
370 | s = QGtk3Storage::Source(fixedBrush); |
371 | } |
372 | break; |
373 | |
374 | case QGtk3Storage::SourceType::Modified: { |
375 | GETSTR(sourceObject, ceColorGroup); |
376 | const QPalette::ColorGroup colorGroup = toColorGroup(group: value); |
377 | GETSTR(sourceObject, ceColorRole); |
378 | const QPalette::ColorRole colorRole = toColorRole(role: value); |
379 | GETSTR(sourceObject, ceColorScheme); |
380 | const Qt::ColorScheme colorScheme = toColorScheme(colorScheme: value); |
381 | GETINT(sourceObject, ceLighter, lighter); |
382 | GETINT(sourceObject, ceRed, red); |
383 | GETINT(sourceObject, ceBlue, blue); |
384 | GETINT(sourceObject, ceGreen, green); |
385 | s = QGtk3Storage::Source(colorGroup, colorRole, colorScheme, |
386 | lighter, red, green, blue); |
387 | } |
388 | break; |
389 | |
390 | case QGtk3Storage::SourceType::Invalid: |
391 | qInfo(catFunc: lcQGtk3Interface) << "Invalid source type for palette" << paletteName |
392 | << "Brush." << colorRoleName; |
393 | return false; |
394 | } |
395 | brushes.insert(key: tb, value: s); |
396 | } |
397 | } |
398 | map.insert(key: paletteType, value: brushes); |
399 | } |
400 | return true; |
401 | } |
402 | |
403 | QT_END_NAMESPACE |
404 | |
405 | |