1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2016 Dominik Haumann <dhaumann@kde.org>
4 SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
5
6 SPDX-License-Identifier: MIT
7*/
8
9#include "ksyntaxhighlighting_logging.h"
10#include "themedata_p.h"
11
12#include <QFile>
13#include <QFileInfo>
14#include <QJsonDocument>
15#include <QJsonObject>
16#include <QJsonValue>
17#include <QMetaEnum>
18
19using namespace KSyntaxHighlighting;
20
21ThemeData::ThemeData()
22{
23 memset(m_editorColors, 0, sizeof(m_editorColors));
24 m_textStyles.resize(QMetaEnum::fromType<Theme::TextStyle>().keyCount());
25}
26
27/**
28 * Convert QJsonValue @p val into a color, if possible. Valid colors are only
29 * in hex format: #aarrggbb. On error, returns 0x00000000.
30 */
31static inline QRgb readColor(const QJsonValue &val)
32{
33 const QRgb unsetColor = 0;
34 if (!val.isString()) {
35 return unsetColor;
36 }
37 const QString str = val.toString();
38 if (str.isEmpty() || str[0] != QLatin1Char('#')) {
39 return unsetColor;
40 }
41 const QColor color(str);
42 return color.isValid() ? color.rgba() : unsetColor;
43}
44
45static inline TextStyleData readThemeData(const QJsonObject &obj)
46{
47 TextStyleData td;
48
49 td.textColor = readColor(obj.value(QLatin1String("text-color")));
50 td.backgroundColor = readColor(obj.value(QLatin1String("background-color")));
51 td.selectedTextColor = readColor(obj.value(QLatin1String("selected-text-color")));
52 td.selectedBackgroundColor = readColor(obj.value(QLatin1String("selected-background-color")));
53
54 auto val = obj.value(QLatin1String("bold"));
55 if (val.isBool()) {
56 td.bold = val.toBool();
57 td.hasBold = true;
58 }
59 val = obj.value(QLatin1String("italic"));
60 if (val.isBool()) {
61 td.italic = val.toBool();
62 td.hasItalic = true;
63 }
64 val = obj.value(QLatin1String("underline"));
65 if (val.isBool()) {
66 td.underline = val.toBool();
67 td.hasUnderline = true;
68 }
69 val = obj.value(QLatin1String("strike-through"));
70 if (val.isBool()) {
71 td.strikeThrough = val.toBool();
72 td.hasStrikeThrough = true;
73 }
74
75 return td;
76}
77
78bool ThemeData::load(const QString &filePath)
79{
80 QFile loadFile(filePath);
81 if (!loadFile.open(QIODevice::ReadOnly)) {
82 return false;
83 }
84 const QByteArray jsonData = loadFile.readAll();
85 // look for metadata object
86 int metaDataStart = jsonData.indexOf("\"metadata\"");
87 int start = jsonData.indexOf('{', metaDataStart);
88 int end = jsonData.indexOf("}", metaDataStart);
89 if (start < 0 || end < 0) {
90 qCWarning(Log) << "Failed to parse theme file" << filePath << ":"
91 << "no metadata object found";
92 return false;
93 }
94
95 QJsonParseError parseError;
96 QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData.mid(start, (end + 1) - start), &parseError);
97 if (parseError.error != QJsonParseError::NoError) {
98 qCWarning(Log) << "Failed to parse theme file" << filePath << ":" << parseError.errorString();
99 return false;
100 }
101
102 m_filePath = filePath;
103
104 // read metadata
105 QJsonObject metadata = jsonDoc.object();
106 m_name = metadata.value(QLatin1String("name")).toString();
107 m_revision = metadata.value(QLatin1String("revision")).toInt();
108 return true;
109}
110
111void ThemeData::loadComplete()
112{
113 if (m_completelyLoaded) {
114 return;
115 }
116 m_completelyLoaded = true;
117
118 QFile loadFile(m_filePath);
119 if (!loadFile.open(QIODevice::ReadOnly)) {
120 return;
121 }
122 const QByteArray jsonData = loadFile.readAll();
123
124 QJsonParseError parseError;
125 QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
126 if (parseError.error != QJsonParseError::NoError) {
127 qCWarning(Log) << "Failed to parse theme file" << m_filePath << ":" << parseError.errorString();
128 return;
129 }
130
131 QJsonObject obj = jsonDoc.object();
132 // read text styles
133 const auto metaEnumStyle = QMetaEnum::fromType<Theme::TextStyle>();
134 const QJsonObject textStyles = obj.value(QLatin1String("text-styles")).toObject();
135 for (int i = 0; i < metaEnumStyle.keyCount(); ++i) {
136 Q_ASSERT(i == metaEnumStyle.value(i));
137 m_textStyles[i] = readThemeData(textStyles.value(QLatin1String(metaEnumStyle.key(i))).toObject());
138 }
139
140 // read editor colors
141 const auto metaEnumColor = QMetaEnum::fromType<Theme::EditorColorRole>();
142 const QJsonObject editorColors = obj.value(QLatin1String("editor-colors")).toObject();
143 for (int i = 0; i < metaEnumColor.keyCount(); ++i) {
144 Q_ASSERT(i == metaEnumColor.value(i));
145 m_editorColors[i] = readColor(editorColors.value(QLatin1String(metaEnumColor.key(i))));
146 }
147
148 // if we have no new key around for Theme::BackgroundColor => use old variants to be compatible
149 if (!editorColors.contains(QLatin1String(metaEnumColor.key(Theme::BackgroundColor)))) {
150 m_editorColors[Theme::BackgroundColor] = readColor(editorColors.value(QLatin1String("background-color")));
151 m_editorColors[Theme::TextSelection] = readColor(editorColors.value(QLatin1String("selection")));
152 m_editorColors[Theme::CurrentLine] = readColor(editorColors.value(QLatin1String("current-line")));
153 m_editorColors[Theme::SearchHighlight] = readColor(editorColors.value(QLatin1String("search-highlight")));
154 m_editorColors[Theme::ReplaceHighlight] = readColor(editorColors.value(QLatin1String("replace-highlight")));
155 m_editorColors[Theme::BracketMatching] = readColor(editorColors.value(QLatin1String("bracket-matching")));
156 m_editorColors[Theme::TabMarker] = readColor(editorColors.value(QLatin1String("tab-marker")));
157 m_editorColors[Theme::SpellChecking] = readColor(editorColors.value(QLatin1String("spell-checking")));
158 m_editorColors[Theme::IndentationLine] = readColor(editorColors.value(QLatin1String("indentation-line")));
159 m_editorColors[Theme::IconBorder] = readColor(editorColors.value(QLatin1String("icon-border")));
160 m_editorColors[Theme::CodeFolding] = readColor(editorColors.value(QLatin1String("code-folding")));
161 m_editorColors[Theme::LineNumbers] = readColor(editorColors.value(QLatin1String("line-numbers")));
162 m_editorColors[Theme::CurrentLineNumber] = readColor(editorColors.value(QLatin1String("current-line-number")));
163 m_editorColors[Theme::WordWrapMarker] = readColor(editorColors.value(QLatin1String("word-wrap-marker")));
164 m_editorColors[Theme::ModifiedLines] = readColor(editorColors.value(QLatin1String("modified-lines")));
165 m_editorColors[Theme::SavedLines] = readColor(editorColors.value(QLatin1String("saved-lines")));
166 m_editorColors[Theme::Separator] = readColor(editorColors.value(QLatin1String("separator")));
167 m_editorColors[Theme::MarkBookmark] = readColor(editorColors.value(QLatin1String("mark-bookmark")));
168 m_editorColors[Theme::MarkBreakpointActive] = readColor(editorColors.value(QLatin1String("mark-breakpoint-active")));
169 m_editorColors[Theme::MarkBreakpointReached] = readColor(editorColors.value(QLatin1String("mark-breakpoint-reached")));
170 m_editorColors[Theme::MarkBreakpointDisabled] = readColor(editorColors.value(QLatin1String("mark-breakpoint-disabled")));
171 m_editorColors[Theme::MarkExecution] = readColor(editorColors.value(QLatin1String("mark-execution")));
172 m_editorColors[Theme::MarkWarning] = readColor(editorColors.value(QLatin1String("mark-warning")));
173 m_editorColors[Theme::MarkError] = readColor(editorColors.value(QLatin1String("mark-error")));
174 m_editorColors[Theme::TemplateBackground] = readColor(editorColors.value(QLatin1String("template-background")));
175 m_editorColors[Theme::TemplatePlaceholder] = readColor(editorColors.value(QLatin1String("template-placeholder")));
176 m_editorColors[Theme::TemplateFocusedPlaceholder] = readColor(editorColors.value(QLatin1String("template-focused-placeholder")));
177 m_editorColors[Theme::TemplateReadOnlyPlaceholder] = readColor(editorColors.value(QLatin1String("template-read-only-placeholder")));
178 }
179
180 // read per-definition style overrides
181 const auto customStyles = obj.value(QLatin1String("custom-styles")).toObject();
182 for (auto it = customStyles.begin(); it != customStyles.end(); ++it) {
183 const auto obj = it.value().toObject();
184 auto &overrideStyle = m_textStyleOverrides[it.key()];
185 for (auto it2 = obj.begin(); it2 != obj.end(); ++it2) {
186 overrideStyle.insert(it2.key(), readThemeData(it2.value().toObject()));
187 }
188 }
189
190 return;
191}
192
193QString ThemeData::name() const
194{
195 return m_name;
196}
197
198int ThemeData::revision() const
199{
200 return m_revision;
201}
202
203bool ThemeData::isReadOnly() const
204{
205 return !QFileInfo(m_filePath).isWritable();
206}
207
208QString ThemeData::filePath() const
209{
210 return m_filePath;
211}
212
213TextStyleData ThemeData::textStyle(Theme::TextStyle style) const
214{
215 if (!m_completelyLoaded) {
216 const_cast<ThemeData *>(this)->loadComplete();
217 }
218 return m_textStyles[style];
219}
220
221QRgb ThemeData::textColor(Theme::TextStyle style) const
222{
223 return textStyle(style).textColor;
224}
225
226QRgb ThemeData::selectedTextColor(Theme::TextStyle style) const
227{
228 return textStyle(style).selectedTextColor;
229}
230
231QRgb ThemeData::backgroundColor(Theme::TextStyle style) const
232{
233 return textStyle(style).backgroundColor;
234}
235
236QRgb ThemeData::selectedBackgroundColor(Theme::TextStyle style) const
237{
238 return textStyle(style).selectedBackgroundColor;
239}
240
241bool ThemeData::isBold(Theme::TextStyle style) const
242{
243 return textStyle(style).bold;
244}
245
246bool ThemeData::isItalic(Theme::TextStyle style) const
247{
248 return textStyle(style).italic;
249}
250
251bool ThemeData::isUnderline(Theme::TextStyle style) const
252{
253 return textStyle(style).underline;
254}
255
256bool ThemeData::isStrikeThrough(Theme::TextStyle style) const
257{
258 return textStyle(style).strikeThrough;
259}
260
261QRgb ThemeData::editorColor(Theme::EditorColorRole role) const
262{
263 if (!m_completelyLoaded) {
264 const_cast<ThemeData *>(this)->loadComplete();
265 }
266 Q_ASSERT(static_cast<int>(role) >= 0 && static_cast<int>(role) <= static_cast<int>(Theme::TemplateReadOnlyPlaceholder));
267 return m_editorColors[role];
268}
269
270TextStyleData ThemeData::textStyleOverride(const QString &definitionName, const QString &attributeName) const
271{
272 if (!m_completelyLoaded) {
273 const_cast<ThemeData *>(this)->loadComplete();
274 }
275 auto it = m_textStyleOverrides.find(definitionName);
276 if (it != m_textStyleOverrides.end()) {
277 return it->value(attributeName);
278 }
279 return TextStyleData();
280}
281

source code of syntax-highlighting/src/lib/themedata.cpp