File indexing completed on 2024-05-12 15:50:07
0001 /* 0002 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> 0003 SPDX-FileCopyrightText: 2016 Dominik Haumann <dhaumann@kde.org> 0004 SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com> 0005 0006 SPDX-License-Identifier: MIT 0007 */ 0008 0009 #include "ksyntaxhighlighting_logging.h" 0010 #include "themedata_p.h" 0011 0012 #include <QFile> 0013 #include <QFileInfo> 0014 #include <QJsonDocument> 0015 #include <QJsonObject> 0016 #include <QJsonValue> 0017 #include <QMetaEnum> 0018 0019 using namespace KSyntaxHighlighting; 0020 0021 ThemeData *ThemeData::get(const Theme &theme) 0022 { 0023 return theme.m_data.data(); 0024 } 0025 0026 ThemeData::ThemeData() 0027 { 0028 memset(m_editorColors, 0, sizeof(m_editorColors)); 0029 m_textStyles.resize(QMetaEnum::fromType<Theme::TextStyle>().keyCount()); 0030 } 0031 0032 /** 0033 * Convert QJsonValue @p val into a color, if possible. Valid colors are only 0034 * in hex format: #aarrggbb. On error, returns 0x00000000. 0035 */ 0036 static inline QRgb readColor(const QJsonValue &val) 0037 { 0038 const QRgb unsetColor = 0; 0039 if (!val.isString()) { 0040 return unsetColor; 0041 } 0042 const QString str = val.toString(); 0043 if (str.isEmpty() || str[0] != QLatin1Char('#')) { 0044 return unsetColor; 0045 } 0046 const QColor color(str); 0047 return color.isValid() ? color.rgba() : unsetColor; 0048 } 0049 0050 static inline TextStyleData readThemeData(const QJsonObject &obj) 0051 { 0052 TextStyleData td; 0053 0054 td.textColor = readColor(obj.value(QLatin1String("text-color"))); 0055 td.backgroundColor = readColor(obj.value(QLatin1String("background-color"))); 0056 td.selectedTextColor = readColor(obj.value(QLatin1String("selected-text-color"))); 0057 td.selectedBackgroundColor = readColor(obj.value(QLatin1String("selected-background-color"))); 0058 0059 auto val = obj.value(QLatin1String("bold")); 0060 if (val.isBool()) { 0061 td.bold = val.toBool(); 0062 td.hasBold = true; 0063 } 0064 val = obj.value(QLatin1String("italic")); 0065 if (val.isBool()) { 0066 td.italic = val.toBool(); 0067 td.hasItalic = true; 0068 } 0069 val = obj.value(QLatin1String("underline")); 0070 if (val.isBool()) { 0071 td.underline = val.toBool(); 0072 td.hasUnderline = true; 0073 } 0074 val = obj.value(QLatin1String("strike-through")); 0075 if (val.isBool()) { 0076 td.strikeThrough = val.toBool(); 0077 td.hasStrikeThrough = true; 0078 } 0079 0080 return td; 0081 } 0082 0083 bool ThemeData::load(const QString &filePath) 0084 { 0085 QFile loadFile(filePath); 0086 if (!loadFile.open(QIODevice::ReadOnly)) { 0087 return false; 0088 } 0089 const QByteArray jsonData = loadFile.readAll(); 0090 0091 QJsonParseError parseError; 0092 QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); 0093 if (parseError.error != QJsonParseError::NoError) { 0094 qCWarning(Log) << "Failed to parse theme file" << filePath << ":" << parseError.errorString(); 0095 return false; 0096 } 0097 0098 m_filePath = filePath; 0099 0100 QJsonObject obj = jsonDoc.object(); 0101 0102 // read metadata 0103 const QJsonObject metadata = obj.value(QLatin1String("metadata")).toObject(); 0104 m_name = metadata.value(QLatin1String("name")).toString(); 0105 m_revision = metadata.value(QLatin1String("revision")).toInt(); 0106 0107 // read text styles 0108 const auto metaEnumStyle = QMetaEnum::fromType<Theme::TextStyle>(); 0109 const QJsonObject textStyles = obj.value(QLatin1String("text-styles")).toObject(); 0110 for (int i = 0; i < metaEnumStyle.keyCount(); ++i) { 0111 Q_ASSERT(i == metaEnumStyle.value(i)); 0112 m_textStyles[i] = readThemeData(textStyles.value(QLatin1String(metaEnumStyle.key(i))).toObject()); 0113 } 0114 0115 // read editor colors 0116 const auto metaEnumColor = QMetaEnum::fromType<Theme::EditorColorRole>(); 0117 const QJsonObject editorColors = obj.value(QLatin1String("editor-colors")).toObject(); 0118 for (int i = 0; i < metaEnumColor.keyCount(); ++i) { 0119 Q_ASSERT(i == metaEnumColor.value(i)); 0120 m_editorColors[i] = readColor(editorColors.value(QLatin1String(metaEnumColor.key(i)))); 0121 } 0122 0123 // if we have no new key around for Theme::BackgroundColor => use old variants to be compatible 0124 if (!editorColors.contains(QLatin1String(metaEnumColor.key(Theme::BackgroundColor)))) { 0125 m_editorColors[Theme::BackgroundColor] = readColor(editorColors.value(QLatin1String("background-color"))); 0126 m_editorColors[Theme::TextSelection] = readColor(editorColors.value(QLatin1String("selection"))); 0127 m_editorColors[Theme::CurrentLine] = readColor(editorColors.value(QLatin1String("current-line"))); 0128 m_editorColors[Theme::SearchHighlight] = readColor(editorColors.value(QLatin1String("search-highlight"))); 0129 m_editorColors[Theme::ReplaceHighlight] = readColor(editorColors.value(QLatin1String("replace-highlight"))); 0130 m_editorColors[Theme::BracketMatching] = readColor(editorColors.value(QLatin1String("bracket-matching"))); 0131 m_editorColors[Theme::TabMarker] = readColor(editorColors.value(QLatin1String("tab-marker"))); 0132 m_editorColors[Theme::SpellChecking] = readColor(editorColors.value(QLatin1String("spell-checking"))); 0133 m_editorColors[Theme::IndentationLine] = readColor(editorColors.value(QLatin1String("indentation-line"))); 0134 m_editorColors[Theme::IconBorder] = readColor(editorColors.value(QLatin1String("icon-border"))); 0135 m_editorColors[Theme::CodeFolding] = readColor(editorColors.value(QLatin1String("code-folding"))); 0136 m_editorColors[Theme::LineNumbers] = readColor(editorColors.value(QLatin1String("line-numbers"))); 0137 m_editorColors[Theme::CurrentLineNumber] = readColor(editorColors.value(QLatin1String("current-line-number"))); 0138 m_editorColors[Theme::WordWrapMarker] = readColor(editorColors.value(QLatin1String("word-wrap-marker"))); 0139 m_editorColors[Theme::ModifiedLines] = readColor(editorColors.value(QLatin1String("modified-lines"))); 0140 m_editorColors[Theme::SavedLines] = readColor(editorColors.value(QLatin1String("saved-lines"))); 0141 m_editorColors[Theme::Separator] = readColor(editorColors.value(QLatin1String("separator"))); 0142 m_editorColors[Theme::MarkBookmark] = readColor(editorColors.value(QLatin1String("mark-bookmark"))); 0143 m_editorColors[Theme::MarkBreakpointActive] = readColor(editorColors.value(QLatin1String("mark-breakpoint-active"))); 0144 m_editorColors[Theme::MarkBreakpointReached] = readColor(editorColors.value(QLatin1String("mark-breakpoint-reached"))); 0145 m_editorColors[Theme::MarkBreakpointDisabled] = readColor(editorColors.value(QLatin1String("mark-breakpoint-disabled"))); 0146 m_editorColors[Theme::MarkExecution] = readColor(editorColors.value(QLatin1String("mark-execution"))); 0147 m_editorColors[Theme::MarkWarning] = readColor(editorColors.value(QLatin1String("mark-warning"))); 0148 m_editorColors[Theme::MarkError] = readColor(editorColors.value(QLatin1String("mark-error"))); 0149 m_editorColors[Theme::TemplateBackground] = readColor(editorColors.value(QLatin1String("template-background"))); 0150 m_editorColors[Theme::TemplatePlaceholder] = readColor(editorColors.value(QLatin1String("template-placeholder"))); 0151 m_editorColors[Theme::TemplateFocusedPlaceholder] = readColor(editorColors.value(QLatin1String("template-focused-placeholder"))); 0152 m_editorColors[Theme::TemplateReadOnlyPlaceholder] = readColor(editorColors.value(QLatin1String("template-read-only-placeholder"))); 0153 } 0154 0155 // read per-definition style overrides 0156 const auto customStyles = obj.value(QLatin1String("custom-styles")).toObject(); 0157 for (auto it = customStyles.begin(); it != customStyles.end(); ++it) { 0158 const auto obj = it.value().toObject(); 0159 auto &overrideStyle = m_textStyleOverrides[it.key()]; 0160 for (auto it2 = obj.begin(); it2 != obj.end(); ++it2) { 0161 overrideStyle.insert(it2.key(), readThemeData(it2.value().toObject())); 0162 } 0163 } 0164 0165 return true; 0166 } 0167 0168 QString ThemeData::name() const 0169 { 0170 return m_name; 0171 } 0172 0173 int ThemeData::revision() const 0174 { 0175 return m_revision; 0176 } 0177 0178 bool ThemeData::isReadOnly() const 0179 { 0180 return !QFileInfo(m_filePath).isWritable(); 0181 } 0182 0183 QString ThemeData::filePath() const 0184 { 0185 return m_filePath; 0186 } 0187 0188 TextStyleData ThemeData::textStyle(Theme::TextStyle style) const 0189 { 0190 return m_textStyles[style]; 0191 } 0192 0193 QRgb ThemeData::textColor(Theme::TextStyle style) const 0194 { 0195 return textStyle(style).textColor; 0196 } 0197 0198 QRgb ThemeData::selectedTextColor(Theme::TextStyle style) const 0199 { 0200 return textStyle(style).selectedTextColor; 0201 } 0202 0203 QRgb ThemeData::backgroundColor(Theme::TextStyle style) const 0204 { 0205 return textStyle(style).backgroundColor; 0206 } 0207 0208 QRgb ThemeData::selectedBackgroundColor(Theme::TextStyle style) const 0209 { 0210 return textStyle(style).selectedBackgroundColor; 0211 } 0212 0213 bool ThemeData::isBold(Theme::TextStyle style) const 0214 { 0215 return textStyle(style).bold; 0216 } 0217 0218 bool ThemeData::isItalic(Theme::TextStyle style) const 0219 { 0220 return textStyle(style).italic; 0221 } 0222 0223 bool ThemeData::isUnderline(Theme::TextStyle style) const 0224 { 0225 return textStyle(style).underline; 0226 } 0227 0228 bool ThemeData::isStrikeThrough(Theme::TextStyle style) const 0229 { 0230 return textStyle(style).strikeThrough; 0231 } 0232 0233 QRgb ThemeData::editorColor(Theme::EditorColorRole role) const 0234 { 0235 Q_ASSERT(static_cast<int>(role) >= 0 && static_cast<int>(role) <= static_cast<int>(Theme::TemplateReadOnlyPlaceholder)); 0236 return m_editorColors[role]; 0237 } 0238 0239 TextStyleData ThemeData::textStyleOverride(const QString &definitionName, const QString &attributeName) const 0240 { 0241 auto it = m_textStyleOverrides.find(definitionName); 0242 if (it != m_textStyleOverrides.end()) { 0243 return it->value(attributeName); 0244 } 0245 return TextStyleData(); 0246 }