1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
4
5 SPDX-License-Identifier: MIT
6*/
7
8#include "format.h"
9#include "definition.h"
10#include "definitionref_p.h"
11#include "format_p.h"
12#include "textstyledata_p.h"
13#include "themedata_p.h"
14#include "xml_p.h"
15
16#include <QColor>
17#include <QMetaEnum>
18#include <QXmlStreamReader>
19
20using namespace KSyntaxHighlighting;
21
22static Theme::TextStyle stringToDefaultFormat(QStringView str)
23{
24 if (!str.startsWith(s: QLatin1String("ds"))) {
25 return Theme::Normal;
26 }
27
28 const auto metaEnum = QMetaEnum::fromType<Theme::TextStyle>();
29
30 bool ok = false;
31 const auto value = metaEnum.keyToValue(key: str.mid(pos: 2).toLatin1().constData(), ok: &ok);
32 if (!ok || value < 0) {
33 return Theme::Normal;
34 }
35 return static_cast<Theme::TextStyle>(value);
36}
37
38FormatPrivate *FormatPrivate::detachAndGet(Format &format)
39{
40 format.d.detach();
41 return format.d.data();
42}
43
44TextStyleData FormatPrivate::styleOverride(const Theme &theme) const
45{
46 return ThemeData::get(theme)->textStyleOverride(definitionName, attributeName: name);
47}
48
49QColor FormatPrivate::color(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const
50{
51 const auto overrideStyle = styleOverride(theme);
52 if (overrideStyle.*styleColor) {
53 return QColor::fromRgb(rgb: overrideStyle.*styleColor);
54 }
55 // use QColor::fromRgba for QRgb => QColor conversion to avoid unset colors == black!
56 return QColor::fromRgba(rgba: style.*styleColor ? style.*styleColor : (theme.*themeColor)(defaultStyle));
57}
58
59bool FormatPrivate::hasColor(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const
60{
61 // use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
62 return color(theme, styleColor, themeColor) != QColor::fromRgba(rgba: (theme.*themeColor)(Theme::Normal)) && (style.*styleColor || (theme.*themeColor)(defaultStyle) || styleOverride(theme).*styleColor);
63}
64
65static QExplicitlySharedDataPointer<FormatPrivate> &sharedDefaultPrivate()
66{
67 static QExplicitlySharedDataPointer<FormatPrivate> def(new FormatPrivate);
68 return def;
69}
70
71Format::Format()
72 : d(sharedDefaultPrivate())
73{
74}
75
76Format::Format(const Format &other)
77 : d(other.d)
78{
79}
80
81Format::~Format()
82{
83}
84
85Format &Format::operator=(const Format &other)
86{
87 d = other.d;
88 return *this;
89}
90
91bool Format::isValid() const
92{
93 return !d->name.isEmpty();
94}
95
96QString Format::name() const
97{
98 return d->name;
99}
100
101int Format::id() const
102{
103 return d->id;
104}
105
106Theme::TextStyle Format::textStyle() const
107{
108 return d->defaultStyle;
109}
110
111bool Format::isDefaultTextStyle(const Theme &theme) const
112{
113 // use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
114 return (!hasTextColor(theme)) && (!hasBackgroundColor(theme)) && (selectedTextColor(theme).rgba() == theme.selectedTextColor(style: Theme::Normal))
115 && (selectedBackgroundColor(theme).rgba() == (theme.selectedBackgroundColor(style: Theme::Normal))) && (isBold(theme) == theme.isBold(style: Theme::Normal))
116 && (isItalic(theme) == theme.isItalic(style: Theme::Normal)) && (isUnderline(theme) == theme.isUnderline(style: Theme::Normal))
117 && (isStrikeThrough(theme) == theme.isStrikeThrough(style: Theme::Normal));
118}
119
120bool Format::hasTextColor(const Theme &theme) const
121{
122 return d->hasColor(theme, styleColor: &TextStyleData::textColor, themeColor: &Theme::textColor);
123}
124
125QColor Format::textColor(const Theme &theme) const
126{
127 return d->color(theme, styleColor: &TextStyleData::textColor, themeColor: &Theme::textColor);
128}
129
130QColor Format::selectedTextColor(const Theme &theme) const
131{
132 return d->color(theme, styleColor: &TextStyleData::selectedTextColor, themeColor: &Theme::selectedTextColor);
133}
134
135bool Format::hasBackgroundColor(const Theme &theme) const
136{
137 return d->hasColor(theme, styleColor: &TextStyleData::backgroundColor, themeColor: &Theme::backgroundColor);
138}
139
140QColor Format::backgroundColor(const Theme &theme) const
141{
142 return d->color(theme, styleColor: &TextStyleData::backgroundColor, themeColor: &Theme::backgroundColor);
143}
144
145QColor Format::selectedBackgroundColor(const Theme &theme) const
146{
147 return d->color(theme, styleColor: &TextStyleData::selectedBackgroundColor, themeColor: &Theme::selectedBackgroundColor);
148}
149
150bool Format::isBold(const Theme &theme) const
151{
152 const auto overrideStyle = d->styleOverride(theme);
153 if (overrideStyle.hasBold) {
154 return overrideStyle.bold;
155 }
156 return d->style.hasBold ? d->style.bold : theme.isBold(style: d->defaultStyle);
157}
158
159bool Format::isItalic(const Theme &theme) const
160{
161 const auto overrideStyle = d->styleOverride(theme);
162 if (overrideStyle.hasItalic) {
163 return overrideStyle.italic;
164 }
165 return d->style.hasItalic ? d->style.italic : theme.isItalic(style: d->defaultStyle);
166}
167
168bool Format::isUnderline(const Theme &theme) const
169{
170 const auto overrideStyle = d->styleOverride(theme);
171 if (overrideStyle.hasUnderline) {
172 return overrideStyle.underline;
173 }
174 return d->style.hasUnderline ? d->style.underline : theme.isUnderline(style: d->defaultStyle);
175}
176
177bool Format::isStrikeThrough(const Theme &theme) const
178{
179 const auto overrideStyle = d->styleOverride(theme);
180 if (overrideStyle.hasStrikeThrough) {
181 return overrideStyle.strikeThrough;
182 }
183 return d->style.hasStrikeThrough ? d->style.strikeThrough : theme.isStrikeThrough(style: d->defaultStyle);
184}
185
186bool Format::spellCheck() const
187{
188 return d->spellCheck;
189}
190
191bool Format::hasBoldOverride() const
192{
193 return d->style.hasBold;
194}
195
196bool Format::hasItalicOverride() const
197{
198 return d->style.hasItalic;
199}
200
201bool Format::hasUnderlineOverride() const
202{
203 return d->style.hasUnderline;
204}
205
206bool Format::hasStrikeThroughOverride() const
207{
208 return d->style.hasStrikeThrough;
209}
210
211bool Format::hasTextColorOverride() const
212{
213 return d->style.textColor;
214}
215
216bool Format::hasBackgroundColorOverride() const
217{
218 return d->style.backgroundColor;
219}
220
221bool Format::hasSelectedTextColorOverride() const
222{
223 return d->style.selectedTextColor;
224}
225
226bool Format::hasSelectedBackgroundColorOverride() const
227{
228 return d->style.selectedBackgroundColor;
229}
230
231void FormatPrivate::load(QXmlStreamReader &reader)
232{
233 name = reader.attributes().value(qualifiedName: QLatin1String("name")).toString();
234 defaultStyle = stringToDefaultFormat(str: reader.attributes().value(qualifiedName: QLatin1String("defStyleNum")));
235
236 QStringView attribute = reader.attributes().value(qualifiedName: QLatin1String("color"));
237 if (!attribute.isEmpty()) {
238 style.textColor = QColor(attribute).rgba();
239 }
240
241 attribute = reader.attributes().value(qualifiedName: QLatin1String("selColor"));
242 if (!attribute.isEmpty()) {
243 style.selectedTextColor = QColor(attribute).rgba();
244 }
245
246 attribute = reader.attributes().value(qualifiedName: QLatin1String("backgroundColor"));
247 if (!attribute.isEmpty()) {
248 style.backgroundColor = QColor(attribute).rgba();
249 }
250
251 attribute = reader.attributes().value(qualifiedName: QLatin1String("selBackgroundColor"));
252 if (!attribute.isEmpty()) {
253 style.selectedBackgroundColor = QColor(attribute).rgba();
254 }
255
256 attribute = reader.attributes().value(qualifiedName: QLatin1String("italic"));
257 if (!attribute.isEmpty()) {
258 style.hasItalic = true;
259 style.italic = Xml::attrToBool(str: attribute);
260 }
261
262 attribute = reader.attributes().value(qualifiedName: QLatin1String("bold"));
263 if (!attribute.isEmpty()) {
264 style.hasBold = true;
265 style.bold = Xml::attrToBool(str: attribute);
266 }
267
268 attribute = reader.attributes().value(qualifiedName: QLatin1String("underline"));
269 if (!attribute.isEmpty()) {
270 style.hasUnderline = true;
271 style.underline = Xml::attrToBool(str: attribute);
272 }
273
274 attribute = reader.attributes().value(qualifiedName: QLatin1String("strikeOut"));
275 if (!attribute.isEmpty()) {
276 style.hasStrikeThrough = true;
277 style.strikeThrough = Xml::attrToBool(str: attribute);
278 }
279
280 attribute = reader.attributes().value(qualifiedName: QLatin1String("spellChecking"));
281 if (!attribute.isEmpty()) {
282 spellCheck = Xml::attrToBool(str: attribute);
283 }
284}
285

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