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 }