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 }