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 | |
20 | using namespace KSyntaxHighlighting; |
21 | |
22 | static 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 | |
38 | FormatPrivate *FormatPrivate::detachAndGet(Format &format) |
39 | { |
40 | format.d.detach(); |
41 | return format.d.data(); |
42 | } |
43 | |
44 | TextStyleData FormatPrivate::styleOverride(const Theme &theme) const |
45 | { |
46 | return ThemeData::get(theme)->textStyleOverride(definitionName, attributeName: name); |
47 | } |
48 | |
49 | QColor 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 | |
59 | bool 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 | |
65 | static QExplicitlySharedDataPointer<FormatPrivate> &sharedDefaultPrivate() |
66 | { |
67 | static QExplicitlySharedDataPointer<FormatPrivate> def(new FormatPrivate); |
68 | return def; |
69 | } |
70 | |
71 | Format::Format() |
72 | : d(sharedDefaultPrivate()) |
73 | { |
74 | } |
75 | |
76 | Format::Format(const Format &other) |
77 | : d(other.d) |
78 | { |
79 | } |
80 | |
81 | Format::~Format() |
82 | { |
83 | } |
84 | |
85 | Format &Format::operator=(const Format &other) |
86 | { |
87 | d = other.d; |
88 | return *this; |
89 | } |
90 | |
91 | bool Format::isValid() const |
92 | { |
93 | return !d->name.isEmpty(); |
94 | } |
95 | |
96 | QString Format::name() const |
97 | { |
98 | return d->name; |
99 | } |
100 | |
101 | int Format::id() const |
102 | { |
103 | return d->id; |
104 | } |
105 | |
106 | Theme::TextStyle Format::textStyle() const |
107 | { |
108 | return d->defaultStyle; |
109 | } |
110 | |
111 | bool 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 | |
120 | bool Format::hasTextColor(const Theme &theme) const |
121 | { |
122 | return d->hasColor(theme, styleColor: &TextStyleData::textColor, themeColor: &Theme::textColor); |
123 | } |
124 | |
125 | QColor Format::textColor(const Theme &theme) const |
126 | { |
127 | return d->color(theme, styleColor: &TextStyleData::textColor, themeColor: &Theme::textColor); |
128 | } |
129 | |
130 | QColor Format::selectedTextColor(const Theme &theme) const |
131 | { |
132 | return d->color(theme, styleColor: &TextStyleData::selectedTextColor, themeColor: &Theme::selectedTextColor); |
133 | } |
134 | |
135 | bool Format::hasBackgroundColor(const Theme &theme) const |
136 | { |
137 | return d->hasColor(theme, styleColor: &TextStyleData::backgroundColor, themeColor: &Theme::backgroundColor); |
138 | } |
139 | |
140 | QColor Format::backgroundColor(const Theme &theme) const |
141 | { |
142 | return d->color(theme, styleColor: &TextStyleData::backgroundColor, themeColor: &Theme::backgroundColor); |
143 | } |
144 | |
145 | QColor Format::selectedBackgroundColor(const Theme &theme) const |
146 | { |
147 | return d->color(theme, styleColor: &TextStyleData::selectedBackgroundColor, themeColor: &Theme::selectedBackgroundColor); |
148 | } |
149 | |
150 | bool 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 | |
159 | bool 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 | |
168 | bool 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 | |
177 | bool 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 | |
186 | bool Format::spellCheck() const |
187 | { |
188 | return d->spellCheck; |
189 | } |
190 | |
191 | bool Format::hasBoldOverride() const |
192 | { |
193 | return d->style.hasBold; |
194 | } |
195 | |
196 | bool Format::hasItalicOverride() const |
197 | { |
198 | return d->style.hasItalic; |
199 | } |
200 | |
201 | bool Format::hasUnderlineOverride() const |
202 | { |
203 | return d->style.hasUnderline; |
204 | } |
205 | |
206 | bool Format::hasStrikeThroughOverride() const |
207 | { |
208 | return d->style.hasStrikeThrough; |
209 | } |
210 | |
211 | bool Format::hasTextColorOverride() const |
212 | { |
213 | return d->style.textColor; |
214 | } |
215 | |
216 | bool Format::hasBackgroundColorOverride() const |
217 | { |
218 | return d->style.backgroundColor; |
219 | } |
220 | |
221 | bool Format::hasSelectedTextColorOverride() const |
222 | { |
223 | return d->style.selectedTextColor; |
224 | } |
225 | |
226 | bool Format::hasSelectedBackgroundColorOverride() const |
227 | { |
228 | return d->style.selectedBackgroundColor; |
229 | } |
230 | |
231 | void 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 | |