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
514 const auto numStyles = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount();
516 list.reserve(numStyles);
517 const KSyntaxHighlighting::Theme currentTheme = KateHlManager::self()->repository().theme(schema);
518 for (int z = 0; z < numStyles; z++) {
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;
642 const auto numStyles = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount();
643 for (int z = 0; z < numStyles; z++) {
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;
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
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
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);
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(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::slotChanged);
1049 comboLayout->addStretch();
1050
1051 layout->addLayout(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*/
1071int 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(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(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, [this](int idx) {
1091 const QString schema = defaultSchemaCombo->itemData(idx).toString();
1092 m_themePreview->rendererConfig()->setSchema(schema);
1093 if (schema.isEmpty()) {
1094 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1095 } else {
1096 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1097 }
1098 });
1099}
1100
1101void KateThemeConfigPage::layoutThemeEditorTab(QWidget *tab)
1102{
1103 QVBoxLayout *layout = new QVBoxLayout(tab);
1104 layout->setContentsMargins(0, 0, 0, 0);
1105
1106 // header
1107 QHBoxLayout *headerLayout = new QHBoxLayout;
1108 layout->addLayout(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(schemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::comboBoxIndexChanged);
1118
1119 QPushButton *copyButton = new QPushButton(i18n("&Copy..."), this);
1120 headerLayout->addWidget(copyButton);
1121 connect(copyButton, &QPushButton::clicked, this, &KateThemeConfigPage::copyTheme);
1122
1123 btndel = new QPushButton(i18n("&Delete"), this);
1124 headerLayout->addWidget(btndel);
1125 connect(btndel, &QPushButton::clicked, this, &KateThemeConfigPage::deleteSchema);
1126
1127 QPushButton *btnexport = new QPushButton(i18n("Export..."), this);
1128 headerLayout->addWidget(btnexport);
1129 connect(btnexport, &QPushButton::clicked, this, &KateThemeConfigPage::exportFullSchema);
1130
1131 QPushButton *btnimport = new QPushButton(i18n("Import..."), this);
1132 headerLayout->addWidget(btnimport);
1133 connect(btnimport, &QPushButton::clicked, this, &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(m_colorTab, i18n("Colors"));
1150 connect(m_colorTab, &KateThemeConfigColorTab::changed, this, &KateThemeConfigPage::slotChanged);
1151
1152 m_defaultStylesTab = new KateThemeConfigDefaultStylesTab(m_colorTab);
1153 tabWidget->addTab(m_defaultStylesTab, i18n("Default Text Styles"));
1154 connect(m_defaultStylesTab, &KateThemeConfigDefaultStylesTab::changed, this, &KateThemeConfigPage::slotChanged);
1155
1156 m_highlightTab = new KateThemeConfigHighlightTab(m_defaultStylesTab, m_colorTab);
1157 tabWidget->addTab(m_highlightTab, i18n("Highlighting Text Styles"));
1158 connect(m_highlightTab, &KateThemeConfigHighlightTab::changed, this, &KateThemeConfigPage::slotChanged);
1159
1160 QHBoxLayout *footLayout = new QHBoxLayout;
1161 layout->addLayout(footLayout);
1162}
1163
1164void KateThemeConfigPage::exportFullSchema()
1165{
1166 // get save destination
1167 const QString currentSchemaName = m_currentSchema;
1168 const QString destName = QFileDialog::getSaveFileName(this,
1169 i18n("Exporting color theme: %1", currentSchemaName),
1170 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(schemaCombo->currentIndex()).toString();
1178 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1179
1180 // ensure we overwrite
1181 if (QFile::exists(destName)) {
1182 QFile::remove(destName);
1183 }
1184
1185 // export is easy, just copy the file 1:1
1186 QFile::copy(currentTheme.filePath(), destName);
1187}
1188
1189void KateThemeConfigPage::importFullSchema()
1190{
1191 const QString srcName =
1192 QFileDialog::getOpenFileName(this, i18n("Importing Color Theme"), 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(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(themesFullFileName)) {
1206 i18n("Importing will overwrite the existing theme file \"%1\". This can not be undone.", themesFullFileName),
1207 i18n("Possible Data Loss"),
1208 KGuiItem(i18n("Import Nevertheless")),
1211 return;
1212 }
1213 }
1214
1215 // copy theme file, we might need to create the local dir first
1216 QDir().mkpath(themesPath);
1217
1218 // ensure we overwrite
1219 if (QFile::exists(themesFullFileName)) {
1220 QFile::remove(themesFullFileName);
1221 }
1222 QFile::copy(srcName, 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(i)->clearAttributeArrays();
1228 }
1229
1230 // KateThemeManager::update() sorts the schema alphabetically, hence the
1231 // schema indexes change. Thus, repopulate the schema list...
1232 refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1233}
1234
1235void KateThemeConfigPage::apply()
1236{
1237 // remember name + index
1238 const QString schemaName = schemaCombo->itemData(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(i)->clearAttributeArrays();
1249 }
1250
1251 // than reload the whole stuff, special handle auto selection == empty theme name
1252 const auto defaultTheme = defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString();
1253 if (defaultTheme.isEmpty()) {
1254 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1255 } else {
1256 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, 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(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1264 schemaChanged(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
1273void KateThemeConfigPage::reload()
1274{
1275 // reinitialize combo boxes
1276 refillCombos(KateRendererConfig::global()->schema(), KateRendererConfig::global()->schema());
1277
1278 // finally, activate the current schema again
1279 schemaChanged(schemaCombo->itemData(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
1288void KateThemeConfigPage::refillCombos(const QString &schemaName, const QString &defaultSchemaName)
1289{
1290 schemaCombo->blockSignals(true);
1291 defaultSchemaCombo->blockSignals(true);
1292
1293 // reinitialize combo boxes
1294 schemaCombo->clear();
1295 defaultSchemaCombo->clear();
1296 defaultSchemaCombo->addItem(i18n("Follow System Color Scheme"), QString());
1297 defaultSchemaCombo->insertSeparator(1);
1298 const auto themes = KateHlManager::self()->sortedThemes();
1299 for (const auto &theme : themes) {
1300 schemaCombo->addItem(theme.translatedName(), theme.name());
1301 defaultSchemaCombo->addItem(theme.translatedName(), theme.name());
1302 }
1303
1304 // set the correct indexes again, fallback to always existing default theme
1305 int schemaIndex = schemaCombo->findData(schemaName);
1306 if (schemaIndex == -1) {
1307 schemaIndex = schemaCombo->findData(
1308 KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(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(KateRendererConfig::AutoColorThemeSelection).toBool()) {
1314 defaultSchemaIndex = defaultSchemaCombo->findData(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(false);
1327 defaultSchemaCombo->blockSignals(false);
1328
1329 m_themePreview->rendererConfig()->setSchema(defaultSchemaName);
1330}
1331
1332void 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(i)->clearAttributeArrays();
1338 }
1339
1340 // reload the view
1341 reload();
1342}
1343
1344void KateThemeConfigPage::defaults()
1345{
1346 reset();
1347}
1348
1349void KateThemeConfigPage::deleteSchema()
1350{
1351 const int comboIndex = schemaCombo->currentIndex();
1352 const QString schemaNameToDelete = schemaCombo->itemData(comboIndex).toString();
1353
1354 // KSyntaxHighlighting themes can not be deleted, skip invalid themes, too
1355 const auto theme = KateHlManager::self()->repository().theme(schemaNameToDelete);
1356 if (!theme.isValid() || theme.isReadOnly()) {
1357 return;
1358 }
1359
1360 // ask the user again, this can't be undone
1362 i18n("Do you really want to delete the theme \"%1\"? This can not be undone.", schemaNameToDelete),
1363 i18n("Possible Data Loss"),
1364 KGuiItem(i18n("Delete Nevertheless")),
1367 return;
1368 }
1369
1370 // purge the theme file
1371 QFile::remove(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 QVariant(KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name())));
1379 if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(schemaNameToDelete)) {
1380 defaultSchemaCombo->setCurrentIndex(0);
1381 }
1382
1383 // remove schema from combo box
1384 schemaCombo->removeItem(comboIndex);
1385 defaultSchemaCombo->removeItem(comboIndex);
1386
1387 // Reload the color tab, since it uses cached schemas
1388 m_colorTab->reload();
1389}
1390
1391bool KateThemeConfigPage::copyTheme()
1392{
1393 // get current theme data as template
1394 const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1395 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1396
1397 // location to write theme files to
1398 const QString themesPath = QStandardPaths::writableLocation(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(schemaName).isValid() || QFile::exists(themeFileName)) {
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(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(themesPath);
1434 if (!writeJson(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(schemaName, QVariant(schemaName));
1443 defaultSchemaCombo->addItem(schemaName, QVariant(schemaName));
1444
1445 // finally, activate new schema (last item in the list)
1446 schemaCombo->setCurrentIndex(schemaCombo->count() - 1);
1447 return true;
1448}
1449
1450void 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(schema);
1454 btndel->setEnabled(!theme.isReadOnly());
1455 m_readOnlyThemeLabel->setVisible(theme.isReadOnly());
1456
1457 // propagate changed schema to all tabs
1458 m_colorTab->schemaChanged(schema);
1459 m_defaultStylesTab->schemaChanged(schema);
1460 m_highlightTab->schemaChanged(schema);
1461
1462 // save current schema index
1463 m_currentSchema = schema;
1464}
1465
1466void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex)
1467{
1468 schemaChanged(schemaCombo->itemData(currentIndex).toString());
1469}
1470
1471QString KateThemeConfigPage::name() const
1472{
1473 return i18n("Color Themes");
1474}
1475
1476QString KateThemeConfigPage::fullName() const
1477{
1478 return i18n("Color Themes");
1479}
1480
1481QIcon KateThemeConfigPage::icon() const
1482{
1483 return QIcon::fromTheme(QStringLiteral("preferences-desktop-color"));
1484}
1485
1486// END KateThemeConfigPage
1487
1488#include "moc_katethemeconfig.cpp"
void setCloseButtonVisible(bool visible)
void setMessageType(KMessageWidget::MessageType type)
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
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
bool setValue(const int key, const QVariant &value)
Set a config value.
QTreeWidget that automatically adds columns for KateStyleListItems and provides a popup menu and a sl...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
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()
QString name(StandardShortcut id)
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 addItem(const QIcon &icon, const QString &text, const QVariant &userData)
void clear()
void setCurrentIndex(int index)
void currentIndexChanged(int index)
void setEditable(bool editable)
int findData(const QVariant &data, int role, Qt::MatchFlags flags) const const
void insertSeparator(int index)
QVariant itemData(int index, int role) const const
void removeItem(int index)
bool mkpath(const QString &dirPath) const const
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)
QString fileName() const const
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)
T value(const Key &key) const const
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 addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
void reserve(qsizetype size)
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
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 left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
int addTab(QWidget *page, const QIcon &icon, const QString &label)
void setDocumentMode(bool set)
void expandAll()
void clear()
void expandItem(const QTreeWidgetItem *item)
QString toString() const const
void setEnabled(bool)
virtual bool event(QEvent *event) override
void hide()
QLayout * layout() const const
virtual void showEvent(QShowEvent *event)
QStyle * style() const const
virtual void setVisible(bool visible)
void setWhatsThis(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.