KTextEditor

katethemeconfig.cpp
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 */
42static 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 */
123static 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(QIODevice::ReadOnly)) {
128 return QJsonObject();
129 }
130 const QByteArray jsonData = loadFile.readAll();
131 QJsonParseError parseError;
132 QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &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 */
146static bool writeJson(const QJsonObject &json, const QString &themeFileName)
147{
148 QFile saveFile(themeFileName);
149 if (!saveFile.open(QIODevice::WriteOnly)) {
150 return false;
151 }
152 saveFile.write(QJsonDocument(json).toJson());
153 return true;
154}
155
156// BEGIN KateThemeConfigColorTab -- 'Colors' tab
157KateThemeConfigColorTab::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, 0, 0, 1, 2);
165 l->addWidget(btnUseColorScheme, 1, 1);
166
167 l->setColumnStretch(0, 1);
168 l->setColumnStretch(1, 0);
169
170 connect(btnUseColorScheme, &QPushButton::clicked, ui, &KateColorTreeWidget::selectDefaults);
171 connect(ui, &KateColorTreeWidget::changed, this, &KateThemeConfigColorTab::changed);
172}
173
174static QList<KateColorItem> colorItemList(const KSyntaxHighlighting::Theme &theme)
175{
177
178 //
179 // editor background colors
180 //
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(theme.editorColor(ci.role));
188 items.append(ci);
189
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 &quot;<b>Configure Highlighting</b>&quot; "
195 "dialog.</p>");
196 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
197 items.append(ci);
198
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(theme.editorColor(ci.role));
204 items.append(ci);
205
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(theme.editorColor(ci.role));
211 items.append(ci);
212
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(theme.editorColor(ci.role));
218 items.append(ci);
219
220 //
221 // icon border
222 //
223 ci.category = i18n("Icon Border");
224
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(theme.editorColor(ci.role));
230 items.append(ci);
231
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(theme.editorColor(ci.role));
237 items.append(ci);
238
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(theme.editorColor(ci.role));
244 items.append(ci);
245
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(theme.editorColor(ci.role));
251 items.append(ci);
252
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(theme.editorColor(ci.role));
261 items.append(ci);
262
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(theme.editorColor(ci.role));
268 items.append(ci);
269
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(theme.editorColor(ci.role));
275 items.append(ci);
276
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(theme.editorColor(ci.role));
282 items.append(ci);
283
284 //
285 // text decorations
286 //
287 ci.category = i18n("Text Decorations");
288
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(theme.editorColor(ci.role));
294 items.append(ci);
295
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(theme.editorColor(ci.role));
301 items.append(ci);
302
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(theme.editorColor(ci.role));
308 items.append(ci);
309
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(theme.editorColor(ci.role));
317 items.append(ci);
318
319 //
320 // marker colors
321 //
322 ci.category = i18n("Marker Colors");
323
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>");
335 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
336 ci.name = markerNames[i];
337 ci.key = QLatin1String("Color MarkType ") + QString::number(i + 1);
338 items.append(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
349 ci.name = i18n("Background");
350 ci.key = QStringLiteral("Color Template Background");
351 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
352 items.append(ci);
353
355 ci.name = i18n("Editable Placeholder");
356 ci.key = QStringLiteral("Color Template Editable Placeholder");
357 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
358 items.append(ci);
359
361 ci.name = i18n("Focused Editable Placeholder");
362 ci.key = QStringLiteral("Color Template Focused Editable Placeholder");
363 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
364 items.append(ci);
365
367 ci.name = i18n("Not Editable Placeholder");
368 ci.key = QStringLiteral("Color Template Not Editable Placeholder");
369 ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
370 items.append(ci);
371
372 //
373 // finally, add all elements
374 //
375 return items;
376}
377
378void 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(newSchema);
382 ui->setReadOnly(!theme.isValid() || theme.isReadOnly());
383
384 // save current schema
385 if (!m_currentSchema.isEmpty()) {
386 auto it = m_schemas.find(m_currentSchema);
387 if (it != m_schemas.end()) {
388 m_schemas.erase(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(newSchema) == m_schemas.end()) {
404 QList<KateColorItem> items = colorItemList(theme);
405 for (auto &item : items) {
406 item.color = QColor::fromRgba(theme.editorColor(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(true);
413
414 ui->clear();
415 ui->addColorItems(m_schemas[m_currentSchema]);
416
417 blockSignals(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 */
424static QString hexName(const QColor &c)
425{
426 return c.alpha() == 0xFF ? c.name() : c.name(QColor::HexArgb);
427}
428
429void KateThemeConfigColorTab::apply()
430{
431 schemaChanged(m_currentSchema);
432
433 // we use meta-data of enum for computing the json keys
434 static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator("EditorColorRole");
435 Q_ASSERT(idx >= 0);
436 const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(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(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(item.role))] = hexName(c);
455 }
456 newThemeObject[QLatin1String("editor-colors")] = colors;
457
458 // write json back to file
459 writeJson(newThemeObject, theme.filePath());
460 }
461
462 // all colors are written, so throw away all cached schemas
463 m_schemas.clear();
464}
465
466void 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(backupName);
475}
476
477QColor KateThemeConfigColorTab::backgroundColor() const
478{
479 return ui->findColor(QStringLiteral("Color Background"));
480}
481
482QColor KateThemeConfigColorTab::selectionColor() const
483{
484 return ui->findColor(QStringLiteral("Color Selection"));
485}
486// END KateThemeConfigColorTab
487
488// BEGIN FontColorConfig -- 'Normal Text Styles' tab
489KateThemeConfigDefaultStylesTab::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(m_defaultStyles, &KateStyleTreeWidget::changed, this, &KateThemeConfigDefaultStylesTab::changed);
498 grid->addWidget(m_defaultStyles, 0, 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
509KateAttributeList *KateThemeConfigDefaultStylesTab::attributeList(const QString &schema)
510{
511 auto it = m_defaultStyleLists.find(schema);
512 if (it == m_defaultStyleLists.end()) {
513 // get list of all default styles
515 KateAttributeList list;
516 list.reserve(numStyles);
517 const KSyntaxHighlighting::Theme currentTheme = KateHlManager::self()->repository().theme(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(col));
524 }
525
526 if (const auto col = currentTheme.selectedTextColor(style)) {
527 i->setSelectedForeground(QColor::fromRgba(col));
528 }
529
530 if (const auto col = currentTheme.backgroundColor(style)) {
531 i->setBackground(QColor::fromRgba(col));
532 } else {
533 i->clearBackground();
534 }
535
536 if (const auto col = currentTheme.selectedBackgroundColor(style)) {
537 i->setSelectedBackground(QColor::fromRgba(col));
538 } else {
539 i->clearProperty(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(i);
547 }
548 it = m_defaultStyleLists.emplace(schema, list).first;
549 }
550
551 return &(it->second);
552}
553
554void 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(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(l->at(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, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), 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, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), 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, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), 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, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), 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, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(i)), l->at(i));
600 }
601
602 m_defaultStyles->expandAll();
603}
604
605void KateThemeConfigDefaultStylesTab::updateColorPalette(const QColor &textColor)
606{
607 QPalette p(m_defaultStyles->palette());
608 p.setColor(QPalette::Base, m_colorTab->backgroundColor());
609 p.setColor(QPalette::Highlight, m_colorTab->selectionColor());
610 p.setColor(QPalette::Text, textColor);
611 m_defaultStyles->setPalette(p);
612}
613
614void KateThemeConfigDefaultStylesTab::reload()
615{
616 m_defaultStyles->clear();
617 m_defaultStyleLists.clear();
618
619 schemaChanged(m_currentSchema);
620}
621
622void KateThemeConfigDefaultStylesTab::apply()
623{
624 // get enum meta data for json keys
625 static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator("TextStyle");
626 Q_ASSERT(idx >= 0);
627 const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(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(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;
643 for (int z = 0; z < numStyles; z++) {
644 QJsonObject style;
645 KTextEditor::Attribute::Ptr p = kv.second.at(z);
646 if (p->hasProperty(QTextFormat::ForegroundBrush)) {
647 style[QLatin1String("text-color")] = hexName(p->foreground().color());
648 }
649 if (p->hasProperty(QTextFormat::BackgroundBrush)) {
650 style[QLatin1String("background-color")] = hexName(p->background().color());
651 }
652 if (p->hasProperty(SelectedForeground)) {
653 style[QLatin1String("selected-text-color")] = hexName(p->selectedForeground().color());
654 }
655 if (p->hasProperty(SelectedBackground)) {
656 style[QLatin1String("selected-background-color")] = hexName(p->selectedBackground().color());
657 }
658 if (p->hasProperty(QTextFormat::FontWeight) && p->fontBold()) {
659 style[QLatin1String("bold")] = true;
660 }
661 if (p->hasProperty(QTextFormat::FontItalic) && p->fontItalic()) {
662 style[QLatin1String("italic")] = true;
663 }
664 if (p->hasProperty(QTextFormat::TextUnderlineStyle) && p->fontUnderline()) {
665 style[QLatin1String("underline")] = true;
666 }
667 if (p->hasProperty(QTextFormat::FontStrikeOut) && p->fontStrikeOut()) {
668 style[QLatin1String("strike-through")] = true;
669 }
670 styles[QLatin1String(metaEnum.key((z)))] = style;
671 }
672 newThemeObject[QLatin1String("text-styles")] = styles;
673
674 // write json back to file
675 writeJson(newThemeObject, theme.filePath());
676 }
677}
678
679void KateThemeConfigDefaultStylesTab::showEvent(QShowEvent *event)
680{
681 if (!event->spontaneous() && !m_currentSchema.isEmpty()) {
682 KateAttributeList *l = attributeList(m_currentSchema);
683 Q_ASSERT(l != nullptr);
684 updateColorPalette(l->at(0)->foreground().color());
685 }
686
688}
689// END FontColorConfig
690
691// BEGIN KateThemeConfigHighlightTab -- 'Highlighting Text Styles' tab
692KateThemeConfigHighlightTab::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 *headerLayout = new QHBoxLayout;
702 layout->addLayout(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(hlCombo, &QComboBox::activated, this, &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(section + QLatin1Char('/') + hl.translatedName());
721 } else {
722 hlCombo->addItem(hl.translatedName());
723 }
724 }
725 hlCombo->setCurrentIndex(0);
726
727 // styles listview
728 m_styles = new KateStyleTreeWidget(this, true);
729 connect(m_styles, &KateStyleTreeWidget::changed, this, &KateThemeConfigHighlightTab::changed);
730 layout->addWidget(m_styles, 999);
731
732 // get current highlighting from the host application
733 int hl = 0;
734 KTextEditor::ViewPrivate *kv =
735 qobject_cast<KTextEditor::ViewPrivate *>(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView());
736 if (kv) {
737 const QString hlName = kv->doc()->highlight()->name();
738 hl = KateHlManager::self()->nameFind(hlName);
739 Q_ASSERT(hl >= 0);
740 }
741
742 hlCombo->setCurrentIndex(hl);
743 hlChanged(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>&lt;SPACE&gt;</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
755void KateThemeConfigHighlightTab::hlChanged(int z)
756{
757 m_hl = z;
758 schemaChanged(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 */
765static 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(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(invalidTheme));
778 }
779 if (format.hasBackgroundColorOverride()) {
780 newAttribute->setBackground(format.backgroundColor(invalidTheme));
781 }
782 if (format.hasSelectedTextColorOverride()) {
783 newAttribute->setSelectedForeground(format.selectedTextColor(invalidTheme));
784 }
785 if (format.hasSelectedBackgroundColorOverride()) {
786 newAttribute->setSelectedBackground(format.selectedBackgroundColor(invalidTheme));
787 }
788 if (format.hasBoldOverride()) {
789 newAttribute->setFontBold(format.isBold(invalidTheme));
790 }
791 if (format.hasItalicOverride()) {
792 newAttribute->setFontItalic(format.isItalic(invalidTheme));
793 }
794 if (format.hasUnderlineOverride()) {
795 newAttribute->setFontUnderline(format.isUnderline(invalidTheme));
796 }
797 if (format.hasStrikeThroughOverride()) {
798 newAttribute->setFontStrikeOut(format.isStrikeThrough(invalidTheme));
799 }
800
801 // not really relevant, set it as configured
802 newAttribute->setSkipSpellChecking(format.spellCheck());
803 defaults.append(newAttribute);
804 }
805 return defaults;
806}
807
808void 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(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(m_schema);
823 if (it == m_hlDict.end()) {
824 it = m_hlDict.insert(schema, QHash<int, QList<KTextEditor::Attribute::Ptr>>());
825 }
826
827 // Set listview colors
828 KateAttributeList *l = m_defaults->attributeList(schema);
829 updateColorPalette(l->at(0)->foreground().color());
830
831 // create unified stuff
832 auto attributes = KateHlManager::self()->getHl(m_hl)->attributesForDefinition(m_schema);
833 auto formats = KateHlManager::self()->getHl(m_hl)->formats();
834 auto defaults = defaultsForHighlighting(formats, *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(QLatin1Char(':'));
840 if (c <= 0) {
841 continue;
842 }
843
844 QString highlighting = attributes[i]->name().left(c);
845 QString name = attributes[i]->name().mid(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(m_hl);
864 if (it1 == subMap.end()) {
865 it1 = subMap.insert(m_hl, 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(QLatin1Char(':'));
879 if (c > 0) {
880 QString prefix = itemData->name().left(c);
881 QString name = itemData->name().mid(c + 1);
882
883 QTreeWidgetItem *parent = prefixes[prefix];
884 if (!parent) {
885 parent = new QTreeWidgetItem(m_styles, QStringList() << prefix);
886 m_styles->expandItem(parent);
887 prefixes.insert(prefix, parent);
888 }
889 m_styles->addItem(parent, name, defaults.at(i), itemData);
890 } else {
891 m_styles->addItem(itemData->name(), defaults.at(i), itemData);
892 }
893 ++vec_it;
894 ++i;
895 }
896
897 m_styles->resizeColumns();
898}
899
900void KateThemeConfigHighlightTab::updateColorPalette(const QColor &textColor)
901{
902 QPalette p(m_styles->palette());
903 p.setColor(QPalette::Base, m_colorTab->backgroundColor());
904 p.setColor(QPalette::Highlight, m_colorTab->selectionColor());
905 p.setColor(QPalette::Text, textColor);
906 m_styles->setPalette(p);
907}
908
909void KateThemeConfigHighlightTab::reload()
910{
911 m_styles->clear();
912
913 m_hlDict.clear();
914 m_uniqueAttributes.clear();
915
916 hlChanged(hlCombo->currentIndex());
917}
918
919void 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(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(p->foreground().color());
945 }
946 if (p->background().color() != pDefault->background().color()) {
947 style[QLatin1String("background-color")] = hexName(p->background().color());
948 }
949 if (p->selectedForeground().color() != pDefault->selectedForeground().color()) {
950 style[QLatin1String("selected-text-color")] = hexName(p->selectedForeground().color());
951 }
952 if (p->selectedBackground().color() != pDefault->selectedBackground().color()) {
953 style[QLatin1String("selected-background-color")] = hexName(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(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(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(newThemeObject, theme.filePath());
989 }
990}
991
992QList<int> KateThemeConfigHighlightTab::hlsForSchema(const QString &schema)
993{
994 auto it = m_hlDict.find(schema);
995 if (it != m_hlDict.end()) {
996 return it.value().keys();
997 }
998 return {};
999}
1000
1001void KateThemeConfigHighlightTab::showEvent(QShowEvent *event)
1002{
1003 if (!event->spontaneous()) {
1004 KateAttributeList *l = m_defaults->attributeList(m_schema);
1005 Q_ASSERT(l != nullptr);
1006 updateColorPalette(l->at(0)->foreground().color());
1007 }
1008
1010}
1011// END KateThemeConfigHighlightTab
1012
1013// BEGIN KateThemeConfigPage -- Main dialog page
1014KateThemeConfigPage::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(themeChooser, i18n("Default Theme"));
1027 tabWidget->addTab(themeEditor, i18n("Theme Editor"));
1028 layoutThemeChooserTab(themeChooser);
1029 layoutThemeEditorTab(themeEditor);
1030
1031 reload();
1032}
1033
1034void KateThemeConfigPage::layoutThemeChooserTab(QWidget *tab)
1035{
1036 QVBoxLayout *layout = new QVBoxLayout(tab);
1037 layout->setContentsMargins({});
1038
1039 auto *comboLayout = new QHBoxLayout;
1040 comboLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
1041 style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1042 style()->pixelMetric(QStyle::PM_LayoutRightMargin),
1043 0);
1044
1045 auto lHl = new QLabel(i18n("Select theme:"), this);
1046 comboLayout->addWidget(lHl);
1047
1048 defaultSchemaCombo = new QComboBox(this);
1049 comboLayout->addWidget(defaultSchemaCombo);
1050 defaultSchemaCombo->setEditable(false);
1051 lHl->setBuddy(defaultSchemaCombo);
1052 connect(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::slotChanged);
1053 comboLayout->addStretch();
1054
1055 layout->addLayout(comboLayout);
1056
1057 m_doc = new KTextEditor::DocumentPrivate;
1058 m_doc->setParent(this);
1059
1060 const auto code = R"sample(/**
1061* SPDX-FileCopyrightText: 2020 Christoph Cullmann <cullmann@kde.org>
1062* SPDX-License-Identifier: MIT
1063*/
1064
1065// BEGIN
1066#include <QString>
1067#include <string>
1068// END
1069
1070/**
1071* TODO: improve documentation
1072* @param magicArgument some magic argument
1073* @return magic return value
1074*/
1075int main(uint64_t magicArgument)
1076{
1077 if (magicArgument > 1) {
1078 const std::string string = "source file: \"" __FILE__ "\"";
1079 const QString qString(QStringLiteral("test"));
1080 return qrand();
1081 }
1082
1083 /* BUG: bogus integer constant inside next line */
1084 const double g = 1.1e12 * 0b01'01'01'01 - 43a + 0x11234 * 0234ULL - 'c' * 42;
1085 return g > 1.3f;
1086})sample";
1087
1088 m_doc->setText(QString::fromUtf8(code));
1089 m_doc->setHighlightingMode(QStringLiteral("C++"));
1090 m_themePreview = new KTextEditor::ViewPrivate(m_doc, this);
1091
1092 layout->addWidget(m_themePreview);
1093
1094 connect(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, [this](int idx) {
1095 const QString schema = defaultSchemaCombo->itemData(idx).toString();
1096 m_themePreview->rendererConfig()->setSchema(schema);
1097 if (schema.isEmpty()) {
1098 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1099 } else {
1100 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1101 }
1102 });
1103}
1104
1105void KateThemeConfigPage::layoutThemeEditorTab(QWidget *tab)
1106{
1107 QVBoxLayout *layout = new QVBoxLayout(tab);
1108 layout->setContentsMargins(0, 0, 0, 0);
1109
1110 // header
1111 QHBoxLayout *headerLayout = new QHBoxLayout;
1112 headerLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
1113 style()->pixelMetric(QStyle::PM_LayoutTopMargin),
1114 style()->pixelMetric(QStyle::PM_LayoutRightMargin),
1115 0);
1116 layout->addLayout(headerLayout);
1117
1118 QLabel *lHl = new QLabel(i18n("&Theme:"), this);
1119 headerLayout->addWidget(lHl);
1120
1121 schemaCombo = new QComboBox(this);
1122 schemaCombo->setEditable(false);
1123 lHl->setBuddy(schemaCombo);
1124 headerLayout->addWidget(schemaCombo);
1125 connect(schemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::comboBoxIndexChanged);
1126
1127 auto *copyButton = new QPushButton(i18nc("@action:button", "&Copy…"), this);
1128 headerLayout->addWidget(copyButton);
1129 connect(copyButton, &QPushButton::clicked, this, &KateThemeConfigPage::copyTheme);
1130
1131 btndel = new QPushButton(i18n("&Delete"), this);
1132 headerLayout->addWidget(btndel);
1133 connect(btndel, &QPushButton::clicked, this, &KateThemeConfigPage::deleteSchema);
1134
1135 auto *btnexport = new QPushButton(i18nc("@action:button", "Export…"), this);
1136 headerLayout->addWidget(btnexport);
1137 connect(btnexport, &QPushButton::clicked, this, &KateThemeConfigPage::exportFullSchema);
1138
1139 auto *btnimport = new QPushButton(i18nc("@action:button", "Import…"), this);
1140 headerLayout->addWidget(btnimport);
1141 connect(btnimport, &QPushButton::clicked, this, &KateThemeConfigPage::importFullSchema);
1142
1143 headerLayout->addStretch();
1144
1145 // label to inform about read-only state
1146 m_readOnlyThemeLabel = new KMessageWidget(i18n("Bundled read-only theme. To modify the theme, please copy it."), this);
1147 m_readOnlyThemeLabel->setCloseButtonVisible(false);
1148 m_readOnlyThemeLabel->setMessageType(KMessageWidget::Information);
1149 m_readOnlyThemeLabel->hide();
1150 layout->addWidget(m_readOnlyThemeLabel);
1151
1152 // tabs
1153 QTabWidget *tabWidget = new QTabWidget(this);
1154 layout->addWidget(tabWidget);
1155
1156 m_colorTab = new KateThemeConfigColorTab();
1157 tabWidget->addTab(m_colorTab, i18n("Colors"));
1158 connect(m_colorTab, &KateThemeConfigColorTab::changed, this, &KateThemeConfigPage::slotChanged);
1159
1160 m_defaultStylesTab = new KateThemeConfigDefaultStylesTab(m_colorTab);
1161 tabWidget->addTab(m_defaultStylesTab, i18n("Default Text Styles"));
1162 connect(m_defaultStylesTab, &KateThemeConfigDefaultStylesTab::changed, this, &KateThemeConfigPage::slotChanged);
1163
1164 m_highlightTab = new KateThemeConfigHighlightTab(m_defaultStylesTab, m_colorTab);
1165 tabWidget->addTab(m_highlightTab, i18n("Highlighting Text Styles"));
1166 connect(m_highlightTab, &KateThemeConfigHighlightTab::changed, this, &KateThemeConfigPage::slotChanged);
1167
1168 QHBoxLayout *footLayout = new QHBoxLayout;
1169 layout->addLayout(footLayout);
1170}
1171
1172void KateThemeConfigPage::exportFullSchema()
1173{
1174 // get save destination
1175 const QString currentSchemaName = m_currentSchema;
1176 const QString destName = QFileDialog::getSaveFileName(this,
1177 i18n("Exporting color theme: %1", currentSchemaName),
1178 currentSchemaName + QLatin1String(".theme"),
1179 QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1180 if (destName.isEmpty()) {
1181 return;
1182 }
1183
1184 // get current theme
1185 const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1186 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1187
1188 // ensure we overwrite
1189 if (QFile::exists(destName)) {
1190 QFile::remove(destName);
1191 }
1192
1193 // export is easy, just copy the file 1:1
1194 QFile::copy(currentTheme.filePath(), destName);
1195}
1196
1197void KateThemeConfigPage::importFullSchema()
1198{
1199 const QString srcName =
1200 QFileDialog::getOpenFileName(this, i18n("Importing Color Theme"), QString(), QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1201 if (srcName.isEmpty()) {
1202 return;
1203 }
1204
1205 // location to write theme files to
1206 const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1207
1208 // construct file name for imported theme
1209 const QString themesFullFileName = themesPath + QStringLiteral("/") + QFileInfo(srcName).fileName();
1210
1211 // if something might be overwritten, as the user
1212 if (QFile::exists(themesFullFileName)) {
1214 i18n("Importing will overwrite the existing theme file \"%1\". This can not be undone.", themesFullFileName),
1215 i18n("Possible Data Loss"),
1216 KGuiItem(i18n("Import Nevertheless")),
1219 return;
1220 }
1221 }
1222
1223 // copy theme file, we might need to create the local dir first
1224 QDir().mkpath(themesPath);
1225
1226 // ensure we overwrite
1227 if (QFile::exists(themesFullFileName)) {
1228 QFile::remove(themesFullFileName);
1229 }
1230 QFile::copy(srcName, themesFullFileName);
1231
1232 // reload themes DB & clear all attributes
1233 KateHlManager::self()->reload();
1234 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1235 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1236 }
1237
1238 // KateThemeManager::update() sorts the schema alphabetically, hence the
1239 // schema indexes change. Thus, repopulate the schema list...
1240 refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1241}
1242
1243void KateThemeConfigPage::apply()
1244{
1245 // remember name + index
1246 const QString schemaName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1247
1248 // first apply all tabs
1249 m_colorTab->apply();
1250 m_defaultStylesTab->apply();
1251 m_highlightTab->apply();
1252
1253 // reload themes DB & clear all attributes
1254 KateHlManager::self()->reload();
1255 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1256 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1257 }
1258
1259 // than reload the whole stuff, special handle auto selection == empty theme name
1260 const auto defaultTheme = defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString();
1261 if (defaultTheme.isEmpty()) {
1262 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1263 } else {
1264 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1265 KateRendererConfig::global()->setSchema(defaultTheme);
1266 }
1267 KateRendererConfig::global()->reloadSchema();
1268
1269 // KateThemeManager::update() sorts the schema alphabetically, hence the
1270 // schema indexes change. Thus, repopulate the schema list...
1271 refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1272 schemaChanged(schemaName);
1273
1274 // all tabs need to reload to discard all the cached data, as the index
1275 // mapping may have changed
1276 m_colorTab->reload();
1277 m_defaultStylesTab->reload();
1278 m_highlightTab->reload();
1279}
1280
1281void KateThemeConfigPage::reload()
1282{
1283 // reinitialize combo boxes
1284 refillCombos(KateRendererConfig::global()->schema(), KateRendererConfig::global()->schema());
1285
1286 // finally, activate the current schema again
1287 schemaChanged(schemaCombo->itemData(schemaCombo->currentIndex()).toString());
1288
1289 // all tabs need to reload to discard all the cached data, as the index
1290 // mapping may have changed
1291 m_colorTab->reload();
1292 m_defaultStylesTab->reload();
1293 m_highlightTab->reload();
1294}
1295
1296void KateThemeConfigPage::refillCombos(const QString &schemaName, const QString &defaultSchemaName)
1297{
1298 schemaCombo->blockSignals(true);
1299 defaultSchemaCombo->blockSignals(true);
1300
1301 // reinitialize combo boxes
1302 schemaCombo->clear();
1303 defaultSchemaCombo->clear();
1304 defaultSchemaCombo->addItem(i18n("Follow System Color Scheme"), QString());
1305 defaultSchemaCombo->insertSeparator(1);
1306 const auto themes = KateHlManager::self()->sortedThemes();
1307 for (const auto &theme : themes) {
1308 schemaCombo->addItem(theme.translatedName(), theme.name());
1309 defaultSchemaCombo->addItem(theme.translatedName(), theme.name());
1310 }
1311
1312 // set the correct indexes again, fallback to always existing default theme
1313 int schemaIndex = schemaCombo->findData(schemaName);
1314 if (schemaIndex == -1) {
1315 schemaIndex = schemaCombo->findData(
1316 KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name());
1317 }
1318
1319 // set the correct indexes again, fallback to auto-selection
1320 int defaultSchemaIndex = 0;
1321 if (!KateRendererConfig::global()->value(KateRendererConfig::AutoColorThemeSelection).toBool()) {
1322 defaultSchemaIndex = defaultSchemaCombo->findData(defaultSchemaName);
1323 if (defaultSchemaIndex == -1) {
1324 defaultSchemaIndex = 0;
1325 }
1326 }
1327
1328 Q_ASSERT(schemaIndex != -1);
1329 Q_ASSERT(defaultSchemaIndex != -1);
1330
1331 defaultSchemaCombo->setCurrentIndex(defaultSchemaIndex);
1332 schemaCombo->setCurrentIndex(schemaIndex);
1333
1334 schemaCombo->blockSignals(false);
1335 defaultSchemaCombo->blockSignals(false);
1336
1337 m_themePreview->rendererConfig()->setSchema(defaultSchemaName);
1338}
1339
1340void KateThemeConfigPage::reset()
1341{
1342 // reload themes DB & clear all attributes
1343 KateHlManager::self()->reload();
1344 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1345 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1346 }
1347
1348 // reload the view
1349 reload();
1350}
1351
1352void KateThemeConfigPage::defaults()
1353{
1354 reset();
1355}
1356
1357void KateThemeConfigPage::deleteSchema()
1358{
1359 const int comboIndex = schemaCombo->currentIndex();
1360 const QString schemaNameToDelete = schemaCombo->itemData(comboIndex).toString();
1361
1362 // KSyntaxHighlighting themes can not be deleted, skip invalid themes, too
1363 const auto theme = KateHlManager::self()->repository().theme(schemaNameToDelete);
1364 if (!theme.isValid() || theme.isReadOnly()) {
1365 return;
1366 }
1367
1368 // ask the user again, this can't be undone
1370 i18n("Do you really want to delete the theme \"%1\"? This can not be undone.", schemaNameToDelete),
1371 i18n("Possible Data Loss"),
1372 KGuiItem(i18n("Delete Nevertheless")),
1375 return;
1376 }
1377
1378 // purge the theme file
1379 QFile::remove(theme.filePath());
1380
1381 // reset syntax manager repo to flush deleted theme
1382 KateHlManager::self()->reload();
1383
1384 // fallback to Default schema + auto
1385 schemaCombo->setCurrentIndex(schemaCombo->findData(
1386 QVariant(KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name())));
1387 if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(schemaNameToDelete)) {
1388 defaultSchemaCombo->setCurrentIndex(0);
1389 }
1390
1391 // remove schema from combo box
1392 schemaCombo->removeItem(comboIndex);
1393 defaultSchemaCombo->removeItem(comboIndex);
1394
1395 // Reload the color tab, since it uses cached schemas
1396 m_colorTab->reload();
1397}
1398
1399bool KateThemeConfigPage::copyTheme()
1400{
1401 // get current theme data as template
1402 const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1403 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1404
1405 // location to write theme files to
1406 const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1407
1408 // get sane name
1409 QString schemaName;
1410 QString themeFileName;
1411 while (schemaName.isEmpty()) {
1412 QInputDialog newNameDialog(this);
1413 newNameDialog.setInputMode(QInputDialog::TextInput);
1414 newNameDialog.setWindowTitle(i18n("Copy theme"));
1415 newNameDialog.setLabelText(i18n("Name for copy of color theme \"%1\":", currentThemeName));
1416 newNameDialog.setTextValue(currentThemeName);
1417 if (newNameDialog.exec() == QDialog::Rejected) {
1418 return false;
1419 }
1420 schemaName = newNameDialog.textValue();
1421
1422 // try if schema already around => if yes, retry name input
1423 // we try for duplicated file names, too
1424 themeFileName = themesPath + QStringLiteral("/") + schemaName + QStringLiteral(".theme");
1425 if (KateHlManager::self()->repository().theme(schemaName).isValid() || QFile::exists(themeFileName)) {
1427 i18n("<p>The theme \"%1\" already exists.</p><p>Please choose a different theme name.</p>", schemaName),
1428 i18n("Copy Theme"));
1429 schemaName.clear();
1430 }
1431 }
1432
1433 // get json for current theme
1434 QJsonObject newThemeObject = jsonForTheme(currentTheme);
1435 QJsonObject metaData;
1436 metaData[QLatin1String("revision")] = 1;
1437 metaData[QLatin1String("name")] = schemaName;
1438 newThemeObject[QLatin1String("metadata")] = metaData;
1439
1440 // write to new theme file, we might need to create the local dir first
1441 QDir().mkpath(themesPath);
1442 if (!writeJson(newThemeObject, themeFileName)) {
1443 return false;
1444 }
1445
1446 // reset syntax manager repo to find new theme
1447 KateHlManager::self()->reload();
1448
1449 // append items to combo boxes
1450 schemaCombo->addItem(schemaName, QVariant(schemaName));
1451 defaultSchemaCombo->addItem(schemaName, QVariant(schemaName));
1452
1453 // finally, activate new schema (last item in the list)
1454 schemaCombo->setCurrentIndex(schemaCombo->count() - 1);
1455 return true;
1456}
1457
1458void KateThemeConfigPage::schemaChanged(const QString &schema)
1459{
1460 // we can't delete read-only themes, e.g. the stuff shipped inside Qt resources or system wide installed
1461 const auto theme = KateHlManager::self()->repository().theme(schema);
1462 btndel->setEnabled(!theme.isReadOnly());
1463 m_readOnlyThemeLabel->setVisible(theme.isReadOnly());
1464
1465 // propagate changed schema to all tabs
1466 m_colorTab->schemaChanged(schema);
1467 m_defaultStylesTab->schemaChanged(schema);
1468 m_highlightTab->schemaChanged(schema);
1469
1470 // save current schema index
1471 m_currentSchema = schema;
1472}
1473
1474void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex)
1475{
1476 schemaChanged(schemaCombo->itemData(currentIndex).toString());
1477}
1478
1479QString KateThemeConfigPage::name() const
1480{
1481 return i18n("Color Themes");
1482}
1483
1484QString KateThemeConfigPage::fullName() const
1485{
1486 return i18n("Color Themes");
1487}
1488
1489QIcon KateThemeConfigPage::icon() const
1490{
1491 return QIcon::fromTheme(QStringLiteral("preferences-desktop-color"));
1492}
1493
1494// END KateThemeConfigPage
1495
1496#include "moc_katethemeconfig.cpp"
Q_INVOKABLE KSyntaxHighlighting::Theme theme(const QString &themeName) const
bool isStrikeThrough(TextStyle style) const
QString translatedName() const
QRgb textColor(TextStyle style) const
QString filePath() const
bool isBold(TextStyle style) const
QRgb selectedTextColor(TextStyle style) const
QRgb selectedBackgroundColor(TextStyle style) const
bool isUnderline(TextStyle style) const
bool isItalic(TextStyle style) const
QRgb backgroundColor(TextStyle style) const
QRgb editorColor(EditorColorRole role) const
A class which provides customized text decorations.
Definition attribute.h:51
QExplicitlySharedDataPointer< Attribute > Ptr
Shared data pointer for Attribute.
Definition attribute.h:56
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
bool setValue(const int key, const QVariant &value)
Set a config value.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString name(const QVariant &location)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
bool isValid(QStringView ifopt)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem cancel()
KGuiItem defaults()
const QList< QKeySequence > & reload()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
void clicked(bool checked)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
int alpha() const const
QColor fromRgba(QRgb rgba)
QString name(NameFormat format) const const
void activated(int index)
void currentIndexChanged(int index)
bool copy(const QString &fileName, const QString &newName)
bool exists() const const
bool remove()
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
void addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment)
void setColumnStretch(int column, int stretch)
iterator insert(const Key &key, const T &value)
QIcon fromTheme(const QString &name)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
bool isEmpty() const const
void remove(QLatin1StringView key)
void setBuddy(QWidget *buddy)
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void reserve(qsizetype size)
QMetaEnum fromType()
int keyCount() const const
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
QObject * parent() const const
T qobject_cast(QObject *object)
QString writableLocation(StandardLocation type)
void clear()
QString first(qsizetype n) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
PM_LayoutLeftMargin
int addTab(QWidget *page, const QIcon &icon, const QString &label)
void setDocumentMode(bool set)
QLayout * layout() const const
virtual void showEvent(QShowEvent *event)
QStyle * style() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:52:52 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.