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[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::Mixed: { |
215 | sourceObject.insert(key: ceColorGroup, value: fromColorGroup(group: s.mix.sourceGroup)); |
216 | QJsonArray colorRoles; |
217 | colorRoles << fromColorRole(role: s.mix.colorRole1) |
218 | << fromColorRole(role: s.mix.colorRole2); |
219 | sourceObject.insert(key: ceColorRole, value: colorRoles); |
220 | } |
221 | break; |
222 | |
223 | case QGtk3Storage::SourceType::Invalid: |
224 | break; |
225 | } |
226 | |
227 | brushObject.insert(key: ceData, value: sourceObject); |
228 | brushArray.insert(i: brushIndex, value: brushObject); |
229 | ++brushIndex; |
230 | } |
231 | brushArrayObject.insert(key: fromColorRole(role: brushMapIterator.key()), value: brushArray); |
232 | } |
233 | paletteObject.insert(key: fromPalette(palette: paletteIterator.key()), value: brushArrayObject); |
234 | } |
235 | |
236 | QJsonObject top; |
237 | top.insert(key: cePalettes, value: paletteObject); |
238 | return paletteObject.keys().isEmpty() ? QJsonDocument() : QJsonDocument(top); |
239 | } |
240 | |
241 | bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QString &fileName) |
242 | { |
243 | QFile file(fileName); |
244 | if (!file.open(flags: QIODevice::ReadOnly)) { |
245 | qCWarning(lcQGtk3Interface) << "Unable to open file:" << fileName; |
246 | return false; |
247 | } |
248 | |
249 | QJsonParseError err; |
250 | QJsonDocument doc = QJsonDocument::fromJson(json: file.readAll(), error: &err); |
251 | if (err.error != QJsonParseError::NoError) { |
252 | qCWarning(lcQGtk3Interface) << "Unable to parse Json document from" << fileName |
253 | << err.error << err.errorString(); |
254 | return false; |
255 | } |
256 | |
257 | if (Q_LIKELY(load(map, doc))) { |
258 | qInfo() << "GTK mapping successfully imported from" << fileName; |
259 | return true; |
260 | } |
261 | |
262 | qWarning() << "File" << fileName << "could not be loaded." ; |
263 | return false; |
264 | } |
265 | |
266 | bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) |
267 | { |
268 | #define GETSTR(obj, key)\ |
269 | if (!obj.contains(key)) {\ |
270 | qCInfo(lcQGtk3Interface) << key << "missing for palette" << paletteName\ |
271 | << ", Brush" << colorRoleName;\ |
272 | return false;\ |
273 | }\ |
274 | value = obj[key].toString() |
275 | |
276 | #define GETINT(obj, key, var) GETSTR(obj, key);\ |
277 | if (!obj[key].isDouble()) {\ |
278 | qCInfo(lcQGtk3Interface) << key << "type mismatch" << value\ |
279 | << "is not an integer!"\ |
280 | << "(Palette" << paletteName << "), Brush" << colorRoleName;\ |
281 | return false;\ |
282 | }\ |
283 | const int var = obj[key].toInt() |
284 | |
285 | map.clear(); |
286 | const QJsonObject top(doc.object()); |
287 | if (doc.isEmpty() || top.isEmpty() || !top.contains(key: cePalettes)) { |
288 | qCInfo(lcQGtk3Interface) << "Document does not contain Palettes." ; |
289 | return false; |
290 | } |
291 | |
292 | const QStringList &paletteList = top[cePalettes].toObject().keys(); |
293 | for (const QString &paletteName : paletteList) { |
294 | bool ok; |
295 | const int intVal = QMetaEnum::fromType<QPlatformTheme::Palette>().keyToValue(key: paletteName |
296 | .toLatin1().constData(), ok: &ok); |
297 | if (!ok) { |
298 | qCInfo(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; |
299 | return false; |
300 | } |
301 | const QJsonObject &paletteObject = top[cePalettes][paletteName].toObject(); |
302 | const QStringList &brushList = paletteObject.keys(); |
303 | if (brushList.isEmpty()) { |
304 | qCInfo(lcQGtk3Interface) << "Palette" << paletteName << "does not contain brushes" ; |
305 | return false; |
306 | } |
307 | |
308 | const QPlatformTheme::Palette paletteType = static_cast<QPlatformTheme::Palette>(intVal); |
309 | QGtk3Storage::BrushMap brushes; |
310 | const QStringList &colorRoles = paletteObject.keys(); |
311 | for (const QString &colorRoleName : colorRoles) { |
312 | const int intVal = QMetaEnum::fromType<QPalette::ColorRole>().keyToValue(key: colorRoleName |
313 | .toLatin1().constData(), ok: &ok); |
314 | if (!ok) { |
315 | qCInfo(lcQGtk3Interface) << "Palette" << paletteName |
316 | << "contains invalid color role" << colorRoleName; |
317 | return false; |
318 | } |
319 | const QPalette::ColorRole colorRole = static_cast<QPalette::ColorRole>(intVal); |
320 | const QJsonArray &brushArray = paletteObject[colorRoleName].toArray(); |
321 | for (int brushIndex = 0; brushIndex < brushArray.size(); ++brushIndex) { |
322 | const QJsonObject brushObject = brushArray.at(i: brushIndex).toObject(); |
323 | if (brushObject.isEmpty()) { |
324 | qCInfo(lcQGtk3Interface) << "Brush specification missing at for palette" |
325 | << paletteName << ", Brush" << colorRoleName; |
326 | return false; |
327 | } |
328 | |
329 | QString value; |
330 | GETSTR(brushObject, ceSourceType); |
331 | const QGtk3Storage::SourceType sourceType = toSourceType(sourceType: value); |
332 | GETSTR(brushObject, ceColorGroup); |
333 | const QPalette::ColorGroup colorGroup = toColorGroup(group: value); |
334 | GETSTR(brushObject, ceColorScheme); |
335 | const Qt::ColorScheme colorScheme = toColorScheme(colorScheme: value); |
336 | QGtk3Storage::TargetBrush tb(colorGroup, colorRole, colorScheme); |
337 | QGtk3Storage::Source s; |
338 | |
339 | if (!brushObject.contains(key: ceData) || !brushObject[ceData].isObject()) { |
340 | qCInfo(lcQGtk3Interface) << "Source specification missing for palette" << paletteName |
341 | << "Brush" << colorRoleName; |
342 | return false; |
343 | } |
344 | const QJsonObject &sourceObject = brushObject[ceData].toObject(); |
345 | |
346 | switch (sourceType) { |
347 | case QGtk3Storage::SourceType::Gtk: { |
348 | GETSTR(sourceObject, ceGdkSource); |
349 | const QGtk3Interface::QGtkColorSource gtkSource = toGdkSource(source: value); |
350 | GETSTR(sourceObject, ceGtkState); |
351 | const GtkStateFlags gtkState = toGtkState(type: value); |
352 | GETSTR(sourceObject, ceGtkWidget); |
353 | const QGtk3Interface::QGtkWidget widgetType = toWidgetType(widgetType: value); |
354 | GETINT(sourceObject, ceHeight, height); |
355 | GETINT(sourceObject, ceWidth, width); |
356 | s = QGtk3Storage::Source(widgetType, gtkSource, gtkState, width, height); |
357 | } |
358 | break; |
359 | |
360 | case QGtk3Storage::SourceType::Fixed: { |
361 | if (!sourceObject.contains(key: ceBrush)) { |
362 | qCInfo(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName |
363 | << "Brush" << colorRoleName; |
364 | return false; |
365 | } |
366 | const QJsonObject &fixedSource = sourceObject[ceBrush].toObject(); |
367 | GETINT(fixedSource, ceWidth, width); |
368 | GETINT(fixedSource, ceHeight, height); |
369 | GETSTR(fixedSource, ceColor); |
370 | const QColor color(value); |
371 | if (!color.isValid()) { |
372 | qCInfo(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName |
373 | << "Brush" << colorRoleName; |
374 | return false; |
375 | } |
376 | const QBrush fixedBrush = (width < 0 && height < 0) |
377 | ? QBrush(color, QPixmap(width, height)) |
378 | : QBrush(color); |
379 | s = QGtk3Storage::Source(fixedBrush); |
380 | } |
381 | break; |
382 | |
383 | case QGtk3Storage::SourceType::Modified: { |
384 | GETSTR(sourceObject, ceColorGroup); |
385 | const QPalette::ColorGroup colorGroup = toColorGroup(group: value); |
386 | GETSTR(sourceObject, ceColorRole); |
387 | const QPalette::ColorRole colorRole = toColorRole(role: value); |
388 | GETSTR(sourceObject, ceColorScheme); |
389 | const Qt::ColorScheme colorScheme = toColorScheme(colorScheme: value); |
390 | GETINT(sourceObject, ceLighter, lighter); |
391 | GETINT(sourceObject, ceRed, red); |
392 | GETINT(sourceObject, ceBlue, blue); |
393 | GETINT(sourceObject, ceGreen, green); |
394 | s = QGtk3Storage::Source(colorGroup, colorRole, colorScheme, |
395 | lighter, red, green, blue); |
396 | } |
397 | break; |
398 | |
399 | case QGtk3Storage::SourceType::Mixed: { |
400 | if (!sourceObject[ceColorRole].isArray()) { |
401 | qCInfo(lcQGtk3Interface) << "Mixed brush missing the array of color roles for palette:" << paletteName |
402 | << "Brush" << colorRoleName; |
403 | return false; |
404 | } |
405 | QJsonArray colorRoles = sourceObject[ceColorRole].toArray(); |
406 | if (colorRoles.size() < 2) { |
407 | qCInfo(lcQGtk3Interface) << "Mixed brush missing enough color roles for palette" << paletteName |
408 | << "Brush" << colorRoleName; |
409 | return false; |
410 | } |
411 | const QPalette::ColorRole colorRole1 = toColorRole(role: colorRoles[0].toString()); |
412 | const QPalette::ColorRole colorRole2 = toColorRole(role: colorRoles[1].toString()); |
413 | GETSTR(sourceObject, ceColorGroup); |
414 | const QPalette::ColorGroup sourceGroup = toColorGroup(group: value); |
415 | s = QGtk3Storage::Source(sourceGroup, colorRole1, colorRole2); |
416 | } |
417 | break; |
418 | |
419 | case QGtk3Storage::SourceType::Invalid: |
420 | qCInfo(lcQGtk3Interface) << "Invalid source type for palette" << paletteName |
421 | << "Brush." << colorRoleName; |
422 | return false; |
423 | } |
424 | brushes.insert(key: tb, value: s); |
425 | } |
426 | } |
427 | map.insert(key: paletteType, value: brushes); |
428 | } |
429 | return true; |
430 | } |
431 | |
432 | QT_END_NAMESPACE |
433 | |
434 | |