1 | /* |
2 | SPDX-FileCopyrightText: 2007, 2008 Matthew Woehlke <mw_triad@users.sourceforge.net> |
3 | SPDX-FileCopyrightText: 2001-2003 Christoph Cullmann <cullmann@kde.org> |
4 | SPDX-FileCopyrightText: 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk> |
5 | SPDX-FileCopyrightText: 2012-2018 Dominik Haumann <dhaumann@kde.org> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-or-later |
8 | */ |
9 | |
10 | // BEGIN Includes |
11 | #include "katethemeconfig.h" |
12 | |
13 | #include "katecolortreewidget.h" |
14 | #include "kateconfig.h" |
15 | #include "katedocument.h" |
16 | #include "kateglobal.h" |
17 | #include "katehighlight.h" |
18 | #include "katestyletreewidget.h" |
19 | #include "katesyntaxmanager.h" |
20 | #include "kateview.h" |
21 | |
22 | #include <KLocalizedString> |
23 | #include <KMessageBox> |
24 | #include <KMessageWidget> |
25 | |
26 | #include <QComboBox> |
27 | #include <QFileDialog> |
28 | #include <QGridLayout> |
29 | #include <QInputDialog> |
30 | #include <QJsonObject> |
31 | #include <QLabel> |
32 | #include <QMetaEnum> |
33 | #include <QPushButton> |
34 | #include <QShowEvent> |
35 | #include <QTabWidget> |
36 | |
37 | // END |
38 | |
39 | /** |
40 | * Return the translated name of default style @p n. |
41 | */ |
42 | static inline QString defaultStyleName(KSyntaxHighlighting::Theme::TextStyle style) |
43 | { |
44 | using namespace KTextEditor; |
45 | switch (style) { |
46 | case KSyntaxHighlighting::Theme::TextStyle::Normal: |
47 | return i18nc("@item:intable Text context" , "Normal" ); |
48 | case KSyntaxHighlighting::Theme::TextStyle::Keyword: |
49 | return i18nc("@item:intable Text context" , "Keyword" ); |
50 | case KSyntaxHighlighting::Theme::TextStyle::Function: |
51 | return i18nc("@item:intable Text context" , "Function" ); |
52 | case KSyntaxHighlighting::Theme::TextStyle::Variable: |
53 | return i18nc("@item:intable Text context" , "Variable" ); |
54 | case KSyntaxHighlighting::Theme::TextStyle::ControlFlow: |
55 | return i18nc("@item:intable Text context" , "Control Flow" ); |
56 | case KSyntaxHighlighting::Theme::TextStyle::Operator: |
57 | return i18nc("@item:intable Text context" , "Operator" ); |
58 | case KSyntaxHighlighting::Theme::TextStyle::BuiltIn: |
59 | return i18nc("@item:intable Text context" , "Built-in" ); |
60 | case KSyntaxHighlighting::Theme::TextStyle::Extension: |
61 | return i18nc("@item:intable Text context" , "Extension" ); |
62 | case KSyntaxHighlighting::Theme::TextStyle::Preprocessor: |
63 | return i18nc("@item:intable Text context" , "Preprocessor" ); |
64 | case KSyntaxHighlighting::Theme::TextStyle::Attribute: |
65 | return i18nc("@item:intable Text context" , "Attribute" ); |
66 | |
67 | case KSyntaxHighlighting::Theme::TextStyle::Char: |
68 | return i18nc("@item:intable Text context" , "Character" ); |
69 | case KSyntaxHighlighting::Theme::TextStyle::SpecialChar: |
70 | return i18nc("@item:intable Text context" , "Special Character" ); |
71 | case KSyntaxHighlighting::Theme::TextStyle::String: |
72 | return i18nc("@item:intable Text context" , "String" ); |
73 | case KSyntaxHighlighting::Theme::TextStyle::VerbatimString: |
74 | return i18nc("@item:intable Text context" , "Verbatim String" ); |
75 | case KSyntaxHighlighting::Theme::TextStyle::SpecialString: |
76 | return i18nc("@item:intable Text context" , "Special String" ); |
77 | case KSyntaxHighlighting::Theme::TextStyle::Import: |
78 | return i18nc("@item:intable Text context" , "Imports, Modules, Includes" ); |
79 | |
80 | case KSyntaxHighlighting::Theme::TextStyle::DataType: |
81 | return i18nc("@item:intable Text context" , "Data Type" ); |
82 | case KSyntaxHighlighting::Theme::TextStyle::DecVal: |
83 | return i18nc("@item:intable Text context" , "Decimal/Value" ); |
84 | case KSyntaxHighlighting::Theme::TextStyle::BaseN: |
85 | return i18nc("@item:intable Text context" , "Base-N Integer" ); |
86 | case KSyntaxHighlighting::Theme::TextStyle::Float: |
87 | return i18nc("@item:intable Text context" , "Floating Point" ); |
88 | case KSyntaxHighlighting::Theme::TextStyle::Constant: |
89 | return i18nc("@item:intable Text context" , "Constant" ); |
90 | |
91 | case KSyntaxHighlighting::Theme::TextStyle::Comment: |
92 | return i18nc("@item:intable Text context" , "Comment" ); |
93 | case KSyntaxHighlighting::Theme::TextStyle::Documentation: |
94 | return i18nc("@item:intable Text context" , "Documentation" ); |
95 | case KSyntaxHighlighting::Theme::TextStyle::Annotation: |
96 | return i18nc("@item:intable Text context" , "Annotation" ); |
97 | case KSyntaxHighlighting::Theme::TextStyle::CommentVar: |
98 | return i18nc("@item:intable Text context" , "Comment Variable" ); |
99 | case KSyntaxHighlighting::Theme::TextStyle::RegionMarker: |
100 | // this next one is for denoting the beginning/end of a user defined folding region |
101 | return i18nc("@item:intable Text context" , "Region Marker" ); |
102 | case KSyntaxHighlighting::Theme::TextStyle::Information: |
103 | return i18nc("@item:intable Text context" , "Information" ); |
104 | case KSyntaxHighlighting::Theme::TextStyle::Warning: |
105 | return i18nc("@item:intable Text context" , "Warning" ); |
106 | case KSyntaxHighlighting::Theme::TextStyle::Alert: |
107 | return i18nc("@item:intable Text context" , "Alert" ); |
108 | |
109 | case KSyntaxHighlighting::Theme::TextStyle::Others: |
110 | return i18nc("@item:intable Text context" , "Others" ); |
111 | case KSyntaxHighlighting::Theme::TextStyle::Error: |
112 | // this one is for marking invalid input |
113 | return i18nc("@item:intable Text context" , "Error" ); |
114 | }; |
115 | Q_UNREACHABLE(); |
116 | } |
117 | |
118 | /** |
119 | * Helper to get json object for given valid theme. |
120 | * @param theme theme to get json object for |
121 | * @return json object of theme |
122 | */ |
123 | static QJsonObject jsonForTheme(const KSyntaxHighlighting::Theme &theme) |
124 | { |
125 | // load json content, shall work, as the theme as valid, but still abort on errors |
126 | QFile loadFile(theme.filePath()); |
127 | if (!loadFile.open(flags: QIODevice::ReadOnly)) { |
128 | return QJsonObject(); |
129 | } |
130 | const QByteArray jsonData = loadFile.readAll(); |
131 | QJsonParseError parseError; |
132 | QJsonDocument jsonDoc = QJsonDocument::fromJson(json: jsonData, error: &parseError); |
133 | if (parseError.error != QJsonParseError::NoError) { |
134 | return QJsonObject(); |
135 | } |
136 | return jsonDoc.object(); |
137 | } |
138 | |
139 | /** |
140 | * Helper to write json data to given file path. |
141 | * After the function returns, stuff is flushed to disk for sure. |
142 | * @param json json object with theme data |
143 | * @param themeFileName file name to write to |
144 | * @return did writing succeed? |
145 | */ |
146 | static bool writeJson(const QJsonObject &json, const QString &themeFileName) |
147 | { |
148 | QFile saveFile(themeFileName); |
149 | if (!saveFile.open(flags: QIODevice::WriteOnly)) { |
150 | return false; |
151 | } |
152 | saveFile.write(data: QJsonDocument(json).toJson()); |
153 | return true; |
154 | } |
155 | |
156 | // BEGIN KateThemeConfigColorTab -- 'Colors' tab |
157 | KateThemeConfigColorTab::KateThemeConfigColorTab() |
158 | { |
159 | QGridLayout *l = new QGridLayout(this); |
160 | |
161 | ui = new KateColorTreeWidget(this); |
162 | QPushButton *btnUseColorScheme = new QPushButton(i18n("Use Default Colors" ), this); |
163 | |
164 | l->addWidget(ui, row: 0, column: 0, rowSpan: 1, columnSpan: 2); |
165 | l->addWidget(btnUseColorScheme, row: 1, column: 1); |
166 | |
167 | l->setColumnStretch(column: 0, stretch: 1); |
168 | l->setColumnStretch(column: 1, stretch: 0); |
169 | |
170 | connect(sender: btnUseColorScheme, signal: &QPushButton::clicked, context: ui, slot: &KateColorTreeWidget::selectDefaults); |
171 | connect(sender: ui, signal: &KateColorTreeWidget::changed, context: this, slot: &KateThemeConfigColorTab::changed); |
172 | } |
173 | |
174 | static QList<KateColorItem> colorItemList(const KSyntaxHighlighting::Theme &theme) |
175 | { |
176 | QList<KateColorItem> items; |
177 | |
178 | // |
179 | // editor background colors |
180 | // |
181 | KateColorItem ci(KSyntaxHighlighting::Theme::BackgroundColor); |
182 | ci.category = i18n("Editor Background Colors" ); |
183 | |
184 | ci.name = i18n("Text Area" ); |
185 | ci.key = QStringLiteral("Color Background" ); |
186 | ci.whatsThis = i18n("<p>Sets the background color of the editing area.</p>" ); |
187 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
188 | items.append(t: ci); |
189 | |
190 | ci.role = KSyntaxHighlighting::Theme::TextSelection; |
191 | ci.name = i18n("Selected Text" ); |
192 | ci.key = QStringLiteral("Color Selection" ); |
193 | ci.whatsThis = i18n( |
194 | "<p>Sets the background color of the selection.</p><p>To set the text color for selected text, use the "<b>Configure Highlighting</b>" " |
195 | "dialog.</p>" ); |
196 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
197 | items.append(t: ci); |
198 | |
199 | ci.role = KSyntaxHighlighting::Theme::CurrentLine; |
200 | ci.name = i18n("Current Line" ); |
201 | ci.key = QStringLiteral("Color Highlighted Line" ); |
202 | ci.whatsThis = i18n("<p>Sets the background color of the currently active line, which means the line where your cursor is positioned.</p>" ); |
203 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
204 | items.append(t: ci); |
205 | |
206 | ci.role = KSyntaxHighlighting::Theme::SearchHighlight; |
207 | ci.name = i18n("Search Highlight" ); |
208 | ci.key = QStringLiteral("Color Search Highlight" ); |
209 | ci.whatsThis = i18n("<p>Sets the background color of search results.</p>" ); |
210 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
211 | items.append(t: ci); |
212 | |
213 | ci.role = KSyntaxHighlighting::Theme::ReplaceHighlight; |
214 | ci.name = i18n("Replace Highlight" ); |
215 | ci.key = QStringLiteral("Color Replace Highlight" ); |
216 | ci.whatsThis = i18n("<p>Sets the background color of replaced text.</p>" ); |
217 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
218 | items.append(t: ci); |
219 | |
220 | // |
221 | // icon border |
222 | // |
223 | ci.category = i18n("Icon Border" ); |
224 | |
225 | ci.role = KSyntaxHighlighting::Theme::IconBorder; |
226 | ci.name = i18n("Background Area" ); |
227 | ci.key = QStringLiteral("Color Icon Bar" ); |
228 | ci.whatsThis = i18n("<p>Sets the background color of the icon border.</p>" ); |
229 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
230 | items.append(t: ci); |
231 | |
232 | ci.role = KSyntaxHighlighting::Theme::LineNumbers; |
233 | ci.name = i18n("Line Numbers" ); |
234 | ci.key = QStringLiteral("Color Line Number" ); |
235 | ci.whatsThis = i18n("<p>This color will be used to draw the line numbers (if enabled).</p>" ); |
236 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
237 | items.append(t: ci); |
238 | |
239 | ci.role = KSyntaxHighlighting::Theme::CurrentLineNumber; |
240 | ci.name = i18n("Current Line Number" ); |
241 | ci.key = QStringLiteral("Color Current Line Number" ); |
242 | ci.whatsThis = i18n("<p>This color will be used to draw the number of the current line (if enabled).</p>" ); |
243 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
244 | items.append(t: ci); |
245 | |
246 | ci.role = KSyntaxHighlighting::Theme::Separator; |
247 | ci.name = i18n("Separator" ); |
248 | ci.key = QStringLiteral("Color Separator" ); |
249 | ci.whatsThis = i18n("<p>This color will be used to draw the line between line numbers and the icon borders, if both are enabled.</p>" ); |
250 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
251 | items.append(t: ci); |
252 | |
253 | ci.role = KSyntaxHighlighting::Theme::WordWrapMarker; |
254 | ci.name = i18n("Word Wrap Marker" ); |
255 | ci.key = QStringLiteral("Color Word Wrap Marker" ); |
256 | ci.whatsThis = i18n( |
257 | "<p>Sets the color of Word Wrap-related markers:</p><dl><dt>Static Word Wrap</dt><dd>A vertical line which shows the column where text is going to be " |
258 | "wrapped</dd><dt>Dynamic Word Wrap</dt><dd>An arrow shown to the left of " |
259 | "visually-wrapped lines</dd></dl>" ); |
260 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
261 | items.append(t: ci); |
262 | |
263 | ci.role = KSyntaxHighlighting::Theme::CodeFolding; |
264 | ci.name = i18n("Code Folding" ); |
265 | ci.key = QStringLiteral("Color Code Folding" ); |
266 | ci.whatsThis = i18n("<p>Sets the color of the code folding bar.</p>" ); |
267 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
268 | items.append(t: ci); |
269 | |
270 | ci.role = KSyntaxHighlighting::Theme::ModifiedLines; |
271 | ci.name = i18n("Modified Lines" ); |
272 | ci.key = QStringLiteral("Color Modified Lines" ); |
273 | ci.whatsThis = i18n("<p>Sets the color of the line modification marker for modified lines.</p>" ); |
274 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
275 | items.append(t: ci); |
276 | |
277 | ci.role = KSyntaxHighlighting::Theme::SavedLines; |
278 | ci.name = i18n("Saved Lines" ); |
279 | ci.key = QStringLiteral("Color Saved Lines" ); |
280 | ci.whatsThis = i18n("<p>Sets the color of the line modification marker for saved lines.</p>" ); |
281 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
282 | items.append(t: ci); |
283 | |
284 | // |
285 | // text decorations |
286 | // |
287 | ci.category = i18n("Text Decorations" ); |
288 | |
289 | ci.role = KSyntaxHighlighting::Theme::SpellChecking; |
290 | ci.name = i18n("Spelling Mistake Line" ); |
291 | ci.key = QStringLiteral("Color Spelling Mistake Line" ); |
292 | ci.whatsThis = i18n("<p>Sets the color of the line that is used to indicate spelling mistakes.</p>" ); |
293 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
294 | items.append(t: ci); |
295 | |
296 | ci.role = KSyntaxHighlighting::Theme::TabMarker; |
297 | ci.name = i18n("Tab and Space Markers" ); |
298 | ci.key = QStringLiteral("Color Tab Marker" ); |
299 | ci.whatsThis = i18n("<p>Sets the color of the tabulator marks.</p>" ); |
300 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
301 | items.append(t: ci); |
302 | |
303 | ci.role = KSyntaxHighlighting::Theme::IndentationLine; |
304 | ci.name = i18n("Indentation Line" ); |
305 | ci.key = QStringLiteral("Color Indentation Line" ); |
306 | ci.whatsThis = i18n("<p>Sets the color of the vertical indentation lines.</p>" ); |
307 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
308 | items.append(t: ci); |
309 | |
310 | ci.role = KSyntaxHighlighting::Theme::BracketMatching; |
311 | ci.name = i18n("Bracket Highlight" ); |
312 | ci.key = QStringLiteral("Color Highlighted Bracket" ); |
313 | ci.whatsThis = i18n( |
314 | "<p>Sets the bracket matching color. This means, if you place the cursor e.g. at a <b>(</b>, the matching <b>)</b> will be highlighted with this " |
315 | "color.</p>" ); |
316 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
317 | items.append(t: ci); |
318 | |
319 | // |
320 | // marker colors |
321 | // |
322 | ci.category = i18n("Marker Colors" ); |
323 | |
324 | const QString markerNames[KSyntaxHighlighting::Theme::MarkError - KSyntaxHighlighting::Theme::MarkBookmark + 1] = {i18n("Bookmark" ), |
325 | i18n("Active Breakpoint" ), |
326 | i18n("Reached Breakpoint" ), |
327 | i18n("Disabled Breakpoint" ), |
328 | i18n("Execution" ), |
329 | i18n("Warning" ), |
330 | i18n("Error" )}; |
331 | |
332 | ci.whatsThis = i18n("<p>Sets the background color of mark type.</p><p><b>Note</b>: The marker color is displayed lightly because of transparency.</p>" ); |
333 | for (int i = 0; i <= KSyntaxHighlighting::Theme::MarkError - KSyntaxHighlighting::Theme::MarkBookmark; ++i) { |
334 | ci.role = static_cast<KSyntaxHighlighting::Theme::EditorColorRole>(i + KSyntaxHighlighting::Theme::MarkBookmark); |
335 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
336 | ci.name = markerNames[i]; |
337 | ci.key = QLatin1String("Color MarkType " ) + QString::number(i + 1); |
338 | items.append(t: ci); |
339 | } |
340 | |
341 | // |
342 | // text templates |
343 | // |
344 | ci.category = i18n("Text Templates & Snippets" ); |
345 | |
346 | ci.whatsThis = QString(); // TODO: add whatsThis for text templates |
347 | |
348 | ci.role = KSyntaxHighlighting::Theme::TemplateBackground; |
349 | ci.name = i18n("Background" ); |
350 | ci.key = QStringLiteral("Color Template Background" ); |
351 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
352 | items.append(t: ci); |
353 | |
354 | ci.role = KSyntaxHighlighting::Theme::TemplatePlaceholder; |
355 | ci.name = i18n("Editable Placeholder" ); |
356 | ci.key = QStringLiteral("Color Template Editable Placeholder" ); |
357 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
358 | items.append(t: ci); |
359 | |
360 | ci.role = KSyntaxHighlighting::Theme::TemplateFocusedPlaceholder; |
361 | ci.name = i18n("Focused Editable Placeholder" ); |
362 | ci.key = QStringLiteral("Color Template Focused Editable Placeholder" ); |
363 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
364 | items.append(t: ci); |
365 | |
366 | ci.role = KSyntaxHighlighting::Theme::TemplateReadOnlyPlaceholder; |
367 | ci.name = i18n("Not Editable Placeholder" ); |
368 | ci.key = QStringLiteral("Color Template Not Editable Placeholder" ); |
369 | ci.defaultColor = QColor::fromRgba(rgba: theme.editorColor(role: ci.role)); |
370 | items.append(t: ci); |
371 | |
372 | // |
373 | // finally, add all elements |
374 | // |
375 | return items; |
376 | } |
377 | |
378 | void KateThemeConfigColorTab::schemaChanged(const QString &newSchema) |
379 | { |
380 | // ensure invalid or read-only stuff can't be changed |
381 | const auto theme = KateHlManager::self()->repository().theme(themeName: newSchema); |
382 | ui->setReadOnly(!theme.isValid() || theme.isReadOnly()); |
383 | |
384 | // save current schema |
385 | if (!m_currentSchema.isEmpty()) { |
386 | auto it = m_schemas.find(x: m_currentSchema); |
387 | if (it != m_schemas.end()) { |
388 | m_schemas.erase(x: m_currentSchema); // clear this color schema |
389 | } |
390 | |
391 | // now add it again |
392 | m_schemas[m_currentSchema] = ui->colorItems(); |
393 | } |
394 | |
395 | if (newSchema == m_currentSchema) { |
396 | return; |
397 | } |
398 | |
399 | // switch |
400 | m_currentSchema = newSchema; |
401 | |
402 | // If we havent this schema, read in from config file |
403 | if (m_schemas.find(x: newSchema) == m_schemas.end()) { |
404 | QList<KateColorItem> items = colorItemList(theme); |
405 | for (auto &item : items) { |
406 | item.color = QColor::fromRgba(rgba: theme.editorColor(role: item.role)); |
407 | } |
408 | m_schemas[newSchema] = std::move(items); |
409 | } |
410 | |
411 | // first block signals otherwise setColor emits changed |
412 | const bool blocked = blockSignals(b: true); |
413 | |
414 | ui->clear(); |
415 | ui->addColorItems(colorItems: m_schemas[m_currentSchema]); |
416 | |
417 | blockSignals(b: blocked); |
418 | } |
419 | |
420 | /** |
421 | * @brief Converts @p c to its hex value, if @p c has alpha then the returned string |
422 | * will be of the format #AARRGGBB other wise #RRGGBB |
423 | */ |
424 | static QString hexName(const QColor &c) |
425 | { |
426 | return c.alpha() == 0xFF ? c.name() : c.name(format: QColor::HexArgb); |
427 | } |
428 | |
429 | void KateThemeConfigColorTab::apply() |
430 | { |
431 | schemaChanged(newSchema: m_currentSchema); |
432 | |
433 | // we use meta-data of enum for computing the json keys |
434 | static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator(name: "EditorColorRole" ); |
435 | Q_ASSERT(idx >= 0); |
436 | const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(index: idx); |
437 | |
438 | // export all themes we cached data for |
439 | for (auto it = m_schemas.cbegin(); it != m_schemas.cend(); ++it) { |
440 | // get theme for key, skip invalid or read-only themes for writing |
441 | const auto theme = KateHlManager::self()->repository().theme(themeName: it->first); |
442 | if (!theme.isValid() || theme.isReadOnly()) { |
443 | continue; |
444 | } |
445 | |
446 | // get current theme data from disk |
447 | QJsonObject newThemeObject = jsonForTheme(theme); |
448 | |
449 | // patch the editor-colors part |
450 | QJsonObject colors; |
451 | const auto &colorItems = it->second; |
452 | for (const KateColorItem &item : colorItems) { |
453 | QColor c = item.useDefault ? item.defaultColor : item.color; |
454 | colors[QLatin1String(metaEnum.key(index: item.role))] = hexName(c); |
455 | } |
456 | newThemeObject[QLatin1String("editor-colors" )] = colors; |
457 | |
458 | // write json back to file |
459 | writeJson(json: newThemeObject, themeFileName: theme.filePath()); |
460 | } |
461 | |
462 | // all colors are written, so throw away all cached schemas |
463 | m_schemas.clear(); |
464 | } |
465 | |
466 | void KateThemeConfigColorTab::reload() |
467 | { |
468 | // drop all cached data |
469 | m_schemas.clear(); |
470 | |
471 | // trigger re-creation of ui from theme |
472 | const auto backupName = m_currentSchema; |
473 | m_currentSchema.clear(); |
474 | schemaChanged(newSchema: backupName); |
475 | } |
476 | |
477 | QColor KateThemeConfigColorTab::backgroundColor() const |
478 | { |
479 | return ui->findColor(QStringLiteral("Color Background" )); |
480 | } |
481 | |
482 | QColor KateThemeConfigColorTab::selectionColor() const |
483 | { |
484 | return ui->findColor(QStringLiteral("Color Selection" )); |
485 | } |
486 | // END KateThemeConfigColorTab |
487 | |
488 | // BEGIN FontColorConfig -- 'Normal Text Styles' tab |
489 | KateThemeConfigDefaultStylesTab::KateThemeConfigDefaultStylesTab(KateThemeConfigColorTab *colorTab) |
490 | { |
491 | m_colorTab = colorTab; |
492 | |
493 | // size management |
494 | QGridLayout *grid = new QGridLayout(this); |
495 | |
496 | m_defaultStyles = new KateStyleTreeWidget(this); |
497 | connect(sender: m_defaultStyles, signal: &KateStyleTreeWidget::changed, context: this, slot: &KateThemeConfigDefaultStylesTab::changed); |
498 | grid->addWidget(m_defaultStyles, row: 0, column: 0); |
499 | |
500 | m_defaultStyles->setWhatsThis( |
501 | i18n("<p>This list displays the default styles for the current color theme and " |
502 | "offers the means to edit them. The style name reflects the current " |
503 | "style settings.</p>" |
504 | "<p>To edit the colors, click the colored squares, or select the color " |
505 | "to edit from the popup menu.</p><p>You can unset the Background and Selected " |
506 | "Background colors from the popup menu when appropriate.</p>" )); |
507 | } |
508 | |
509 | KateAttributeList *KateThemeConfigDefaultStylesTab::attributeList(const QString &schema) |
510 | { |
511 | auto it = m_defaultStyleLists.find(x: schema); |
512 | if (it == m_defaultStyleLists.end()) { |
513 | // get list of all default styles |
514 | const auto numStyles = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount(); |
515 | KateAttributeList list; |
516 | list.reserve(asize: numStyles); |
517 | const KSyntaxHighlighting::Theme currentTheme = KateHlManager::self()->repository().theme(themeName: schema); |
518 | for (int z = 0; z < numStyles; z++) { |
519 | KTextEditor::Attribute::Ptr i(new KTextEditor::Attribute()); |
520 | const auto style = static_cast<KSyntaxHighlighting::Theme::TextStyle>(z); |
521 | |
522 | if (const auto col = currentTheme.textColor(style)) { |
523 | i->setForeground(QColor::fromRgba(rgba: col)); |
524 | } |
525 | |
526 | if (const auto col = currentTheme.selectedTextColor(style)) { |
527 | i->setSelectedForeground(QColor::fromRgba(rgba: col)); |
528 | } |
529 | |
530 | if (const auto col = currentTheme.backgroundColor(style)) { |
531 | i->setBackground(QColor::fromRgba(rgba: col)); |
532 | } else { |
533 | i->clearBackground(); |
534 | } |
535 | |
536 | if (const auto col = currentTheme.selectedBackgroundColor(style)) { |
537 | i->setSelectedBackground(QColor::fromRgba(rgba: col)); |
538 | } else { |
539 | i->clearProperty(propertyId: SelectedBackground); |
540 | } |
541 | |
542 | i->setFontBold(currentTheme.isBold(style)); |
543 | i->setFontItalic(currentTheme.isItalic(style)); |
544 | i->setFontUnderline(currentTheme.isUnderline(style)); |
545 | i->setFontStrikeOut(currentTheme.isStrikeThrough(style)); |
546 | list.append(t: i); |
547 | } |
548 | it = m_defaultStyleLists.emplace(args: schema, args&: list).first; |
549 | } |
550 | |
551 | return &(it->second); |
552 | } |
553 | |
554 | void KateThemeConfigDefaultStylesTab::schemaChanged(const QString &schema) |
555 | { |
556 | // ensure invalid or read-only stuff can't be changed |
557 | const auto theme = KateHlManager::self()->repository().theme(themeName: schema); |
558 | m_defaultStyles->setReadOnly(!theme.isValid() || theme.isReadOnly()); |
559 | |
560 | m_currentSchema = schema; |
561 | |
562 | m_defaultStyles->clear(); |
563 | |
564 | KateAttributeList *l = attributeList(schema); |
565 | updateColorPalette(textColor: l->at(i: 0)->foreground().color()); |
566 | |
567 | // normal text and source code |
568 | QTreeWidgetItem *parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable" , "Normal Text & Source Code" )); |
569 | parent->setFirstColumnSpanned(true); |
570 | for (int i = (int)KSyntaxHighlighting::Theme::TextStyle::Normal; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Attribute; ++i) { |
571 | m_defaultStyles->addItem(parent, styleName: defaultStyleName(style: static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), defaultstyle: l->at(i)); |
572 | } |
573 | |
574 | // Number, Types & Constants |
575 | parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable" , "Numbers, Types & Constants" )); |
576 | parent->setFirstColumnSpanned(true); |
577 | for (int i = (int)KSyntaxHighlighting::Theme::TextStyle::DataType; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Constant; ++i) { |
578 | m_defaultStyles->addItem(parent, styleName: defaultStyleName(style: static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), defaultstyle: l->at(i)); |
579 | } |
580 | |
581 | // strings & characters |
582 | parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable" , "Strings & Characters" )); |
583 | parent->setFirstColumnSpanned(true); |
584 | for (int i = (int)KSyntaxHighlighting::Theme::TextStyle::Char; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Import; ++i) { |
585 | m_defaultStyles->addItem(parent, styleName: defaultStyleName(style: static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), defaultstyle: l->at(i)); |
586 | } |
587 | |
588 | // comments & documentation |
589 | parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable" , "Comments & Documentation" )); |
590 | parent->setFirstColumnSpanned(true); |
591 | for (int i = (int)KSyntaxHighlighting::Theme::TextStyle::Comment; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Alert; ++i) { |
592 | m_defaultStyles->addItem(parent, styleName: defaultStyleName(style: static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), defaultstyle: l->at(i)); |
593 | } |
594 | |
595 | // Misc |
596 | parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable" , "Miscellaneous" )); |
597 | parent->setFirstColumnSpanned(true); |
598 | for (int i = (int)KSyntaxHighlighting::Theme::TextStyle::Others; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Error; ++i) { |
599 | m_defaultStyles->addItem(parent, styleName: defaultStyleName(style: static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), defaultstyle: l->at(i)); |
600 | } |
601 | |
602 | m_defaultStyles->expandAll(); |
603 | } |
604 | |
605 | void KateThemeConfigDefaultStylesTab::updateColorPalette(const QColor &textColor) |
606 | { |
607 | QPalette p(m_defaultStyles->palette()); |
608 | p.setColor(acr: QPalette::Base, acolor: m_colorTab->backgroundColor()); |
609 | p.setColor(acr: QPalette::Highlight, acolor: m_colorTab->selectionColor()); |
610 | p.setColor(acr: QPalette::Text, acolor: textColor); |
611 | m_defaultStyles->setPalette(p); |
612 | } |
613 | |
614 | void KateThemeConfigDefaultStylesTab::reload() |
615 | { |
616 | m_defaultStyles->clear(); |
617 | m_defaultStyleLists.clear(); |
618 | |
619 | schemaChanged(schema: m_currentSchema); |
620 | } |
621 | |
622 | void KateThemeConfigDefaultStylesTab::apply() |
623 | { |
624 | // get enum meta data for json keys |
625 | static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator(name: "TextStyle" ); |
626 | Q_ASSERT(idx >= 0); |
627 | const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(index: idx); |
628 | |
629 | // export all configured styles of the cached themes |
630 | for (const auto &kv : m_defaultStyleLists) { |
631 | // get theme for key, skip invalid or read-only themes for writing |
632 | const auto theme = KateHlManager::self()->repository().theme(themeName: kv.first); |
633 | if (!theme.isValid() || theme.isReadOnly()) { |
634 | continue; |
635 | } |
636 | |
637 | // get current theme data from disk |
638 | QJsonObject newThemeObject = jsonForTheme(theme); |
639 | |
640 | // patch the text-styles part |
641 | QJsonObject styles; |
642 | const auto numStyles = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount(); |
643 | for (int z = 0; z < numStyles; z++) { |
644 | QJsonObject style; |
645 | KTextEditor::Attribute::Ptr p = kv.second.at(i: z); |
646 | if (p->hasProperty(propertyId: QTextFormat::ForegroundBrush)) { |
647 | style[QLatin1String("text-color" )] = hexName(c: p->foreground().color()); |
648 | } |
649 | if (p->hasProperty(propertyId: QTextFormat::BackgroundBrush)) { |
650 | style[QLatin1String("background-color" )] = hexName(c: p->background().color()); |
651 | } |
652 | if (p->hasProperty(propertyId: SelectedForeground)) { |
653 | style[QLatin1String("selected-text-color" )] = hexName(c: p->selectedForeground().color()); |
654 | } |
655 | if (p->hasProperty(propertyId: SelectedBackground)) { |
656 | style[QLatin1String("selected-background-color" )] = hexName(c: p->selectedBackground().color()); |
657 | } |
658 | if (p->hasProperty(propertyId: QTextFormat::FontWeight) && p->fontBold()) { |
659 | style[QLatin1String("bold" )] = true; |
660 | } |
661 | if (p->hasProperty(propertyId: QTextFormat::FontItalic) && p->fontItalic()) { |
662 | style[QLatin1String("italic" )] = true; |
663 | } |
664 | if (p->hasProperty(propertyId: QTextFormat::TextUnderlineStyle) && p->fontUnderline()) { |
665 | style[QLatin1String("underline" )] = true; |
666 | } |
667 | if (p->hasProperty(propertyId: QTextFormat::FontStrikeOut) && p->fontStrikeOut()) { |
668 | style[QLatin1String("strike-through" )] = true; |
669 | } |
670 | styles[QLatin1String(metaEnum.key(index: (z)))] = style; |
671 | } |
672 | newThemeObject[QLatin1String("text-styles" )] = styles; |
673 | |
674 | // write json back to file |
675 | writeJson(json: newThemeObject, themeFileName: theme.filePath()); |
676 | } |
677 | } |
678 | |
679 | void KateThemeConfigDefaultStylesTab::showEvent(QShowEvent *event) |
680 | { |
681 | if (!event->spontaneous() && !m_currentSchema.isEmpty()) { |
682 | KateAttributeList *l = attributeList(schema: m_currentSchema); |
683 | Q_ASSERT(l != nullptr); |
684 | updateColorPalette(textColor: l->at(i: 0)->foreground().color()); |
685 | } |
686 | |
687 | QWidget::showEvent(event); |
688 | } |
689 | // END FontColorConfig |
690 | |
691 | // BEGIN KateThemeConfigHighlightTab -- 'Highlighting Text Styles' tab |
692 | KateThemeConfigHighlightTab::KateThemeConfigHighlightTab(KateThemeConfigDefaultStylesTab *page, KateThemeConfigColorTab *colorTab) |
693 | { |
694 | m_defaults = page; |
695 | m_colorTab = colorTab; |
696 | |
697 | m_hl = 0; |
698 | |
699 | QVBoxLayout *layout = new QVBoxLayout(this); |
700 | |
701 | QHBoxLayout * = new QHBoxLayout; |
702 | layout->addLayout(layout: headerLayout); |
703 | |
704 | QLabel *lHl = new QLabel(i18n("H&ighlight:" ), this); |
705 | headerLayout->addWidget(lHl); |
706 | |
707 | hlCombo = new QComboBox(this); |
708 | hlCombo->setEditable(false); |
709 | headerLayout->addWidget(hlCombo); |
710 | |
711 | lHl->setBuddy(hlCombo); |
712 | connect(sender: hlCombo, signal: &QComboBox::activated, context: this, slot: &KateThemeConfigHighlightTab::hlChanged); |
713 | |
714 | headerLayout->addStretch(); |
715 | |
716 | const auto modeList = KateHlManager::self()->modeList(); |
717 | for (const auto &hl : modeList) { |
718 | const auto section = hl.translatedSection(); |
719 | if (!section.isEmpty()) { |
720 | hlCombo->addItem(atext: section + QLatin1Char('/') + hl.translatedName()); |
721 | } else { |
722 | hlCombo->addItem(atext: hl.translatedName()); |
723 | } |
724 | } |
725 | hlCombo->setCurrentIndex(0); |
726 | |
727 | // styles listview |
728 | m_styles = new KateStyleTreeWidget(this, true); |
729 | connect(sender: m_styles, signal: &KateStyleTreeWidget::changed, context: this, slot: &KateThemeConfigHighlightTab::changed); |
730 | layout->addWidget(m_styles, stretch: 999); |
731 | |
732 | // get current highlighting from the host application |
733 | int hl = 0; |
734 | KTextEditor::ViewPrivate *kv = |
735 | qobject_cast<KTextEditor::ViewPrivate *>(object: KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView()); |
736 | if (kv) { |
737 | const QString hlName = kv->doc()->highlight()->name(); |
738 | hl = KateHlManager::self()->nameFind(name: hlName); |
739 | Q_ASSERT(hl >= 0); |
740 | } |
741 | |
742 | hlCombo->setCurrentIndex(hl); |
743 | hlChanged(z: hl); |
744 | |
745 | m_styles->setWhatsThis( |
746 | i18n("<p>This list displays the contexts of the current syntax highlight mode and " |
747 | "offers the means to edit them. The context name reflects the current " |
748 | "style settings.</p><p>To edit using the keyboard, press " |
749 | "<strong><SPACE></strong> and choose a property from the popup menu.</p>" |
750 | "<p>To edit the colors, click the colored squares, or select the color " |
751 | "to edit from the popup menu.</p><p>You can unset the Background and Selected " |
752 | "Background colors from the context menu when appropriate.</p>" )); |
753 | } |
754 | |
755 | void KateThemeConfigHighlightTab::hlChanged(int z) |
756 | { |
757 | m_hl = z; |
758 | schemaChanged(schema: m_schema); |
759 | } |
760 | |
761 | /** |
762 | * Helper to get the "default attributes" for the given schema + highlighting. |
763 | * This means all stuff set without taking theme overrides for the highlighting into account. |
764 | */ |
765 | static KateAttributeList defaultsForHighlighting(const std::vector<KSyntaxHighlighting::Format> &formats, const KateAttributeList &defaultStyleAttributes) |
766 | { |
767 | const KSyntaxHighlighting::Theme invalidTheme; |
768 | KateAttributeList defaults; |
769 | for (const auto &format : formats) { |
770 | // create a KTextEditor attribute matching the default style for this format |
771 | // use the default style attribute we got passed to have the one we currently have configured in the settings here |
772 | KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(*defaultStyleAttributes.at(i: format.textStyle()))); |
773 | |
774 | // check for override => if yes, set attribute as overridden, use invalid theme to avoid the usage of theme override! |
775 | |
776 | if (format.hasTextColorOverride()) { |
777 | newAttribute->setForeground(format.textColor(theme: invalidTheme)); |
778 | } |
779 | if (format.hasBackgroundColorOverride()) { |
780 | newAttribute->setBackground(format.backgroundColor(theme: invalidTheme)); |
781 | } |
782 | if (format.hasSelectedTextColorOverride()) { |
783 | newAttribute->setSelectedForeground(format.selectedTextColor(theme: invalidTheme)); |
784 | } |
785 | if (format.hasSelectedBackgroundColorOverride()) { |
786 | newAttribute->setSelectedBackground(format.selectedBackgroundColor(theme: invalidTheme)); |
787 | } |
788 | if (format.hasBoldOverride()) { |
789 | newAttribute->setFontBold(format.isBold(theme: invalidTheme)); |
790 | } |
791 | if (format.hasItalicOverride()) { |
792 | newAttribute->setFontItalic(format.isItalic(theme: invalidTheme)); |
793 | } |
794 | if (format.hasUnderlineOverride()) { |
795 | newAttribute->setFontUnderline(format.isUnderline(theme: invalidTheme)); |
796 | } |
797 | if (format.hasStrikeThroughOverride()) { |
798 | newAttribute->setFontStrikeOut(format.isStrikeThrough(theme: invalidTheme)); |
799 | } |
800 | |
801 | // not really relevant, set it as configured |
802 | newAttribute->setSkipSpellChecking(format.spellCheck()); |
803 | defaults.append(t: newAttribute); |
804 | } |
805 | return defaults; |
806 | } |
807 | |
808 | void KateThemeConfigHighlightTab::schemaChanged(const QString &schema) |
809 | { |
810 | // ensure invalid or read-only stuff can't be changed |
811 | const auto theme = KateHlManager::self()->repository().theme(themeName: schema); |
812 | |
813 | // NOTE: None (m_hl == 0) can't be changed with the current way |
814 | // TODO: removed it from the list? |
815 | const auto isNoneSchema = m_hl == 0; |
816 | m_styles->setReadOnly(!theme.isValid() || theme.isReadOnly() || isNoneSchema); |
817 | |
818 | m_schema = schema; |
819 | |
820 | m_styles->clear(); |
821 | |
822 | auto it = m_hlDict.find(key: m_schema); |
823 | if (it == m_hlDict.end()) { |
824 | it = m_hlDict.insert(key: schema, value: QHash<int, QList<KTextEditor::Attribute::Ptr>>()); |
825 | } |
826 | |
827 | // Set listview colors |
828 | KateAttributeList *l = m_defaults->attributeList(schema); |
829 | updateColorPalette(textColor: l->at(i: 0)->foreground().color()); |
830 | |
831 | // create unified stuff |
832 | auto attributes = KateHlManager::self()->getHl(n: m_hl)->attributesForDefinition(schema: m_schema); |
833 | auto formats = KateHlManager::self()->getHl(n: m_hl)->formats(); |
834 | auto defaults = defaultsForHighlighting(formats, defaultStyleAttributes: *l); |
835 | |
836 | for (int i = 0; i < attributes.size(); ++i) { |
837 | // All stylenames have their language mode prefixed, e.g. HTML:Comment |
838 | // split them and put them into nice substructures. |
839 | int c = attributes[i]->name().indexOf(c: QLatin1Char(':')); |
840 | if (c <= 0) { |
841 | continue; |
842 | } |
843 | |
844 | QString highlighting = attributes[i]->name().left(n: c); |
845 | QString name = attributes[i]->name().mid(position: c + 1); |
846 | auto &uniqueAttribute = m_uniqueAttributes[m_schema][highlighting][name].first; |
847 | auto &uniqueAttributeDefault = m_uniqueAttributes[m_schema][highlighting][name].second; |
848 | |
849 | if (uniqueAttribute.data()) { |
850 | attributes[i] = uniqueAttribute; |
851 | } else { |
852 | uniqueAttribute = attributes[i]; |
853 | } |
854 | |
855 | if (uniqueAttributeDefault.data()) { |
856 | defaults[i] = uniqueAttributeDefault; |
857 | } else { |
858 | uniqueAttributeDefault = defaults[i]; |
859 | } |
860 | } |
861 | |
862 | auto &subMap = it.value(); |
863 | auto it1 = subMap.find(key: m_hl); |
864 | if (it1 == subMap.end()) { |
865 | it1 = subMap.insert(key: m_hl, value: attributes); |
866 | } |
867 | |
868 | QHash<QString, QTreeWidgetItem *> prefixes; |
869 | const auto &attribs = it1.value(); |
870 | auto vec_it = attribs.cbegin(); |
871 | int i = 0; |
872 | while (vec_it != attribs.end()) { |
873 | const KTextEditor::Attribute::Ptr itemData = *vec_it; |
874 | Q_ASSERT(itemData); |
875 | |
876 | // All stylenames have their language mode prefixed, e.g. HTML:Comment |
877 | // split them and put them into nice substructures. |
878 | int c = itemData->name().indexOf(c: QLatin1Char(':')); |
879 | if (c > 0) { |
880 | QString prefix = itemData->name().left(n: c); |
881 | QString name = itemData->name().mid(position: c + 1); |
882 | |
883 | QTreeWidgetItem *parent = prefixes[prefix]; |
884 | if (!parent) { |
885 | parent = new QTreeWidgetItem(m_styles, QStringList() << prefix); |
886 | m_styles->expandItem(item: parent); |
887 | prefixes.insert(key: prefix, value: parent); |
888 | } |
889 | m_styles->addItem(parent, styleName: name, defaultstyle: defaults.at(i), data: itemData); |
890 | } else { |
891 | m_styles->addItem(styleName: itemData->name(), defaultstyle: defaults.at(i), data: itemData); |
892 | } |
893 | ++vec_it; |
894 | ++i; |
895 | } |
896 | |
897 | m_styles->resizeColumns(); |
898 | } |
899 | |
900 | void KateThemeConfigHighlightTab::updateColorPalette(const QColor &textColor) |
901 | { |
902 | QPalette p(m_styles->palette()); |
903 | p.setColor(acr: QPalette::Base, acolor: m_colorTab->backgroundColor()); |
904 | p.setColor(acr: QPalette::Highlight, acolor: m_colorTab->selectionColor()); |
905 | p.setColor(acr: QPalette::Text, acolor: textColor); |
906 | m_styles->setPalette(p); |
907 | } |
908 | |
909 | void KateThemeConfigHighlightTab::reload() |
910 | { |
911 | m_styles->clear(); |
912 | |
913 | m_hlDict.clear(); |
914 | m_uniqueAttributes.clear(); |
915 | |
916 | hlChanged(z: hlCombo->currentIndex()); |
917 | } |
918 | |
919 | void KateThemeConfigHighlightTab::apply() |
920 | { |
921 | // handle all cached themes data |
922 | for (const auto &themeIt : m_uniqueAttributes) { |
923 | // get theme for key, skip invalid or read-only themes for writing |
924 | const auto theme = KateHlManager::self()->repository().theme(themeName: themeIt.first); |
925 | if (!theme.isValid() || theme.isReadOnly()) { |
926 | continue; |
927 | } |
928 | |
929 | // get current theme data from disk |
930 | QJsonObject newThemeObject = jsonForTheme(theme); |
931 | |
932 | // look at all highlightings we have info stored, important: keep info we did load from file and not overwrite here! |
933 | QJsonObject overrides = newThemeObject[QLatin1String("custom-styles" )].toObject(); |
934 | for (const auto &highlightingIt : themeIt.second) { |
935 | // start with stuff we know from the loaded json |
936 | const QString definitionName = highlightingIt.first; |
937 | QJsonObject styles = overrides[definitionName].toObject(); |
938 | for (const auto &attributeIt : highlightingIt.second) { |
939 | // we need to store even if we have nothing set as long as the value differs from the default, see bug 459093 |
940 | QJsonObject style; |
941 | KTextEditor::Attribute::Ptr p = attributeIt.second.first; |
942 | KTextEditor::Attribute::Ptr pDefault = attributeIt.second.second; |
943 | if (p->foreground().color() != pDefault->foreground().color()) { |
944 | style[QLatin1String("text-color" )] = hexName(c: p->foreground().color()); |
945 | } |
946 | if (p->background().color() != pDefault->background().color()) { |
947 | style[QLatin1String("background-color" )] = hexName(c: p->background().color()); |
948 | } |
949 | if (p->selectedForeground().color() != pDefault->selectedForeground().color()) { |
950 | style[QLatin1String("selected-text-color" )] = hexName(c: p->selectedForeground().color()); |
951 | } |
952 | if (p->selectedBackground().color() != pDefault->selectedBackground().color()) { |
953 | style[QLatin1String("selected-background-color" )] = hexName(c: p->selectedBackground().color()); |
954 | } |
955 | if (p->fontBold() != pDefault->fontBold()) { |
956 | style[QLatin1String("bold" )] = p->fontBold(); |
957 | } |
958 | if (p->fontItalic() != pDefault->fontItalic()) { |
959 | style[QLatin1String("italic" )] = p->fontItalic(); |
960 | } |
961 | if (p->fontUnderline() != pDefault->fontUnderline()) { |
962 | style[QLatin1String("underline" )] = p->fontUnderline(); |
963 | } |
964 | if (p->fontStrikeOut() != pDefault->fontStrikeOut()) { |
965 | style[QLatin1String("strike-through" )] = p->fontStrikeOut(); |
966 | } |
967 | |
968 | // either set the new stuff or erase the old entry we might have set from the loaded json |
969 | if (!style.isEmpty()) { |
970 | styles[attributeIt.first] = style; |
971 | } else { |
972 | styles.remove(key: attributeIt.first); |
973 | } |
974 | } |
975 | |
976 | // either set the new stuff or erase the old entry we might have set from the loaded json |
977 | if (!styles.isEmpty()) { |
978 | overrides[definitionName] = styles; |
979 | } else { |
980 | overrides.remove(key: definitionName); |
981 | } |
982 | } |
983 | |
984 | // we set even empty overrides, to ensure we overwrite stuff! |
985 | newThemeObject[QLatin1String("custom-styles" )] = overrides; |
986 | |
987 | // write json back to file |
988 | writeJson(json: newThemeObject, themeFileName: theme.filePath()); |
989 | } |
990 | } |
991 | |
992 | QList<int> KateThemeConfigHighlightTab::hlsForSchema(const QString &schema) |
993 | { |
994 | auto it = m_hlDict.find(key: schema); |
995 | if (it != m_hlDict.end()) { |
996 | return it.value().keys(); |
997 | } |
998 | return {}; |
999 | } |
1000 | |
1001 | void KateThemeConfigHighlightTab::showEvent(QShowEvent *event) |
1002 | { |
1003 | if (!event->spontaneous()) { |
1004 | KateAttributeList *l = m_defaults->attributeList(schema: m_schema); |
1005 | Q_ASSERT(l != nullptr); |
1006 | updateColorPalette(textColor: l->at(i: 0)->foreground().color()); |
1007 | } |
1008 | |
1009 | QWidget::showEvent(event); |
1010 | } |
1011 | // END KateThemeConfigHighlightTab |
1012 | |
1013 | // BEGIN KateThemeConfigPage -- Main dialog page |
1014 | KateThemeConfigPage::KateThemeConfigPage(QWidget *parent) |
1015 | : KateConfigPage(parent) |
1016 | { |
1017 | QHBoxLayout *layout = new QHBoxLayout(this); |
1018 | layout->setContentsMargins({}); |
1019 | |
1020 | QTabWidget *tabWidget = new QTabWidget(this); |
1021 | tabWidget->setDocumentMode(true); |
1022 | layout->addWidget(tabWidget); |
1023 | |
1024 | auto *themeEditor = new QWidget(this); |
1025 | auto *themeChooser = new QWidget(this); |
1026 | tabWidget->addTab(widget: themeChooser, i18n("Default Theme" )); |
1027 | tabWidget->addTab(widget: themeEditor, i18n("Theme Editor" )); |
1028 | layoutThemeChooserTab(tab: themeChooser); |
1029 | layoutThemeEditorTab(tab: themeEditor); |
1030 | |
1031 | reload(); |
1032 | } |
1033 | |
1034 | void KateThemeConfigPage::layoutThemeChooserTab(QWidget *tab) |
1035 | { |
1036 | QVBoxLayout *layout = new QVBoxLayout(tab); |
1037 | layout->setContentsMargins({}); |
1038 | |
1039 | auto *comboLayout = new QHBoxLayout; |
1040 | |
1041 | auto lHl = new QLabel(i18n("Select theme:" ), this); |
1042 | comboLayout->addWidget(lHl); |
1043 | |
1044 | defaultSchemaCombo = new QComboBox(this); |
1045 | comboLayout->addWidget(defaultSchemaCombo); |
1046 | defaultSchemaCombo->setEditable(false); |
1047 | lHl->setBuddy(defaultSchemaCombo); |
1048 | connect(sender: defaultSchemaCombo, signal: &QComboBox::currentIndexChanged, context: this, slot: &KateThemeConfigPage::slotChanged); |
1049 | comboLayout->addStretch(); |
1050 | |
1051 | layout->addLayout(layout: comboLayout); |
1052 | |
1053 | m_doc = new KTextEditor::DocumentPrivate; |
1054 | m_doc->setParent(this); |
1055 | |
1056 | const auto code = R"sample(/** |
1057 | * SPDX-FileCopyrightText: 2020 Christoph Cullmann <cullmann@kde.org> |
1058 | * SPDX-License-Identifier: MIT |
1059 | */ |
1060 | |
1061 | // BEGIN |
1062 | #include <QString> |
1063 | #include <string> |
1064 | // END |
1065 | |
1066 | /** |
1067 | * TODO: improve documentation |
1068 | * @param magicArgument some magic argument |
1069 | * @return magic return value |
1070 | */ |
1071 | int main(uint64_t magicArgument) |
1072 | { |
1073 | if (magicArgument > 1) { |
1074 | const std::string string = "source file: \"" __FILE__ "\""; |
1075 | const QString qString(QStringLiteral("test")); |
1076 | return qrand(); |
1077 | } |
1078 | |
1079 | /* BUG: bogus integer constant inside next line */ |
1080 | const double g = 1.1e12 * 0b01'01'01'01 - 43a + 0x11234 * 0234ULL - 'c' * 42; |
1081 | return g > 1.3f; |
1082 | })sample" ; |
1083 | |
1084 | m_doc->setText(QString::fromUtf8(utf8: code)); |
1085 | m_doc->setHighlightingMode(QStringLiteral("C++" )); |
1086 | m_themePreview = new KTextEditor::ViewPrivate(m_doc, this); |
1087 | |
1088 | layout->addWidget(m_themePreview); |
1089 | |
1090 | connect(sender: defaultSchemaCombo, signal: &QComboBox::currentIndexChanged, context: this, slot: [this](int idx) { |
1091 | const QString schema = defaultSchemaCombo->itemData(index: idx).toString(); |
1092 | m_themePreview->rendererConfig()->setSchema(schema); |
1093 | if (schema.isEmpty()) { |
1094 | m_themePreview->rendererConfig()->setValue(key: KateRendererConfig::AutoColorThemeSelection, value: true); |
1095 | } else { |
1096 | m_themePreview->rendererConfig()->setValue(key: KateRendererConfig::AutoColorThemeSelection, value: false); |
1097 | } |
1098 | }); |
1099 | } |
1100 | |
1101 | void KateThemeConfigPage::layoutThemeEditorTab(QWidget *tab) |
1102 | { |
1103 | QVBoxLayout *layout = new QVBoxLayout(tab); |
1104 | layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
1105 | |
1106 | // header |
1107 | QHBoxLayout * = new QHBoxLayout; |
1108 | layout->addLayout(layout: headerLayout); |
1109 | |
1110 | QLabel *lHl = new QLabel(i18n("&Theme:" ), this); |
1111 | headerLayout->addWidget(lHl); |
1112 | |
1113 | schemaCombo = new QComboBox(this); |
1114 | schemaCombo->setEditable(false); |
1115 | lHl->setBuddy(schemaCombo); |
1116 | headerLayout->addWidget(schemaCombo); |
1117 | connect(sender: schemaCombo, signal: &QComboBox::currentIndexChanged, context: this, slot: &KateThemeConfigPage::comboBoxIndexChanged); |
1118 | |
1119 | QPushButton *copyButton = new QPushButton(i18n("&Copy..." ), this); |
1120 | headerLayout->addWidget(copyButton); |
1121 | connect(sender: copyButton, signal: &QPushButton::clicked, context: this, slot: &KateThemeConfigPage::copyTheme); |
1122 | |
1123 | btndel = new QPushButton(i18n("&Delete" ), this); |
1124 | headerLayout->addWidget(btndel); |
1125 | connect(sender: btndel, signal: &QPushButton::clicked, context: this, slot: &KateThemeConfigPage::deleteSchema); |
1126 | |
1127 | QPushButton *btnexport = new QPushButton(i18n("Export..." ), this); |
1128 | headerLayout->addWidget(btnexport); |
1129 | connect(sender: btnexport, signal: &QPushButton::clicked, context: this, slot: &KateThemeConfigPage::exportFullSchema); |
1130 | |
1131 | QPushButton *btnimport = new QPushButton(i18n("Import..." ), this); |
1132 | headerLayout->addWidget(btnimport); |
1133 | connect(sender: btnimport, signal: &QPushButton::clicked, context: this, slot: &KateThemeConfigPage::importFullSchema); |
1134 | |
1135 | headerLayout->addStretch(); |
1136 | |
1137 | // label to inform about read-only state |
1138 | m_readOnlyThemeLabel = new KMessageWidget(i18n("Bundled read-only theme. To modify the theme, please copy it." ), this); |
1139 | m_readOnlyThemeLabel->setCloseButtonVisible(false); |
1140 | m_readOnlyThemeLabel->setMessageType(KMessageWidget::Information); |
1141 | m_readOnlyThemeLabel->hide(); |
1142 | layout->addWidget(m_readOnlyThemeLabel); |
1143 | |
1144 | // tabs |
1145 | QTabWidget *tabWidget = new QTabWidget(this); |
1146 | layout->addWidget(tabWidget); |
1147 | |
1148 | m_colorTab = new KateThemeConfigColorTab(); |
1149 | tabWidget->addTab(widget: m_colorTab, i18n("Colors" )); |
1150 | connect(sender: m_colorTab, signal: &KateThemeConfigColorTab::changed, context: this, slot: &KateThemeConfigPage::slotChanged); |
1151 | |
1152 | m_defaultStylesTab = new KateThemeConfigDefaultStylesTab(m_colorTab); |
1153 | tabWidget->addTab(widget: m_defaultStylesTab, i18n("Default Text Styles" )); |
1154 | connect(sender: m_defaultStylesTab, signal: &KateThemeConfigDefaultStylesTab::changed, context: this, slot: &KateThemeConfigPage::slotChanged); |
1155 | |
1156 | m_highlightTab = new KateThemeConfigHighlightTab(m_defaultStylesTab, m_colorTab); |
1157 | tabWidget->addTab(widget: m_highlightTab, i18n("Highlighting Text Styles" )); |
1158 | connect(sender: m_highlightTab, signal: &KateThemeConfigHighlightTab::changed, context: this, slot: &KateThemeConfigPage::slotChanged); |
1159 | |
1160 | QHBoxLayout * = new QHBoxLayout; |
1161 | layout->addLayout(layout: footLayout); |
1162 | } |
1163 | |
1164 | void KateThemeConfigPage::exportFullSchema() |
1165 | { |
1166 | // get save destination |
1167 | const QString currentSchemaName = m_currentSchema; |
1168 | const QString destName = QFileDialog::getSaveFileName(parent: this, |
1169 | i18n("Exporting color theme: %1" , currentSchemaName), |
1170 | dir: currentSchemaName + QLatin1String(".theme" ), |
1171 | QStringLiteral("%1 (*.theme)" ).arg(i18n("Color theme" ))); |
1172 | if (destName.isEmpty()) { |
1173 | return; |
1174 | } |
1175 | |
1176 | // get current theme |
1177 | const QString currentThemeName = schemaCombo->itemData(index: schemaCombo->currentIndex()).toString(); |
1178 | const auto currentTheme = KateHlManager::self()->repository().theme(themeName: currentThemeName); |
1179 | |
1180 | // ensure we overwrite |
1181 | if (QFile::exists(fileName: destName)) { |
1182 | QFile::remove(fileName: destName); |
1183 | } |
1184 | |
1185 | // export is easy, just copy the file 1:1 |
1186 | QFile::copy(fileName: currentTheme.filePath(), newName: destName); |
1187 | } |
1188 | |
1189 | void KateThemeConfigPage::importFullSchema() |
1190 | { |
1191 | const QString srcName = |
1192 | QFileDialog::getOpenFileName(parent: this, i18n("Importing Color Theme" ), dir: QString(), QStringLiteral("%1 (*.theme)" ).arg(i18n("Color theme" ))); |
1193 | if (srcName.isEmpty()) { |
1194 | return; |
1195 | } |
1196 | |
1197 | // location to write theme files to |
1198 | const QString themesPath = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes" ); |
1199 | |
1200 | // construct file name for imported theme |
1201 | const QString themesFullFileName = themesPath + QStringLiteral("/" ) + QFileInfo(srcName).fileName(); |
1202 | |
1203 | // if something might be overwritten, as the user |
1204 | if (QFile::exists(fileName: themesFullFileName)) { |
1205 | if (KMessageBox::warningContinueCancel(parent: this, |
1206 | i18n("Importing will overwrite the existing theme file \"%1\". This can not be undone." , themesFullFileName), |
1207 | i18n("Possible Data Loss" ), |
1208 | buttonContinue: KGuiItem(i18n("Import Nevertheless" )), |
1209 | buttonCancel: KStandardGuiItem::cancel()) |
1210 | != KMessageBox::Continue) { |
1211 | return; |
1212 | } |
1213 | } |
1214 | |
1215 | // copy theme file, we might need to create the local dir first |
1216 | QDir().mkpath(dirPath: themesPath); |
1217 | |
1218 | // ensure we overwrite |
1219 | if (QFile::exists(fileName: themesFullFileName)) { |
1220 | QFile::remove(fileName: themesFullFileName); |
1221 | } |
1222 | QFile::copy(fileName: srcName, newName: themesFullFileName); |
1223 | |
1224 | // reload themes DB & clear all attributes |
1225 | KateHlManager::self()->reload(); |
1226 | for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) { |
1227 | KateHlManager::self()->getHl(n: i)->clearAttributeArrays(); |
1228 | } |
1229 | |
1230 | // KateThemeManager::update() sorts the schema alphabetically, hence the |
1231 | // schema indexes change. Thus, repopulate the schema list... |
1232 | refillCombos(schemaName: schemaCombo->itemData(index: schemaCombo->currentIndex()).toString(), defaultSchemaName: defaultSchemaCombo->itemData(index: defaultSchemaCombo->currentIndex()).toString()); |
1233 | } |
1234 | |
1235 | void KateThemeConfigPage::apply() |
1236 | { |
1237 | // remember name + index |
1238 | const QString schemaName = schemaCombo->itemData(index: schemaCombo->currentIndex()).toString(); |
1239 | |
1240 | // first apply all tabs |
1241 | m_colorTab->apply(); |
1242 | m_defaultStylesTab->apply(); |
1243 | m_highlightTab->apply(); |
1244 | |
1245 | // reload themes DB & clear all attributes |
1246 | KateHlManager::self()->reload(); |
1247 | for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) { |
1248 | KateHlManager::self()->getHl(n: i)->clearAttributeArrays(); |
1249 | } |
1250 | |
1251 | // than reload the whole stuff, special handle auto selection == empty theme name |
1252 | const auto defaultTheme = defaultSchemaCombo->itemData(index: defaultSchemaCombo->currentIndex()).toString(); |
1253 | if (defaultTheme.isEmpty()) { |
1254 | KateRendererConfig::global()->setValue(key: KateRendererConfig::AutoColorThemeSelection, value: true); |
1255 | } else { |
1256 | KateRendererConfig::global()->setValue(key: KateRendererConfig::AutoColorThemeSelection, value: false); |
1257 | KateRendererConfig::global()->setSchema(defaultTheme); |
1258 | } |
1259 | KateRendererConfig::global()->reloadSchema(); |
1260 | |
1261 | // KateThemeManager::update() sorts the schema alphabetically, hence the |
1262 | // schema indexes change. Thus, repopulate the schema list... |
1263 | refillCombos(schemaName: schemaCombo->itemData(index: schemaCombo->currentIndex()).toString(), defaultSchemaName: defaultSchemaCombo->itemData(index: defaultSchemaCombo->currentIndex()).toString()); |
1264 | schemaChanged(schema: schemaName); |
1265 | |
1266 | // all tabs need to reload to discard all the cached data, as the index |
1267 | // mapping may have changed |
1268 | m_colorTab->reload(); |
1269 | m_defaultStylesTab->reload(); |
1270 | m_highlightTab->reload(); |
1271 | } |
1272 | |
1273 | void KateThemeConfigPage::reload() |
1274 | { |
1275 | // reinitialize combo boxes |
1276 | refillCombos(schemaName: KateRendererConfig::global()->schema(), defaultSchemaName: KateRendererConfig::global()->schema()); |
1277 | |
1278 | // finally, activate the current schema again |
1279 | schemaChanged(schema: schemaCombo->itemData(index: schemaCombo->currentIndex()).toString()); |
1280 | |
1281 | // all tabs need to reload to discard all the cached data, as the index |
1282 | // mapping may have changed |
1283 | m_colorTab->reload(); |
1284 | m_defaultStylesTab->reload(); |
1285 | m_highlightTab->reload(); |
1286 | } |
1287 | |
1288 | void KateThemeConfigPage::refillCombos(const QString &schemaName, const QString &defaultSchemaName) |
1289 | { |
1290 | schemaCombo->blockSignals(b: true); |
1291 | defaultSchemaCombo->blockSignals(b: true); |
1292 | |
1293 | // reinitialize combo boxes |
1294 | schemaCombo->clear(); |
1295 | defaultSchemaCombo->clear(); |
1296 | defaultSchemaCombo->addItem(i18n("Follow System Color Scheme" ), auserData: QString()); |
1297 | defaultSchemaCombo->insertSeparator(index: 1); |
1298 | const auto themes = KateHlManager::self()->sortedThemes(); |
1299 | for (const auto &theme : themes) { |
1300 | schemaCombo->addItem(atext: theme.translatedName(), auserData: theme.name()); |
1301 | defaultSchemaCombo->addItem(atext: theme.translatedName(), auserData: theme.name()); |
1302 | } |
1303 | |
1304 | // set the correct indexes again, fallback to always existing default theme |
1305 | int schemaIndex = schemaCombo->findData(data: schemaName); |
1306 | if (schemaIndex == -1) { |
1307 | schemaIndex = schemaCombo->findData( |
1308 | data: KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(t: KSyntaxHighlighting::Repository::LightTheme).name()); |
1309 | } |
1310 | |
1311 | // set the correct indexes again, fallback to auto-selection |
1312 | int defaultSchemaIndex = 0; |
1313 | if (!KateRendererConfig::global()->value(key: KateRendererConfig::AutoColorThemeSelection).toBool()) { |
1314 | defaultSchemaIndex = defaultSchemaCombo->findData(data: defaultSchemaName); |
1315 | if (defaultSchemaIndex == -1) { |
1316 | defaultSchemaIndex = 0; |
1317 | } |
1318 | } |
1319 | |
1320 | Q_ASSERT(schemaIndex != -1); |
1321 | Q_ASSERT(defaultSchemaIndex != -1); |
1322 | |
1323 | defaultSchemaCombo->setCurrentIndex(defaultSchemaIndex); |
1324 | schemaCombo->setCurrentIndex(schemaIndex); |
1325 | |
1326 | schemaCombo->blockSignals(b: false); |
1327 | defaultSchemaCombo->blockSignals(b: false); |
1328 | |
1329 | m_themePreview->rendererConfig()->setSchema(defaultSchemaName); |
1330 | } |
1331 | |
1332 | void KateThemeConfigPage::reset() |
1333 | { |
1334 | // reload themes DB & clear all attributes |
1335 | KateHlManager::self()->reload(); |
1336 | for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) { |
1337 | KateHlManager::self()->getHl(n: i)->clearAttributeArrays(); |
1338 | } |
1339 | |
1340 | // reload the view |
1341 | reload(); |
1342 | } |
1343 | |
1344 | void KateThemeConfigPage::defaults() |
1345 | { |
1346 | reset(); |
1347 | } |
1348 | |
1349 | void KateThemeConfigPage::deleteSchema() |
1350 | { |
1351 | const int comboIndex = schemaCombo->currentIndex(); |
1352 | const QString schemaNameToDelete = schemaCombo->itemData(index: comboIndex).toString(); |
1353 | |
1354 | // KSyntaxHighlighting themes can not be deleted, skip invalid themes, too |
1355 | const auto theme = KateHlManager::self()->repository().theme(themeName: schemaNameToDelete); |
1356 | if (!theme.isValid() || theme.isReadOnly()) { |
1357 | return; |
1358 | } |
1359 | |
1360 | // ask the user again, this can't be undone |
1361 | if (KMessageBox::warningContinueCancel(parent: this, |
1362 | i18n("Do you really want to delete the theme \"%1\"? This can not be undone." , schemaNameToDelete), |
1363 | i18n("Possible Data Loss" ), |
1364 | buttonContinue: KGuiItem(i18n("Delete Nevertheless" )), |
1365 | buttonCancel: KStandardGuiItem::cancel()) |
1366 | != KMessageBox::Continue) { |
1367 | return; |
1368 | } |
1369 | |
1370 | // purge the theme file |
1371 | QFile::remove(fileName: theme.filePath()); |
1372 | |
1373 | // reset syntax manager repo to flush deleted theme |
1374 | KateHlManager::self()->reload(); |
1375 | |
1376 | // fallback to Default schema + auto |
1377 | schemaCombo->setCurrentIndex(schemaCombo->findData( |
1378 | data: QVariant(KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(t: KSyntaxHighlighting::Repository::LightTheme).name()))); |
1379 | if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(data: schemaNameToDelete)) { |
1380 | defaultSchemaCombo->setCurrentIndex(0); |
1381 | } |
1382 | |
1383 | // remove schema from combo box |
1384 | schemaCombo->removeItem(index: comboIndex); |
1385 | defaultSchemaCombo->removeItem(index: comboIndex); |
1386 | |
1387 | // Reload the color tab, since it uses cached schemas |
1388 | m_colorTab->reload(); |
1389 | } |
1390 | |
1391 | bool KateThemeConfigPage::copyTheme() |
1392 | { |
1393 | // get current theme data as template |
1394 | const QString currentThemeName = schemaCombo->itemData(index: schemaCombo->currentIndex()).toString(); |
1395 | const auto currentTheme = KateHlManager::self()->repository().theme(themeName: currentThemeName); |
1396 | |
1397 | // location to write theme files to |
1398 | const QString themesPath = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes" ); |
1399 | |
1400 | // get sane name |
1401 | QString schemaName; |
1402 | QString themeFileName; |
1403 | while (schemaName.isEmpty()) { |
1404 | QInputDialog newNameDialog(this); |
1405 | newNameDialog.setInputMode(QInputDialog::TextInput); |
1406 | newNameDialog.setWindowTitle(i18n("Copy theme" )); |
1407 | newNameDialog.setLabelText(i18n("Name for copy of color theme \"%1\":" , currentThemeName)); |
1408 | newNameDialog.setTextValue(currentThemeName); |
1409 | if (newNameDialog.exec() == QDialog::Rejected) { |
1410 | return false; |
1411 | } |
1412 | schemaName = newNameDialog.textValue(); |
1413 | |
1414 | // try if schema already around => if yes, retry name input |
1415 | // we try for duplicated file names, too |
1416 | themeFileName = themesPath + QStringLiteral("/" ) + schemaName + QStringLiteral(".theme" ); |
1417 | if (KateHlManager::self()->repository().theme(themeName: schemaName).isValid() || QFile::exists(fileName: themeFileName)) { |
1418 | KMessageBox::information(parent: this, |
1419 | i18n("<p>The theme \"%1\" already exists.</p><p>Please choose a different theme name.</p>" , schemaName), |
1420 | i18n("Copy Theme" )); |
1421 | schemaName.clear(); |
1422 | } |
1423 | } |
1424 | |
1425 | // get json for current theme |
1426 | QJsonObject newThemeObject = jsonForTheme(theme: currentTheme); |
1427 | QJsonObject metaData; |
1428 | metaData[QLatin1String("revision" )] = 1; |
1429 | metaData[QLatin1String("name" )] = schemaName; |
1430 | newThemeObject[QLatin1String("metadata" )] = metaData; |
1431 | |
1432 | // write to new theme file, we might need to create the local dir first |
1433 | QDir().mkpath(dirPath: themesPath); |
1434 | if (!writeJson(json: newThemeObject, themeFileName)) { |
1435 | return false; |
1436 | } |
1437 | |
1438 | // reset syntax manager repo to find new theme |
1439 | KateHlManager::self()->reload(); |
1440 | |
1441 | // append items to combo boxes |
1442 | schemaCombo->addItem(atext: schemaName, auserData: QVariant(schemaName)); |
1443 | defaultSchemaCombo->addItem(atext: schemaName, auserData: QVariant(schemaName)); |
1444 | |
1445 | // finally, activate new schema (last item in the list) |
1446 | schemaCombo->setCurrentIndex(schemaCombo->count() - 1); |
1447 | return true; |
1448 | } |
1449 | |
1450 | void KateThemeConfigPage::schemaChanged(const QString &schema) |
1451 | { |
1452 | // we can't delete read-only themes, e.g. the stuff shipped inside Qt resources or system wide installed |
1453 | const auto theme = KateHlManager::self()->repository().theme(themeName: schema); |
1454 | btndel->setEnabled(!theme.isReadOnly()); |
1455 | m_readOnlyThemeLabel->setVisible(theme.isReadOnly()); |
1456 | |
1457 | // propagate changed schema to all tabs |
1458 | m_colorTab->schemaChanged(newSchema: schema); |
1459 | m_defaultStylesTab->schemaChanged(schema); |
1460 | m_highlightTab->schemaChanged(schema); |
1461 | |
1462 | // save current schema index |
1463 | m_currentSchema = schema; |
1464 | } |
1465 | |
1466 | void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex) |
1467 | { |
1468 | schemaChanged(schema: schemaCombo->itemData(index: currentIndex).toString()); |
1469 | } |
1470 | |
1471 | QString KateThemeConfigPage::name() const |
1472 | { |
1473 | return i18n("Color Themes" ); |
1474 | } |
1475 | |
1476 | QString KateThemeConfigPage::fullName() const |
1477 | { |
1478 | return i18n("Color Themes" ); |
1479 | } |
1480 | |
1481 | QIcon KateThemeConfigPage::icon() const |
1482 | { |
1483 | return QIcon::fromTheme(QStringLiteral("preferences-desktop-color" )); |
1484 | } |
1485 | |
1486 | // END KateThemeConfigPage |
1487 | |
1488 | #include "moc_katethemeconfig.cpp" |
1489 | |