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