// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "designericons.h" #include #include #include #include #include #include #include #include using namespace QmlDesigner; namespace { // Blank namespace template struct DesignerIconEnums { typedef EType EnumType; static QString toString(const EnumType &enumValue); static EnumType value(const QString &keyStr, bool *ok = nullptr); static const QMetaEnum metaEnum; static const QString keyName; }; template struct DesignerEnumConfidentType { typedef EType EnumType; }; template <> struct DesignerEnumConfidentType { typedef DesignerIcons::Mode EnumType; }; template <> struct DesignerEnumConfidentType { typedef DesignerIcons::State EnumType; }; template QString getEnumName() { QMetaEnum metaEnum = QMetaEnum::fromType(); QString enumName = QString::fromLatin1(metaEnum.enumName()); if (enumName.size() && enumName.at(0).isUpper()) enumName.replace(0, 1, enumName.at(0).toLower()); return enumName; }; template <> QString getEnumName() { return QLatin1String("iconName"); }; template const QMetaEnum DesignerIconEnums::metaEnum = QMetaEnum::fromType::EnumType>(); template const QString DesignerIconEnums::keyName = getEnumName::EnumType>(); template QString DesignerIconEnums::toString(const EType &enumValue) { return QString::fromLatin1(metaEnum.valueToKey(enumValue)); } template EType DesignerIconEnums::value(const QString &keyStr, bool *ok) { return static_cast(metaEnum.keyToValue(keyStr.toLatin1(), ok)); } QJsonObject mergeJsons(const QJsonObject &prior, const QJsonObject &joiner) { QJsonObject object = prior; const QStringList joinerKeys = joiner.keys(); for (const QString &joinerKey : joinerKeys) { if (!object.contains(joinerKey)) { object.insert(joinerKey, joiner.value(joinerKey)); } else { QJsonValue ov = object.value(joinerKey); QJsonValue jv = joiner.value(joinerKey); if (ov.isObject() && jv.isObject()) { QJsonObject mg = mergeJsons(ov.toObject(), jv.toObject()); object.insert(joinerKey, mg); } } } return object; } inline QString toJsonSize(const QSize &size) { return QString::fromLatin1("%1x%2").arg(size.width()).arg(size.height()); } template void pushSimpleEnumValue(QJsonObject &object, const EnumType &enumVal) { const QString &enumKey = DesignerIconEnums::keyName; QString enumValue = DesignerIconEnums::toString(enumVal); object.insert(enumKey, enumValue); } template T jsonSafeValue(const QJsonObject &jsonObject, const QString &symbolName, std::function validityCheck = [](const T&) -> bool {return true;}) { if (!jsonObject.contains(symbolName)) throw InvalidArgumentException(__LINE__, __FUNCTION__, __FILE__, symbolName.toLatin1()); QVariant symbolVar = jsonObject.value(symbolName); T extractedVal = symbolVar.value(); if (!validityCheck(extractedVal)) throw InvalidArgumentException(__LINE__, __FUNCTION__, __FILE__, symbolName.toLatin1()); return extractedVal; } QSize jsonSafeSize(const QJsonObject &jsonObject, const QString &symbolName) { QString extractedVal = jsonSafeValue(jsonObject, symbolName); QStringList dims = extractedVal.split("x"); if (dims.size() == 2) { bool wOk; bool hOk; int cWidth = dims.first().toInt(&wOk); int cHeight = dims.last().toInt(&hOk); if (wOk && hOk) return {cWidth, cHeight}; } throw InvalidArgumentException(__LINE__, __FUNCTION__, __FILE__, symbolName.toLatin1()); return {}; } template T jsonSafeMetaEnum(const QJsonObject &jsonObject, const QString &symbolName = DesignerIconEnums::keyName) { QString extractedVal = jsonSafeValue(jsonObject, symbolName); bool ok; T enumIndex = static_cast (DesignerIconEnums::value(extractedVal, &ok)); if (ok) return enumIndex; throw InvalidArgumentException(__LINE__, __FUNCTION__, __FILE__, symbolName.toLatin1()); return {}; } template struct JsonMap {}; template <> struct JsonMap { static IconFontHelper value(const QJsonObject &jsonObject, const QJsonObject &telescopingMap) { QJsonObject fontHelperJson = mergeJsons(jsonObject, telescopingMap); return IconFontHelper::fromJson(fontHelperJson); } static QJsonObject json(const IconFontHelper &iconFontHelper) { QJsonObject object; pushSimpleEnumValue(object, iconFontHelper.themeIcon()); pushSimpleEnumValue(object, iconFontHelper.themeColor()); object.insert("size", toJsonSize(iconFontHelper.size())); return object; } }; template struct JsonMap> { typedef QMap MapType; static MapType value(const QJsonObject &mapObject, const QJsonObject &telescopingMap) { typedef typename MapType::key_type KeyType; typedef typename MapType::mapped_type ValueType; QMap output; QJsonObject curObject = mergeJsons(mapObject, telescopingMap); const QStringList keyList = curObject.keys(); QStringList validKeys; QString keyTypeStr = DesignerIconEnums::keyName; QJsonObject nextTelescopingMap = telescopingMap; for (const QString &jsonKey : keyList) { bool keyAvailable = false; DesignerIconEnums::value(jsonKey, &keyAvailable); if (keyAvailable) validKeys.append(jsonKey); else nextTelescopingMap.insert(jsonKey, curObject.value(jsonKey)); } for (const QString &jsonKey : validKeys) { bool keyAvailable = false; const KeyType key = DesignerIconEnums::value(jsonKey, &keyAvailable); QJsonValue jsonValue = curObject.value(jsonKey); nextTelescopingMap.insert(keyTypeStr, jsonKey); if (!jsonValue.isObject()) { qWarning() << Q_FUNC_INFO << __LINE__ << "Value of the" << jsonKey << "should be a json object."; continue; } output.insert(key, JsonMap::value(jsonValue.toObject(), nextTelescopingMap)); } return output; } static QJsonObject json(const QMap &map) { QJsonObject output; #if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) for (const auto &[key, val] : map.asKeyValueRange()) output[DesignerIconEnums::toString(key)] = JsonMap::json(val); #else const auto mapKeys = map.keys(); for (const Key &key : mapKeys) { const Value &val = map.value(key); output[DesignerIconEnums::toString(key)] = JsonMap::json(val); } #endif return output; } }; } // End of blank namespace class QmlDesigner::DesignerIconsPrivate { public: DesignerIconsPrivate(const QString &fontName) : mFontName(fontName) {} QString mFontName; DesignerIcons::IconsMap icons; static QCache cache; }; QCache DesignerIconsPrivate::cache(100); IconFontHelper::IconFontHelper(Theme::Icon themeIcon, Theme::Color color, const QSize &size, QIcon::Mode mode, QIcon::State state) : Super(Theme::getIconUnicode(themeIcon), Theme::getColor(color), size, mode, state) , mThemeIcon(themeIcon) , mThemeColor(color) {} IconFontHelper IconFontHelper::fromJson(const QJsonObject &jsonObject) { try { Theme::Icon iconName = jsonSafeMetaEnum(jsonObject); Theme::Color iconColor = jsonSafeMetaEnum(jsonObject); QSize iconSize = jsonSafeSize(jsonObject, "size"); QIcon::Mode iconMode = jsonSafeMetaEnum(jsonObject); QIcon::State iconState = jsonSafeMetaEnum(jsonObject); return IconFontHelper(iconName, iconColor, iconSize, iconMode, iconState); } catch (const Exception &exception) { exception.showException("Faild to load IconFontHelper"); return {}; } } QJsonObject IconFontHelper::toJson() const { QJsonObject jsonObject; pushSimpleEnumValue(jsonObject, themeIcon()); pushSimpleEnumValue(jsonObject, themeColor()); pushSimpleEnumValue(jsonObject, mode()); pushSimpleEnumValue(jsonObject, state()); jsonObject.insert("size", toJsonSize(size())); return jsonObject; } Theme::Icon IconFontHelper::themeIcon() const { return mThemeIcon; } Theme::Color IconFontHelper::themeColor() const { return mThemeColor; } IconFontHelper::IconFontHelper() : IconFontHelper({}, {}, {}, {}, {}) {} DesignerIcons::DesignerIcons(const QString &fontName, const QString &iconDatabase) : d(new DesignerIconsPrivate(fontName)) { if (iconDatabase.size()) loadIconSettings(iconDatabase); } DesignerIcons::~DesignerIcons() { delete d; } QIcon DesignerIcons::icon(IconId icon, Area area) const { return Utils::StyleHelper::getIconFromIconFont(d->mFontName, helperList(icon, area)); } void DesignerIcons::loadIconSettings(const QString &fileName) { if (d->cache.contains(fileName)) { d->icons = *d->cache.object(fileName); return; } QFile designerIconFile(fileName); if (!designerIconFile.open(QFile::ReadOnly)) { qWarning() << Q_FUNC_INFO << __LINE__ << "Can not open file:" << fileName << designerIconFile.errorString(); return; } if (designerIconFile.size() > 100e3) { qWarning() << Q_FUNC_INFO << __LINE__ << "Large File:" << fileName; return; } QJsonParseError parseError; QJsonDocument jsonDoc = QJsonDocument::fromJson(designerIconFile.readAll(), &parseError); if (parseError.error != QJsonParseError::NoError) { qWarning() << Q_FUNC_INFO << __LINE__ << "Json Parse Error - " << parseError.errorString() << "---\t File: " << fileName; return; } if (!jsonDoc.isObject()) { qWarning() << Q_FUNC_INFO << __LINE__ << "Invalid Json Array in file: " << fileName; return; } clearAll(); d->icons = JsonMap::value(jsonDoc.object(), {}); d->cache.insert(fileName, new IconsMap(d->icons), 1); } void DesignerIcons::exportSettings(const QString &fileName) const { QFile outFile(fileName); if (!outFile.open(QFile::WriteOnly)) { qWarning() << Q_FUNC_INFO << __LINE__ << "Can not open file for writing:" << fileName; return; } QJsonDocument jsonDocument; jsonDocument.setObject(JsonMap::json(d->icons)); outFile.write(jsonDocument.toJson()); outFile.close(); } void DesignerIcons::clearAll() { d->icons.clear(); } void DesignerIcons::addIcon(const IconId &iconId, const Area &area, const IconFontHelper &fontHelper) { AreaMap &areaMap = d->icons[iconId]; IconMap &iconMap = areaMap[area]; ModeMap &modeMap = iconMap[static_cast(fontHelper.state())]; modeMap.insert(static_cast(fontHelper.mode()), fontHelper); } void DesignerIcons::addIcon(IconId iconId, Area area, QIcon::Mode mode, QIcon::State state, Theme::Icon themeIcon, Theme::Color color, const QSize &size) { addIcon(iconId, area, {themeIcon, color, size, mode, state}); } void DesignerIcons::addIcon(IconId iconId, Area area, Theme::Icon themeIcon, Theme::Color color, const QSize &size) { addIcon(iconId, area, {themeIcon, color, size}); } QIcon DesignerIcons::rotateIcon(const QIcon &icon, const double °rees) { QIcon result; const QMetaEnum &modeMetaEnum = DesignerIconEnums().metaEnum; const QMetaEnum &stateMetaEnum = DesignerIconEnums().metaEnum; const int maxModeIdx = modeMetaEnum.keyCount(); const int maxStateIdx = stateMetaEnum.keyCount(); for (int modeI = 0; modeI < maxModeIdx; ++modeI) { const QIcon::Mode mode = static_cast(modeMetaEnum.value(modeI)); for (int stateI = 0; stateI < maxStateIdx; ++stateI) { const QIcon::State state = static_cast(stateMetaEnum.value(stateI)); const QList iconSizes = icon.availableSizes(); for (const QSize &size : iconSizes) { QTransform tr; tr.translate(size.width()/2, size.height()/2); tr.rotate(degrees); QPixmap pix = icon.pixmap(size, mode, state).transformed(tr); result.addPixmap(pix, mode, state); } } } return result; } QList DesignerIcons::helperList(const IconId &iconId, const Area &area) const { QList helperList; const IconMap &iconMap = d->icons.value(iconId).value(area); for (const ModeMap &modMap : iconMap) { for (const IconFontHelper &iconFontHelper : modMap) helperList.append(iconFontHelper); } return helperList; }