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) {
940 KTextEditor::Attribute::Ptr p = attributeIt.second.first;
941 KTextEditor::Attribute::Ptr pDefault = attributeIt.second.second;
942 if (p->hasProperty(QTextFormat::ForegroundBrush) && p->foreground().color() != pDefault->foreground().color()) {
943 style[QLatin1String("text-color")] = hexName(p->foreground().color());
944 }
945 if (p->hasProperty(QTextFormat::BackgroundBrush) && p->background().color() != pDefault->background().color()) {
946 style[QLatin1String("background-color")] = hexName(p->background().color());
947 }
948 if (p->hasProperty(SelectedForeground) && p->selectedForeground().color() != pDefault->selectedForeground().color()) {
949 style[QLatin1String("selected-text-color")] = hexName(p->selectedForeground().color());
950 }
951 if (p->hasProperty(SelectedBackground) && p->selectedBackground().color() != pDefault->selectedBackground().color()) {
952 style[QLatin1String("selected-background-color")] = hexName(p->selectedBackground().color());
953 }
954 if (p->hasProperty(QTextFormat::FontWeight) && p->fontBold() != pDefault->fontBold()) {
955 style[QLatin1String("bold")] = p->fontBold();
956 }
957 if (p->hasProperty(QTextFormat::FontItalic) && p->fontItalic() != pDefault->fontItalic()) {
958 style[QLatin1String("italic")] = p->fontItalic();
959 }
960 if (p->hasProperty(QTextFormat::TextUnderlineStyle) && p->fontUnderline() != pDefault->fontUnderline()) {
961 style[QLatin1String("underline")] = p->fontUnderline();
962 }
963 if (p->hasProperty(QTextFormat::FontStrikeOut) && p->fontStrikeOut() != pDefault->fontStrikeOut()) {
964 style[QLatin1String("strike-through")] = p->fontStrikeOut();
965 }
966
967 // either set the new stuff or erase the old entry we might have set from the loaded json
968 if (!style.isEmpty()) {
969 styles[attributeIt.first] = style;
970 } else {
971 styles.remove(attributeIt.first);
972 }
973 }
974
975 // either set the new stuff or erase the old entry we might have set from the loaded json
976 if (!styles.isEmpty()) {
977 overrides[definitionName] = styles;
978 } else {
979 overrides.remove(definitionName);
980 }
981 }
982
983 // we set even empty overrides, to ensure we overwrite stuff!
984 newThemeObject[QLatin1String("custom-styles")] = overrides;
985
986 // write json back to file
987 writeJson(newThemeObject, theme.filePath());
988 }
989}
990
991QList<int> KateThemeConfigHighlightTab::hlsForSchema(const QString &schema)
992{
993 auto it = m_hlDict.find(schema);
994 if (it != m_hlDict.end()) {
995 return it.value().keys();
996 }
997 return {};
998}
999
1000void KateThemeConfigHighlightTab::showEvent(QShowEvent *event)
1001{
1002 if (!event->spontaneous()) {
1003 KateAttributeList *l = m_defaults->attributeList(m_schema);
1004 Q_ASSERT(l != nullptr);
1005 updateColorPalette(l->at(0)->foreground().color());
1006 }
1007
1009}
1010// END KateThemeConfigHighlightTab
1011
1012// BEGIN KateThemeConfigPage -- Main dialog page
1013KateThemeConfigPage::KateThemeConfigPage(QWidget *parent)
1014 : KateConfigPage(parent)
1015{
1016 QHBoxLayout *layout = new QHBoxLayout(this);
1017 layout->setContentsMargins({});
1018
1019 QTabWidget *tabWidget = new QTabWidget(this);
1020 tabWidget->setDocumentMode(true);
1021 layout->addWidget(tabWidget);
1022
1023 auto *themeEditor = new QWidget(this);
1024 auto *themeChooser = new QWidget(this);
1025 tabWidget->addTab(themeChooser, i18n("Default Theme"));
1026 tabWidget->addTab(themeEditor, i18n("Theme Editor"));
1027 layoutThemeChooserTab(themeChooser);
1028 layoutThemeEditorTab(themeEditor);
1029
1030 reload();
1031}
1032
1033void KateThemeConfigPage::layoutThemeChooserTab(QWidget *tab)
1034{
1035 QVBoxLayout *layout = new QVBoxLayout(tab);
1037
1038 auto *comboLayout = new QHBoxLayout;
1039
1040 auto lHl = new QLabel(i18n("Select theme:"), this);
1041 comboLayout->addWidget(lHl);
1042
1043 defaultSchemaCombo = new QComboBox(this);
1044 comboLayout->addWidget(defaultSchemaCombo);
1045 defaultSchemaCombo->setEditable(false);
1046 lHl->setBuddy(defaultSchemaCombo);
1047 connect(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::slotChanged);
1048 comboLayout->addStretch();
1049
1050 layout->addLayout(comboLayout);
1051
1052 m_doc = new KTextEditor::DocumentPrivate;
1053 m_doc->setParent(this);
1054
1055 const auto code = R"sample(/**
1056* SPDX-FileCopyrightText: 2020 Christoph Cullmann <cullmann@kde.org>
1057* SPDX-License-Identifier: MIT
1058*/
1059
1060// BEGIN
1061#include <QString>
1062#include <string>
1063// END
1064
1065/**
1066* TODO: improve documentation
1067* @param magicArgument some magic argument
1068* @return magic return value
1069*/
1070int main(uint64_t magicArgument)
1071{
1072 if (magicArgument > 1) {
1073 const std::string string = "source file: \"" __FILE__ "\"";
1074 const QString qString(QStringLiteral("test"));
1075 return qrand();
1076 }
1077
1078 /* BUG: bogus integer constant inside next line */
1079 const double g = 1.1e12 * 0b01'01'01'01 - 43a + 0x11234 * 0234ULL - 'c' * 42;
1080 return g > 1.3f;
1081})sample";
1082
1083 m_doc->setText(QString::fromUtf8(code));
1084 m_doc->setHighlightingMode(QStringLiteral("C++"));
1085 m_themePreview = new KTextEditor::ViewPrivate(m_doc, this);
1086
1087 layout->addWidget(m_themePreview);
1088
1089 connect(defaultSchemaCombo, &QComboBox::currentIndexChanged, this, [this](int idx) {
1090 const QString schema = defaultSchemaCombo->itemData(idx).toString();
1091 m_themePreview->rendererConfig()->setSchema(schema);
1092 if (schema.isEmpty()) {
1093 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1094 } else {
1095 m_themePreview->rendererConfig()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1096 }
1097 });
1098}
1099
1100void KateThemeConfigPage::layoutThemeEditorTab(QWidget *tab)
1101{
1102 QVBoxLayout *layout = new QVBoxLayout(tab);
1103 layout->setContentsMargins(0, 0, 0, 0);
1104
1105 // header
1106 QHBoxLayout *headerLayout = new QHBoxLayout;
1107 layout->addLayout(headerLayout);
1108
1109 QLabel *lHl = new QLabel(i18n("&Theme:"), this);
1110 headerLayout->addWidget(lHl);
1111
1112 schemaCombo = new QComboBox(this);
1113 schemaCombo->setEditable(false);
1114 lHl->setBuddy(schemaCombo);
1115 headerLayout->addWidget(schemaCombo);
1116 connect(schemaCombo, &QComboBox::currentIndexChanged, this, &KateThemeConfigPage::comboBoxIndexChanged);
1117
1118 QPushButton *copyButton = new QPushButton(i18n("&Copy..."), this);
1119 headerLayout->addWidget(copyButton);
1120 connect(copyButton, &QPushButton::clicked, this, &KateThemeConfigPage::copyTheme);
1121
1122 btndel = new QPushButton(i18n("&Delete"), this);
1123 headerLayout->addWidget(btndel);
1124 connect(btndel, &QPushButton::clicked, this, &KateThemeConfigPage::deleteSchema);
1125
1126 QPushButton *btnexport = new QPushButton(i18n("Export..."), this);
1127 headerLayout->addWidget(btnexport);
1128 connect(btnexport, &QPushButton::clicked, this, &KateThemeConfigPage::exportFullSchema);
1129
1130 QPushButton *btnimport = new QPushButton(i18n("Import..."), this);
1131 headerLayout->addWidget(btnimport);
1132 connect(btnimport, &QPushButton::clicked, this, &KateThemeConfigPage::importFullSchema);
1133
1134 headerLayout->addStretch();
1135
1136 // label to inform about read-only state
1137 m_readOnlyThemeLabel = new KMessageWidget(i18n("Bundled read-only theme. To modify the theme, please copy it."), this);
1138 m_readOnlyThemeLabel->setCloseButtonVisible(false);
1139 m_readOnlyThemeLabel->setMessageType(KMessageWidget::Information);
1140 m_readOnlyThemeLabel->hide();
1141 layout->addWidget(m_readOnlyThemeLabel);
1142
1143 // tabs
1144 QTabWidget *tabWidget = new QTabWidget(this);
1145 layout->addWidget(tabWidget);
1146
1147 m_colorTab = new KateThemeConfigColorTab();
1148 tabWidget->addTab(m_colorTab, i18n("Colors"));
1149 connect(m_colorTab, &KateThemeConfigColorTab::changed, this, &KateThemeConfigPage::slotChanged);
1150
1151 m_defaultStylesTab = new KateThemeConfigDefaultStylesTab(m_colorTab);
1152 tabWidget->addTab(m_defaultStylesTab, i18n("Default Text Styles"));
1153 connect(m_defaultStylesTab, &KateThemeConfigDefaultStylesTab::changed, this, &KateThemeConfigPage::slotChanged);
1154
1155 m_highlightTab = new KateThemeConfigHighlightTab(m_defaultStylesTab, m_colorTab);
1156 tabWidget->addTab(m_highlightTab, i18n("Highlighting Text Styles"));
1157 connect(m_highlightTab, &KateThemeConfigHighlightTab::changed, this, &KateThemeConfigPage::slotChanged);
1158
1159 QHBoxLayout *footLayout = new QHBoxLayout;
1160 layout->addLayout(footLayout);
1161}
1162
1163void KateThemeConfigPage::exportFullSchema()
1164{
1165 // get save destination
1166 const QString currentSchemaName = m_currentSchema;
1167 const QString destName = QFileDialog::getSaveFileName(this,
1168 i18n("Exporting color theme: %1", currentSchemaName),
1169 currentSchemaName + QLatin1String(".theme"),
1170 QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1171 if (destName.isEmpty()) {
1172 return;
1173 }
1174
1175 // get current theme
1176 const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1177 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1178
1179 // ensure we overwrite
1180 if (QFile::exists(destName)) {
1181 QFile::remove(destName);
1182 }
1183
1184 // export is easy, just copy the file 1:1
1185 QFile::copy(currentTheme.filePath(), destName);
1186}
1187
1188void KateThemeConfigPage::importFullSchema()
1189{
1190 const QString srcName =
1191 QFileDialog::getOpenFileName(this, i18n("Importing Color Theme"), QString(), QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1192 if (srcName.isEmpty()) {
1193 return;
1194 }
1195
1196 // location to write theme files to
1197 const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1198
1199 // construct file name for imported theme
1200 const QString themesFullFileName = themesPath + QStringLiteral("/") + QFileInfo(srcName).fileName();
1201
1202 // if something might be overwritten, as the user
1203 if (QFile::exists(themesFullFileName)) {
1205 i18n("Importing will overwrite the existing theme file \"%1\". This can not be undone.", themesFullFileName),
1206 i18n("Possible Data Loss"),
1207 KGuiItem(i18n("Import Nevertheless")),
1210 return;
1211 }
1212 }
1213
1214 // copy theme file, we might need to create the local dir first
1215 QDir().mkpath(themesPath);
1216
1217 // ensure we overwrite
1218 if (QFile::exists(themesFullFileName)) {
1219 QFile::remove(themesFullFileName);
1220 }
1221 QFile::copy(srcName, themesFullFileName);
1222
1223 // reload themes DB & clear all attributes
1224 KateHlManager::self()->reload();
1225 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1226 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1227 }
1228
1229 // KateThemeManager::update() sorts the schema alphabetically, hence the
1230 // schema indexes change. Thus, repopulate the schema list...
1231 refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1232}
1233
1234void KateThemeConfigPage::apply()
1235{
1236 // remember name + index
1237 const QString schemaName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1238
1239 // first apply all tabs
1240 m_colorTab->apply();
1241 m_defaultStylesTab->apply();
1242 m_highlightTab->apply();
1243
1244 // reload themes DB & clear all attributes
1245 KateHlManager::self()->reload();
1246 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1247 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1248 }
1249
1250 // than reload the whole stuff, special handle auto selection == empty theme name
1251 const auto defaultTheme = defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString();
1252 if (defaultTheme.isEmpty()) {
1253 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1254 } else {
1255 KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1256 KateRendererConfig::global()->setSchema(defaultTheme);
1257 }
1258 KateRendererConfig::global()->reloadSchema();
1259
1260 // KateThemeManager::update() sorts the schema alphabetically, hence the
1261 // schema indexes change. Thus, repopulate the schema list...
1262 refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1263 schemaChanged(schemaName);
1264}
1265
1266void KateThemeConfigPage::reload()
1267{
1268 // reinitialize combo boxes
1269 refillCombos(KateRendererConfig::global()->schema(), KateRendererConfig::global()->schema());
1270
1271 // finally, activate the current schema again
1272 schemaChanged(schemaCombo->itemData(schemaCombo->currentIndex()).toString());
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::refillCombos(const QString &schemaName, const QString &defaultSchemaName)
1282{
1283 schemaCombo->blockSignals(true);
1284 defaultSchemaCombo->blockSignals(true);
1285
1286 // reinitialize combo boxes
1287 schemaCombo->clear();
1288 defaultSchemaCombo->clear();
1289 defaultSchemaCombo->addItem(i18n("Follow System Color Scheme"), QString());
1290 defaultSchemaCombo->insertSeparator(1);
1291 const auto themes = KateHlManager::self()->sortedThemes();
1292 for (const auto &theme : themes) {
1293 schemaCombo->addItem(theme.translatedName(), theme.name());
1294 defaultSchemaCombo->addItem(theme.translatedName(), theme.name());
1295 }
1296
1297 // set the correct indexes again, fallback to always existing default theme
1298 int schemaIndex = schemaCombo->findData(schemaName);
1299 if (schemaIndex == -1) {
1300 schemaIndex = schemaCombo->findData(
1301 KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name());
1302 }
1303
1304 // set the correct indexes again, fallback to auto-selection
1305 int defaultSchemaIndex = 0;
1306 if (!KateRendererConfig::global()->value(KateRendererConfig::AutoColorThemeSelection).toBool()) {
1307 defaultSchemaIndex = defaultSchemaCombo->findData(defaultSchemaName);
1308 if (defaultSchemaIndex == -1) {
1309 defaultSchemaIndex = 0;
1310 }
1311 }
1312
1313 Q_ASSERT(schemaIndex != -1);
1314 Q_ASSERT(defaultSchemaIndex != -1);
1315
1316 defaultSchemaCombo->setCurrentIndex(defaultSchemaIndex);
1317 schemaCombo->setCurrentIndex(schemaIndex);
1318
1319 schemaCombo->blockSignals(false);
1320 defaultSchemaCombo->blockSignals(false);
1321
1322 m_themePreview->rendererConfig()->setSchema(defaultSchemaName);
1323}
1324
1325void KateThemeConfigPage::reset()
1326{
1327 // reload themes DB & clear all attributes
1328 KateHlManager::self()->reload();
1329 for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1330 KateHlManager::self()->getHl(i)->clearAttributeArrays();
1331 }
1332
1333 // reload the view
1334 reload();
1335}
1336
1337void KateThemeConfigPage::defaults()
1338{
1339 reset();
1340}
1341
1342void KateThemeConfigPage::deleteSchema()
1343{
1344 const int comboIndex = schemaCombo->currentIndex();
1345 const QString schemaNameToDelete = schemaCombo->itemData(comboIndex).toString();
1346
1347 // KSyntaxHighlighting themes can not be deleted, skip invalid themes, too
1348 const auto theme = KateHlManager::self()->repository().theme(schemaNameToDelete);
1349 if (!theme.isValid() || theme.isReadOnly()) {
1350 return;
1351 }
1352
1353 // ask the user again, this can't be undone
1355 i18n("Do you really want to delete the theme \"%1\"? This can not be undone.", schemaNameToDelete),
1356 i18n("Possible Data Loss"),
1357 KGuiItem(i18n("Delete Nevertheless")),
1360 return;
1361 }
1362
1363 // purge the theme file
1364 QFile::remove(theme.filePath());
1365
1366 // reset syntax manager repo to flush deleted theme
1367 KateHlManager::self()->reload();
1368
1369 // fallback to Default schema + auto
1370 schemaCombo->setCurrentIndex(schemaCombo->findData(
1371 QVariant(KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name())));
1372 if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(schemaNameToDelete)) {
1373 defaultSchemaCombo->setCurrentIndex(0);
1374 }
1375
1376 // remove schema from combo box
1377 schemaCombo->removeItem(comboIndex);
1378 defaultSchemaCombo->removeItem(comboIndex);
1379
1380 // Reload the color tab, since it uses cached schemas
1381 m_colorTab->reload();
1382}
1383
1384bool KateThemeConfigPage::copyTheme()
1385{
1386 // get current theme data as template
1387 const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1388 const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1389
1390 // location to write theme files to
1391 const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1392
1393 // get sane name
1394 QString schemaName;
1395 QString themeFileName;
1396 while (schemaName.isEmpty()) {
1397 QInputDialog newNameDialog(this);
1398 newNameDialog.setInputMode(QInputDialog::TextInput);
1399 newNameDialog.setWindowTitle(i18n("Copy theme"));
1400 newNameDialog.setLabelText(i18n("Name for copy of color theme \"%1\":", currentThemeName));
1401 newNameDialog.setTextValue(currentThemeName);
1402 if (newNameDialog.exec() == QDialog::Rejected) {
1403 return false;
1404 }
1405 schemaName = newNameDialog.textValue();
1406
1407 // try if schema already around => if yes, retry name input
1408 // we try for duplicated file names, too
1409 themeFileName = themesPath + QStringLiteral("/") + schemaName + QStringLiteral(".theme");
1410 if (KateHlManager::self()->repository().theme(schemaName).isValid() || QFile::exists(themeFileName)) {
1412 i18n("<p>The theme \"%1\" already exists.</p><p>Please choose a different theme name.</p>", schemaName),
1413 i18n("Copy Theme"));
1414 schemaName.clear();
1415 }
1416 }
1417
1418 // get json for current theme
1419 QJsonObject newThemeObject = jsonForTheme(currentTheme);
1420 QJsonObject metaData;
1421 metaData[QLatin1String("revision")] = 1;
1422 metaData[QLatin1String("name")] = schemaName;
1423 newThemeObject[QLatin1String("metadata")] = metaData;
1424
1425 // write to new theme file, we might need to create the local dir first
1426 QDir().mkpath(themesPath);
1427 if (!writeJson(newThemeObject, themeFileName)) {
1428 return false;
1429 }
1430
1431 // reset syntax manager repo to find new theme
1432 KateHlManager::self()->reload();
1433
1434 // append items to combo boxes
1435 schemaCombo->addItem(schemaName, QVariant(schemaName));
1436 defaultSchemaCombo->addItem(schemaName, QVariant(schemaName));
1437
1438 // finally, activate new schema (last item in the list)
1439 schemaCombo->setCurrentIndex(schemaCombo->count() - 1);
1440 return true;
1441}
1442
1443void KateThemeConfigPage::schemaChanged(const QString &schema)
1444{
1445 // we can't delete read-only themes, e.g. the stuff shipped inside Qt resources or system wide installed
1446 const auto theme = KateHlManager::self()->repository().theme(schema);
1447 btndel->setEnabled(!theme.isReadOnly());
1448 m_readOnlyThemeLabel->setVisible(theme.isReadOnly());
1449
1450 // propagate changed schema to all tabs
1451 m_colorTab->schemaChanged(schema);
1452 m_defaultStylesTab->schemaChanged(schema);
1453 m_highlightTab->schemaChanged(schema);
1454
1455 // save current schema index
1456 m_currentSchema = schema;
1457}
1458
1459void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex)
1460{
1461 schemaChanged(schemaCombo->itemData(currentIndex).toString());
1462}
1463
1464QString KateThemeConfigPage::name() const
1465{
1466 return i18n("Color Themes");
1467}
1468
1469QString KateThemeConfigPage::fullName() const
1470{
1471 return i18n("Color Themes");
1472}
1473
1474QIcon KateThemeConfigPage::icon() const
1475{
1476 return QIcon::fromTheme(QStringLiteral("preferences-desktop-color"));
1477}
1478
1479// END KateThemeConfigPage
1480
1481#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)
QString name(StandardAction id)
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() const const
void activated(int index)
void addItem(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 &newName)
bool exists() const const
bool remove()
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString fileName() const const
void addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment)
void setColumnStretch(int column, int stretch)
QHash::iterator insert(const Key &key, const T &value)
const 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(const QString &key)
void setBuddy(QWidget *buddy)
void addWidget(QWidget *w)
void setContentsMargins(int left, int top, int right, int bottom)
void append(const T &value)
const T & at(int i) const const
void reserve(int alloc)
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QString writableLocation(QStandardPaths::StandardLocation type)
void clear()
QString fromUtf8(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(int n) const const
QString mid(int position, int n) const const
QString number(int n, int base)
int addTab(QWidget *page, 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 Sat Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.