| 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 | |