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