File indexing completed on 2024-05-05 16:18:08

0001 /*
0002     SPDX-FileCopyrightText: 2007, 2008 Matthew Woehlke <mw_triad@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2001-2003 Christoph Cullmann <cullmann@kde.org>
0004     SPDX-FileCopyrightText: 2002, 2003 Anders Lund <anders.lund@lund.tdcadsl.dk>
0005     SPDX-FileCopyrightText: 2012-2018 Dominik Haumann <dhaumann@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 // BEGIN Includes
0011 #include "katethemeconfig.h"
0012 
0013 #include "katecolortreewidget.h"
0014 #include "kateconfig.h"
0015 #include "katedocument.h"
0016 #include "kateglobal.h"
0017 #include "katehighlight.h"
0018 #include "katerenderer.h"
0019 #include "katestyletreewidget.h"
0020 #include "katesyntaxmanager.h"
0021 #include "kateview.h"
0022 
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <KMessageWidget>
0026 
0027 #include <QComboBox>
0028 #include <QFileDialog>
0029 #include <QGridLayout>
0030 #include <QInputDialog>
0031 #include <QJsonObject>
0032 #include <QLabel>
0033 #include <QMetaEnum>
0034 #include <QPushButton>
0035 #include <QTabWidget>
0036 
0037 // END
0038 
0039 /**
0040  * Return the translated name of default style @p n.
0041  */
0042 static inline QString defaultStyleName(KTextEditor::DefaultStyle style)
0043 {
0044     using namespace KTextEditor;
0045     switch (style) {
0046     case dsNormal:
0047         return i18nc("@item:intable Text context", "Normal");
0048     case dsKeyword:
0049         return i18nc("@item:intable Text context", "Keyword");
0050     case dsFunction:
0051         return i18nc("@item:intable Text context", "Function");
0052     case dsVariable:
0053         return i18nc("@item:intable Text context", "Variable");
0054     case dsControlFlow:
0055         return i18nc("@item:intable Text context", "Control Flow");
0056     case dsOperator:
0057         return i18nc("@item:intable Text context", "Operator");
0058     case dsBuiltIn:
0059         return i18nc("@item:intable Text context", "Built-in");
0060     case dsExtension:
0061         return i18nc("@item:intable Text context", "Extension");
0062     case dsPreprocessor:
0063         return i18nc("@item:intable Text context", "Preprocessor");
0064     case dsAttribute:
0065         return i18nc("@item:intable Text context", "Attribute");
0066 
0067     case dsChar:
0068         return i18nc("@item:intable Text context", "Character");
0069     case dsSpecialChar:
0070         return i18nc("@item:intable Text context", "Special Character");
0071     case dsString:
0072         return i18nc("@item:intable Text context", "String");
0073     case dsVerbatimString:
0074         return i18nc("@item:intable Text context", "Verbatim String");
0075     case dsSpecialString:
0076         return i18nc("@item:intable Text context", "Special String");
0077     case dsImport:
0078         return i18nc("@item:intable Text context", "Imports, Modules, Includes");
0079 
0080     case dsDataType:
0081         return i18nc("@item:intable Text context", "Data Type");
0082     case dsDecVal:
0083         return i18nc("@item:intable Text context", "Decimal/Value");
0084     case dsBaseN:
0085         return i18nc("@item:intable Text context", "Base-N Integer");
0086     case dsFloat:
0087         return i18nc("@item:intable Text context", "Floating Point");
0088     case dsConstant:
0089         return i18nc("@item:intable Text context", "Constant");
0090 
0091     case dsComment:
0092         return i18nc("@item:intable Text context", "Comment");
0093     case dsDocumentation:
0094         return i18nc("@item:intable Text context", "Documentation");
0095     case dsAnnotation:
0096         return i18nc("@item:intable Text context", "Annotation");
0097     case dsCommentVar:
0098         return i18nc("@item:intable Text context", "Comment Variable");
0099     case dsRegionMarker:
0100         // this next one is for denoting the beginning/end of a user defined folding region
0101         return i18nc("@item:intable Text context", "Region Marker");
0102     case dsInformation:
0103         return i18nc("@item:intable Text context", "Information");
0104     case dsWarning:
0105         return i18nc("@item:intable Text context", "Warning");
0106     case dsAlert:
0107         return i18nc("@item:intable Text context", "Alert");
0108 
0109     case dsOthers:
0110         return i18nc("@item:intable Text context", "Others");
0111     case dsError:
0112         // this one is for marking invalid input
0113         return i18nc("@item:intable Text context", "Error");
0114     };
0115     Q_UNREACHABLE();
0116 }
0117 
0118 /**
0119  * Helper to get json object for given valid theme.
0120  * @param theme theme to get json object for
0121  * @return json object of theme
0122  */
0123 static QJsonObject jsonForTheme(const KSyntaxHighlighting::Theme &theme)
0124 {
0125     // load json content, shall work, as the theme as valid, but still abort on errors
0126     QFile loadFile(theme.filePath());
0127     if (!loadFile.open(QIODevice::ReadOnly)) {
0128         return QJsonObject();
0129     }
0130     const QByteArray jsonData = loadFile.readAll();
0131     QJsonParseError parseError;
0132     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
0133     if (parseError.error != QJsonParseError::NoError) {
0134         return QJsonObject();
0135     }
0136     return jsonDoc.object();
0137 }
0138 
0139 /**
0140  * Helper to write json data to given file path.
0141  * After the function returns, stuff is flushed to disk for sure.
0142  * @param json json object with theme data
0143  * @param themeFileName file name to write to
0144  * @return did writing succeed?
0145  */
0146 static bool writeJson(const QJsonObject &json, const QString &themeFileName)
0147 {
0148     QFile saveFile(themeFileName);
0149     if (!saveFile.open(QIODevice::WriteOnly)) {
0150         return false;
0151     }
0152     saveFile.write(QJsonDocument(json).toJson());
0153     return true;
0154 }
0155 
0156 // BEGIN KateThemeConfigColorTab -- 'Colors' tab
0157 KateThemeConfigColorTab::KateThemeConfigColorTab()
0158 {
0159     QGridLayout *l = new QGridLayout(this);
0160 
0161     ui = new KateColorTreeWidget(this);
0162     QPushButton *btnUseColorScheme = new QPushButton(i18n("Use Default Colors"), this);
0163 
0164     l->addWidget(ui, 0, 0, 1, 2);
0165     l->addWidget(btnUseColorScheme, 1, 1);
0166 
0167     l->setColumnStretch(0, 1);
0168     l->setColumnStretch(1, 0);
0169 
0170     connect(btnUseColorScheme, &QPushButton::clicked, ui, &KateColorTreeWidget::selectDefaults);
0171     connect(ui, &KateColorTreeWidget::changed, this, &KateThemeConfigColorTab::changed);
0172 }
0173 
0174 static QVector<KateColorItem> colorItemList(const KSyntaxHighlighting::Theme &theme)
0175 {
0176     QVector<KateColorItem> items;
0177 
0178     //
0179     // editor background colors
0180     //
0181     KateColorItem ci(KSyntaxHighlighting::Theme::BackgroundColor);
0182     ci.category = i18n("Editor Background Colors");
0183 
0184     ci.name = i18n("Text Area");
0185     ci.key = QStringLiteral("Color Background");
0186     ci.whatsThis = i18n("<p>Sets the background color of the editing area.</p>");
0187     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0188     items.append(ci);
0189 
0190     ci.role = KSyntaxHighlighting::Theme::TextSelection;
0191     ci.name = i18n("Selected Text");
0192     ci.key = QStringLiteral("Color Selection");
0193     ci.whatsThis = i18n(
0194         "<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; "
0195         "dialog.</p>");
0196     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0197     items.append(ci);
0198 
0199     ci.role = KSyntaxHighlighting::Theme::CurrentLine;
0200     ci.name = i18n("Current Line");
0201     ci.key = QStringLiteral("Color Highlighted Line");
0202     ci.whatsThis = i18n("<p>Sets the background color of the currently active line, which means the line where your cursor is positioned.</p>");
0203     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0204     items.append(ci);
0205 
0206     ci.role = KSyntaxHighlighting::Theme::SearchHighlight;
0207     ci.name = i18n("Search Highlight");
0208     ci.key = QStringLiteral("Color Search Highlight");
0209     ci.whatsThis = i18n("<p>Sets the background color of search results.</p>");
0210     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0211     items.append(ci);
0212 
0213     ci.role = KSyntaxHighlighting::Theme::ReplaceHighlight;
0214     ci.name = i18n("Replace Highlight");
0215     ci.key = QStringLiteral("Color Replace Highlight");
0216     ci.whatsThis = i18n("<p>Sets the background color of replaced text.</p>");
0217     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0218     items.append(ci);
0219 
0220     //
0221     // icon border
0222     //
0223     ci.category = i18n("Icon Border");
0224 
0225     ci.role = KSyntaxHighlighting::Theme::IconBorder;
0226     ci.name = i18n("Background Area");
0227     ci.key = QStringLiteral("Color Icon Bar");
0228     ci.whatsThis = i18n("<p>Sets the background color of the icon border.</p>");
0229     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0230     items.append(ci);
0231 
0232     ci.role = KSyntaxHighlighting::Theme::LineNumbers;
0233     ci.name = i18n("Line Numbers");
0234     ci.key = QStringLiteral("Color Line Number");
0235     ci.whatsThis = i18n("<p>This color will be used to draw the line numbers (if enabled).</p>");
0236     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0237     items.append(ci);
0238 
0239     ci.role = KSyntaxHighlighting::Theme::CurrentLineNumber;
0240     ci.name = i18n("Current Line Number");
0241     ci.key = QStringLiteral("Color Current Line Number");
0242     ci.whatsThis = i18n("<p>This color will be used to draw the number of the current line (if enabled).</p>");
0243     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0244     items.append(ci);
0245 
0246     ci.role = KSyntaxHighlighting::Theme::Separator;
0247     ci.name = i18n("Separator");
0248     ci.key = QStringLiteral("Color Separator");
0249     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>");
0250     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0251     items.append(ci);
0252 
0253     ci.role = KSyntaxHighlighting::Theme::WordWrapMarker;
0254     ci.name = i18n("Word Wrap Marker");
0255     ci.key = QStringLiteral("Color Word Wrap Marker");
0256     ci.whatsThis = i18n(
0257         "<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 "
0258         "wrapped</dd><dt>Dynamic Word Wrap</dt><dd>An arrow shown to the left of "
0259         "visually-wrapped lines</dd></dl>");
0260     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0261     items.append(ci);
0262 
0263     ci.role = KSyntaxHighlighting::Theme::CodeFolding;
0264     ci.name = i18n("Code Folding");
0265     ci.key = QStringLiteral("Color Code Folding");
0266     ci.whatsThis = i18n("<p>Sets the color of the code folding bar.</p>");
0267     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0268     items.append(ci);
0269 
0270     ci.role = KSyntaxHighlighting::Theme::ModifiedLines;
0271     ci.name = i18n("Modified Lines");
0272     ci.key = QStringLiteral("Color Modified Lines");
0273     ci.whatsThis = i18n("<p>Sets the color of the line modification marker for modified lines.</p>");
0274     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0275     items.append(ci);
0276 
0277     ci.role = KSyntaxHighlighting::Theme::SavedLines;
0278     ci.name = i18n("Saved Lines");
0279     ci.key = QStringLiteral("Color Saved Lines");
0280     ci.whatsThis = i18n("<p>Sets the color of the line modification marker for saved lines.</p>");
0281     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0282     items.append(ci);
0283 
0284     //
0285     // text decorations
0286     //
0287     ci.category = i18n("Text Decorations");
0288 
0289     ci.role = KSyntaxHighlighting::Theme::SpellChecking;
0290     ci.name = i18n("Spelling Mistake Line");
0291     ci.key = QStringLiteral("Color Spelling Mistake Line");
0292     ci.whatsThis = i18n("<p>Sets the color of the line that is used to indicate spelling mistakes.</p>");
0293     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0294     items.append(ci);
0295 
0296     ci.role = KSyntaxHighlighting::Theme::TabMarker;
0297     ci.name = i18n("Tab and Space Markers");
0298     ci.key = QStringLiteral("Color Tab Marker");
0299     ci.whatsThis = i18n("<p>Sets the color of the tabulator marks.</p>");
0300     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0301     items.append(ci);
0302 
0303     ci.role = KSyntaxHighlighting::Theme::IndentationLine;
0304     ci.name = i18n("Indentation Line");
0305     ci.key = QStringLiteral("Color Indentation Line");
0306     ci.whatsThis = i18n("<p>Sets the color of the vertical indentation lines.</p>");
0307     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0308     items.append(ci);
0309 
0310     ci.role = KSyntaxHighlighting::Theme::BracketMatching;
0311     ci.name = i18n("Bracket Highlight");
0312     ci.key = QStringLiteral("Color Highlighted Bracket");
0313     ci.whatsThis = i18n(
0314         "<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 "
0315         "color.</p>");
0316     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0317     items.append(ci);
0318 
0319     //
0320     // marker colors
0321     //
0322     ci.category = i18n("Marker Colors");
0323 
0324     const QString markerNames[KSyntaxHighlighting::Theme::MarkError - KSyntaxHighlighting::Theme::MarkBookmark + 1] = {i18n("Bookmark"),
0325                                                                                                                        i18n("Active Breakpoint"),
0326                                                                                                                        i18n("Reached Breakpoint"),
0327                                                                                                                        i18n("Disabled Breakpoint"),
0328                                                                                                                        i18n("Execution"),
0329                                                                                                                        i18n("Warning"),
0330                                                                                                                        i18n("Error")};
0331 
0332     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>");
0333     for (int i = 0; i <= KSyntaxHighlighting::Theme::MarkError - KSyntaxHighlighting::Theme::MarkBookmark; ++i) {
0334         ci.role = static_cast<KSyntaxHighlighting::Theme::EditorColorRole>(i + KSyntaxHighlighting::Theme::MarkBookmark);
0335         ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0336         ci.name = markerNames[i];
0337         ci.key = QLatin1String("Color MarkType ") + QString::number(i + 1);
0338         items.append(ci);
0339     }
0340 
0341     //
0342     // text templates
0343     //
0344     ci.category = i18n("Text Templates & Snippets");
0345 
0346     ci.whatsThis = QString(); // TODO: add whatsThis for text templates
0347 
0348     ci.role = KSyntaxHighlighting::Theme::TemplateBackground;
0349     ci.name = i18n("Background");
0350     ci.key = QStringLiteral("Color Template Background");
0351     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0352     items.append(ci);
0353 
0354     ci.role = KSyntaxHighlighting::Theme::TemplatePlaceholder;
0355     ci.name = i18n("Editable Placeholder");
0356     ci.key = QStringLiteral("Color Template Editable Placeholder");
0357     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0358     items.append(ci);
0359 
0360     ci.role = KSyntaxHighlighting::Theme::TemplateFocusedPlaceholder;
0361     ci.name = i18n("Focused Editable Placeholder");
0362     ci.key = QStringLiteral("Color Template Focused Editable Placeholder");
0363     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0364     items.append(ci);
0365 
0366     ci.role = KSyntaxHighlighting::Theme::TemplateReadOnlyPlaceholder;
0367     ci.name = i18n("Not Editable Placeholder");
0368     ci.key = QStringLiteral("Color Template Not Editable Placeholder");
0369     ci.defaultColor = QColor::fromRgba(theme.editorColor(ci.role));
0370     items.append(ci);
0371 
0372     //
0373     // finally, add all elements
0374     //
0375     return items;
0376 }
0377 
0378 void KateThemeConfigColorTab::schemaChanged(const QString &newSchema)
0379 {
0380     // ensure invalid or read-only stuff can't be changed
0381     const auto theme = KateHlManager::self()->repository().theme(newSchema);
0382     ui->setReadOnly(!theme.isValid() || theme.isReadOnly());
0383 
0384     // save current schema
0385     if (!m_currentSchema.isEmpty()) {
0386         auto it = m_schemas.find(m_currentSchema);
0387         if (it != m_schemas.end()) {
0388             m_schemas.erase(m_currentSchema); // clear this color schema
0389         }
0390 
0391         // now add it again
0392         m_schemas[m_currentSchema] = ui->colorItems();
0393     }
0394 
0395     if (newSchema == m_currentSchema) {
0396         return;
0397     }
0398 
0399     // switch
0400     m_currentSchema = newSchema;
0401 
0402     // If we havent this schema, read in from config file
0403     if (m_schemas.find(newSchema) == m_schemas.end()) {
0404         QVector<KateColorItem> items = colorItemList(theme);
0405         for (auto &item : items) {
0406             item.color = QColor::fromRgba(theme.editorColor(item.role));
0407         }
0408         m_schemas[newSchema] = std::move(items);
0409     }
0410 
0411     // first block signals otherwise setColor emits changed
0412     const bool blocked = blockSignals(true);
0413 
0414     ui->clear();
0415     ui->addColorItems(m_schemas[m_currentSchema]);
0416 
0417     blockSignals(blocked);
0418 }
0419 
0420 /**
0421  * @brief Converts @p c to its hex value, if @p c has alpha then the returned string
0422  * will be of the format #AARRGGBB other wise #RRGGBB
0423  */
0424 static QString hexName(const QColor &c)
0425 {
0426     return c.alpha() == 0xFF ? c.name() : c.name(QColor::HexArgb);
0427 }
0428 
0429 void KateThemeConfigColorTab::apply()
0430 {
0431     schemaChanged(m_currentSchema);
0432 
0433     // we use meta-data of enum for computing the json keys
0434     static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator("EditorColorRole");
0435     Q_ASSERT(idx >= 0);
0436     const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(idx);
0437 
0438     // export all themes we cached data for
0439     for (auto it = m_schemas.cbegin(); it != m_schemas.cend(); ++it) {
0440         // get theme for key, skip invalid or read-only themes for writing
0441         const auto theme = KateHlManager::self()->repository().theme(it->first);
0442         if (!theme.isValid() || theme.isReadOnly()) {
0443             continue;
0444         }
0445 
0446         // get current theme data from disk
0447         QJsonObject newThemeObject = jsonForTheme(theme);
0448 
0449         // patch the editor-colors part
0450         QJsonObject colors;
0451         const auto &colorItems = it->second;
0452         for (const KateColorItem &item : colorItems) {
0453             QColor c = item.useDefault ? item.defaultColor : item.color;
0454             colors[QLatin1String(metaEnum.key(item.role))] = hexName(c);
0455         }
0456         newThemeObject[QLatin1String("editor-colors")] = colors;
0457 
0458         // write json back to file
0459         writeJson(newThemeObject, theme.filePath());
0460     }
0461 
0462     // all colors are written, so throw away all cached schemas
0463     m_schemas.clear();
0464 }
0465 
0466 void KateThemeConfigColorTab::reload()
0467 {
0468     // drop all cached data
0469     m_schemas.clear();
0470 
0471     // trigger re-creation of ui from theme
0472     const auto backupName = m_currentSchema;
0473     m_currentSchema.clear();
0474     schemaChanged(backupName);
0475 }
0476 
0477 QColor KateThemeConfigColorTab::backgroundColor() const
0478 {
0479     return ui->findColor(QStringLiteral("Color Background"));
0480 }
0481 
0482 QColor KateThemeConfigColorTab::selectionColor() const
0483 {
0484     return ui->findColor(QStringLiteral("Color Selection"));
0485 }
0486 // END KateThemeConfigColorTab
0487 
0488 // BEGIN FontColorConfig -- 'Normal Text Styles' tab
0489 KateThemeConfigDefaultStylesTab::KateThemeConfigDefaultStylesTab(KateThemeConfigColorTab *colorTab)
0490 {
0491     m_colorTab = colorTab;
0492 
0493     // size management
0494     QGridLayout *grid = new QGridLayout(this);
0495 
0496     m_defaultStyles = new KateStyleTreeWidget(this);
0497     connect(m_defaultStyles, &KateStyleTreeWidget::changed, this, &KateThemeConfigDefaultStylesTab::changed);
0498     grid->addWidget(m_defaultStyles, 0, 0);
0499 
0500     m_defaultStyles->setWhatsThis(
0501         i18n("<p>This list displays the default styles for the current color theme and "
0502              "offers the means to edit them. The style name reflects the current "
0503              "style settings.</p>"
0504              "<p>To edit the colors, click the colored squares, or select the color "
0505              "to edit from the popup menu.</p><p>You can unset the Background and Selected "
0506              "Background colors from the popup menu when appropriate.</p>"));
0507 }
0508 
0509 KateAttributeList *KateThemeConfigDefaultStylesTab::attributeList(const QString &schema)
0510 {
0511     auto it = m_defaultStyleLists.find(schema);
0512     if (it == m_defaultStyleLists.end()) {
0513         // get list of all default styles
0514         const auto numStyles = KTextEditor::defaultStyleCount();
0515         KateAttributeList list;
0516         list.reserve(numStyles);
0517         const KSyntaxHighlighting::Theme currentTheme = KateHlManager::self()->repository().theme(schema);
0518         for (int z = 0; z < numStyles; z++) {
0519             KTextEditor::Attribute::Ptr i(new KTextEditor::Attribute());
0520             const auto style = defaultStyleToTextStyle(static_cast<KTextEditor::DefaultStyle>(z));
0521 
0522             if (const auto col = currentTheme.textColor(style)) {
0523                 i->setForeground(QColor::fromRgba(col));
0524             }
0525 
0526             if (const auto col = currentTheme.selectedTextColor(style)) {
0527                 i->setSelectedForeground(QColor::fromRgba(col));
0528             }
0529 
0530             if (const auto col = currentTheme.backgroundColor(style)) {
0531                 i->setBackground(QColor::fromRgba(col));
0532             } else {
0533                 i->clearBackground();
0534             }
0535 
0536             if (const auto col = currentTheme.selectedBackgroundColor(style)) {
0537                 i->setSelectedBackground(QColor::fromRgba(col));
0538             } else {
0539                 i->clearProperty(SelectedBackground);
0540             }
0541 
0542             i->setFontBold(currentTheme.isBold(style));
0543             i->setFontItalic(currentTheme.isItalic(style));
0544             i->setFontUnderline(currentTheme.isUnderline(style));
0545             i->setFontStrikeOut(currentTheme.isStrikeThrough(style));
0546             list.append(i);
0547         }
0548         it = m_defaultStyleLists.emplace(schema, list).first;
0549     }
0550 
0551     return &(it->second);
0552 }
0553 
0554 void KateThemeConfigDefaultStylesTab::schemaChanged(const QString &schema)
0555 {
0556     // ensure invalid or read-only stuff can't be changed
0557     const auto theme = KateHlManager::self()->repository().theme(schema);
0558     m_defaultStyles->setReadOnly(!theme.isValid() || theme.isReadOnly());
0559 
0560     m_currentSchema = schema;
0561 
0562     m_defaultStyles->clear();
0563 
0564     KateAttributeList *l = attributeList(schema);
0565     updateColorPalette(l->at(0)->foreground().color());
0566 
0567     // normal text and source code
0568     QTreeWidgetItem *parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Normal Text & Source Code"));
0569     parent->setFirstColumnSpanned(true);
0570     for (int i = (int)KTextEditor::dsNormal; i <= (int)KTextEditor::dsAttribute; ++i) {
0571         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KTextEditor::DefaultStyle>(i)), l->at(i));
0572     }
0573 
0574     // Number, Types & Constants
0575     parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Numbers, Types & Constants"));
0576     parent->setFirstColumnSpanned(true);
0577     for (int i = (int)KTextEditor::dsDataType; i <= (int)KTextEditor::dsConstant; ++i) {
0578         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KTextEditor::DefaultStyle>(i)), l->at(i));
0579     }
0580 
0581     // strings & characters
0582     parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Strings & Characters"));
0583     parent->setFirstColumnSpanned(true);
0584     for (int i = (int)KTextEditor::dsChar; i <= (int)KTextEditor::dsImport; ++i) {
0585         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KTextEditor::DefaultStyle>(i)), l->at(i));
0586     }
0587 
0588     // comments & documentation
0589     parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Comments & Documentation"));
0590     parent->setFirstColumnSpanned(true);
0591     for (int i = (int)KTextEditor::dsComment; i <= (int)KTextEditor::dsAlert; ++i) {
0592         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KTextEditor::DefaultStyle>(i)), l->at(i));
0593     }
0594 
0595     // Misc
0596     parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Miscellaneous"));
0597     parent->setFirstColumnSpanned(true);
0598     for (int i = (int)KTextEditor::dsOthers; i <= (int)KTextEditor::dsError; ++i) {
0599         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KTextEditor::DefaultStyle>(i)), l->at(i));
0600     }
0601 
0602     m_defaultStyles->expandAll();
0603 }
0604 
0605 void KateThemeConfigDefaultStylesTab::updateColorPalette(const QColor &textColor)
0606 {
0607     QPalette p(m_defaultStyles->palette());
0608     p.setColor(QPalette::Base, m_colorTab->backgroundColor());
0609     p.setColor(QPalette::Highlight, m_colorTab->selectionColor());
0610     p.setColor(QPalette::Text, textColor);
0611     m_defaultStyles->setPalette(p);
0612 }
0613 
0614 void KateThemeConfigDefaultStylesTab::reload()
0615 {
0616     m_defaultStyles->clear();
0617     m_defaultStyleLists.clear();
0618 
0619     schemaChanged(m_currentSchema);
0620 }
0621 
0622 void KateThemeConfigDefaultStylesTab::apply()
0623 {
0624     // get enum meta data for json keys
0625     static const auto idx = KSyntaxHighlighting::Theme::staticMetaObject.indexOfEnumerator("TextStyle");
0626     Q_ASSERT(idx >= 0);
0627     const auto metaEnum = KSyntaxHighlighting::Theme::staticMetaObject.enumerator(idx);
0628 
0629     // export all configured styles of the cached themes
0630     for (const auto &kv : m_defaultStyleLists) {
0631         // get theme for key, skip invalid or read-only themes for writing
0632         const auto theme = KateHlManager::self()->repository().theme(kv.first);
0633         if (!theme.isValid() || theme.isReadOnly()) {
0634             continue;
0635         }
0636 
0637         // get current theme data from disk
0638         QJsonObject newThemeObject = jsonForTheme(theme);
0639 
0640         // patch the text-styles part
0641         QJsonObject styles;
0642         const auto numStyles = KTextEditor::defaultStyleCount();
0643         for (int z = 0; z < numStyles; z++) {
0644             QJsonObject style;
0645             KTextEditor::Attribute::Ptr p = kv.second.at(z);
0646             if (p->hasProperty(QTextFormat::ForegroundBrush)) {
0647                 style[QLatin1String("text-color")] = hexName(p->foreground().color());
0648             }
0649             if (p->hasProperty(QTextFormat::BackgroundBrush)) {
0650                 style[QLatin1String("background-color")] = hexName(p->background().color());
0651             }
0652             if (p->hasProperty(SelectedForeground)) {
0653                 style[QLatin1String("selected-text-color")] = hexName(p->selectedForeground().color());
0654             }
0655             if (p->hasProperty(SelectedBackground)) {
0656                 style[QLatin1String("selected-background-color")] = hexName(p->selectedBackground().color());
0657             }
0658             if (p->hasProperty(QTextFormat::FontWeight) && p->fontBold()) {
0659                 style[QLatin1String("bold")] = true;
0660             }
0661             if (p->hasProperty(QTextFormat::FontItalic) && p->fontItalic()) {
0662                 style[QLatin1String("italic")] = true;
0663             }
0664             if (p->hasProperty(QTextFormat::TextUnderlineStyle) && p->fontUnderline()) {
0665                 style[QLatin1String("underline")] = true;
0666             }
0667             if (p->hasProperty(QTextFormat::FontStrikeOut) && p->fontStrikeOut()) {
0668                 style[QLatin1String("strike-through")] = true;
0669             }
0670             styles[QLatin1String(metaEnum.key(defaultStyleToTextStyle(static_cast<KTextEditor::DefaultStyle>(z))))] = style;
0671         }
0672         newThemeObject[QLatin1String("text-styles")] = styles;
0673 
0674         // write json back to file
0675         writeJson(newThemeObject, theme.filePath());
0676     }
0677 }
0678 
0679 void KateThemeConfigDefaultStylesTab::showEvent(QShowEvent *event)
0680 {
0681     if (!event->spontaneous() && !m_currentSchema.isEmpty()) {
0682         KateAttributeList *l = attributeList(m_currentSchema);
0683         Q_ASSERT(l != nullptr);
0684         updateColorPalette(l->at(0)->foreground().color());
0685     }
0686 
0687     QWidget::showEvent(event);
0688 }
0689 // END FontColorConfig
0690 
0691 // BEGIN KateThemeConfigHighlightTab -- 'Highlighting Text Styles' tab
0692 KateThemeConfigHighlightTab::KateThemeConfigHighlightTab(KateThemeConfigDefaultStylesTab *page, KateThemeConfigColorTab *colorTab)
0693 {
0694     m_defaults = page;
0695     m_colorTab = colorTab;
0696 
0697     m_hl = 0;
0698 
0699     QVBoxLayout *layout = new QVBoxLayout(this);
0700 
0701     QHBoxLayout *headerLayout = new QHBoxLayout;
0702     layout->addLayout(headerLayout);
0703 
0704     QLabel *lHl = new QLabel(i18n("H&ighlight:"), this);
0705     headerLayout->addWidget(lHl);
0706 
0707     hlCombo = new QComboBox(this);
0708     hlCombo->setEditable(false);
0709     headerLayout->addWidget(hlCombo);
0710 
0711     lHl->setBuddy(hlCombo);
0712     connect(hlCombo, qOverload<int>(&QComboBox::activated), this, &KateThemeConfigHighlightTab::hlChanged);
0713 
0714     headerLayout->addStretch();
0715 
0716     const auto modeList = KateHlManager::self()->modeList();
0717     for (const auto &hl : modeList) {
0718         const auto section = hl.translatedSection();
0719         if (!section.isEmpty()) {
0720             hlCombo->addItem(section + QLatin1Char('/') + hl.translatedName());
0721         } else {
0722             hlCombo->addItem(hl.translatedName());
0723         }
0724     }
0725     hlCombo->setCurrentIndex(0);
0726 
0727     // styles listview
0728     m_styles = new KateStyleTreeWidget(this, true);
0729     connect(m_styles, &KateStyleTreeWidget::changed, this, &KateThemeConfigHighlightTab::changed);
0730     layout->addWidget(m_styles, 999);
0731 
0732     // get current highlighting from the host application
0733     int hl = 0;
0734     KTextEditor::ViewPrivate *kv =
0735         qobject_cast<KTextEditor::ViewPrivate *>(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView());
0736     if (kv) {
0737         const QString hlName = kv->doc()->highlight()->name();
0738         hl = KateHlManager::self()->nameFind(hlName);
0739         Q_ASSERT(hl >= 0);
0740     }
0741 
0742     hlCombo->setCurrentIndex(hl);
0743     hlChanged(hl);
0744 
0745     m_styles->setWhatsThis(
0746         i18n("<p>This list displays the contexts of the current syntax highlight mode and "
0747              "offers the means to edit them. The context name reflects the current "
0748              "style settings.</p><p>To edit using the keyboard, press "
0749              "<strong>&lt;SPACE&gt;</strong> and choose a property from the popup menu.</p>"
0750              "<p>To edit the colors, click the colored squares, or select the color "
0751              "to edit from the popup menu.</p><p>You can unset the Background and Selected "
0752              "Background colors from the context menu when appropriate.</p>"));
0753 }
0754 
0755 void KateThemeConfigHighlightTab::hlChanged(int z)
0756 {
0757     m_hl = z;
0758     schemaChanged(m_schema);
0759 }
0760 
0761 /**
0762  * Helper to get the "default attributes" for the given schema + highlighting.
0763  * This means all stuff set without taking theme overrides for the highlighting into account.
0764  */
0765 static KateAttributeList defaultsForHighlighting(const std::vector<KSyntaxHighlighting::Format> &formats, const KateAttributeList &defaultStyleAttributes)
0766 {
0767     const KSyntaxHighlighting::Theme invalidTheme;
0768     KateAttributeList defaults;
0769     for (const auto &format : formats) {
0770         // create a KTextEditor attribute matching the default style for this format
0771         // use the default style attribute we got passed to have the one we currently have configured in the settings here
0772         KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(*defaultStyleAttributes.at(textStyleToDefaultStyle(format.textStyle()))));
0773 
0774         // check for override => if yes, set attribute as overridden, use invalid theme to avoid the usage of theme override!
0775 
0776         if (format.hasTextColorOverride()) {
0777             newAttribute->setForeground(format.textColor(invalidTheme));
0778         }
0779         if (format.hasBackgroundColorOverride()) {
0780             newAttribute->setBackground(format.backgroundColor(invalidTheme));
0781         }
0782         if (format.hasSelectedTextColorOverride()) {
0783             newAttribute->setSelectedForeground(format.selectedTextColor(invalidTheme));
0784         }
0785         if (format.hasSelectedBackgroundColorOverride()) {
0786             newAttribute->setSelectedBackground(format.selectedBackgroundColor(invalidTheme));
0787         }
0788         if (format.hasBoldOverride()) {
0789             newAttribute->setFontBold(format.isBold(invalidTheme));
0790         }
0791         if (format.hasItalicOverride()) {
0792             newAttribute->setFontItalic(format.isItalic(invalidTheme));
0793         }
0794         if (format.hasUnderlineOverride()) {
0795             newAttribute->setFontUnderline(format.isUnderline(invalidTheme));
0796         }
0797         if (format.hasStrikeThroughOverride()) {
0798             newAttribute->setFontStrikeOut(format.isStrikeThrough(invalidTheme));
0799         }
0800 
0801         // not really relevant, set it as configured
0802         newAttribute->setSkipSpellChecking(format.spellCheck());
0803         defaults.append(newAttribute);
0804     }
0805     return defaults;
0806 }
0807 
0808 void KateThemeConfigHighlightTab::schemaChanged(const QString &schema)
0809 {
0810     // ensure invalid or read-only stuff can't be changed
0811     const auto theme = KateHlManager::self()->repository().theme(schema);
0812 
0813     // NOTE: None (m_hl == 0) can't be changed with the current way
0814     // TODO: removed it from the list?
0815     const auto isNoneSchema = m_hl == 0;
0816     m_styles->setReadOnly(!theme.isValid() || theme.isReadOnly() || isNoneSchema);
0817 
0818     m_schema = schema;
0819 
0820     m_styles->clear();
0821 
0822     auto it = m_hlDict.find(m_schema);
0823     if (it == m_hlDict.end()) {
0824         it = m_hlDict.insert(schema, QHash<int, QVector<KTextEditor::Attribute::Ptr>>());
0825     }
0826 
0827     // Set listview colors
0828     KateAttributeList *l = m_defaults->attributeList(schema);
0829     updateColorPalette(l->at(0)->foreground().color());
0830 
0831     // create unified stuff
0832     auto attributes = KateHlManager::self()->getHl(m_hl)->attributesForDefinition(m_schema);
0833     auto formats = KateHlManager::self()->getHl(m_hl)->formats();
0834     auto defaults = defaultsForHighlighting(formats, *l);
0835 
0836     for (int i = 0; i < attributes.size(); ++i) {
0837         // All stylenames have their language mode prefixed, e.g. HTML:Comment
0838         // split them and put them into nice substructures.
0839         int c = attributes[i]->name().indexOf(QLatin1Char(':'));
0840         if (c <= 0) {
0841             continue;
0842         }
0843 
0844         QString highlighting = attributes[i]->name().left(c);
0845         QString name = attributes[i]->name().mid(c + 1);
0846         auto &uniqueAttribute = m_uniqueAttributes[m_schema][highlighting][name].first;
0847         auto &uniqueAttributeDefault = m_uniqueAttributes[m_schema][highlighting][name].second;
0848 
0849         if (uniqueAttribute.data()) {
0850             attributes[i] = uniqueAttribute;
0851         } else {
0852             uniqueAttribute = attributes[i];
0853         }
0854 
0855         if (uniqueAttributeDefault.data()) {
0856             defaults[i] = uniqueAttributeDefault;
0857         } else {
0858             uniqueAttributeDefault = defaults[i];
0859         }
0860     }
0861 
0862     auto &subMap = it.value();
0863     auto it1 = subMap.find(m_hl);
0864     if (it1 == subMap.end()) {
0865         it1 = subMap.insert(m_hl, attributes);
0866     }
0867 
0868     QHash<QString, QTreeWidgetItem *> prefixes;
0869     const auto &attribs = it1.value();
0870     auto vec_it = attribs.cbegin();
0871     int i = 0;
0872     while (vec_it != attribs.end()) {
0873         const KTextEditor::Attribute::Ptr itemData = *vec_it;
0874         Q_ASSERT(itemData);
0875 
0876         // All stylenames have their language mode prefixed, e.g. HTML:Comment
0877         // split them and put them into nice substructures.
0878         int c = itemData->name().indexOf(QLatin1Char(':'));
0879         if (c > 0) {
0880             QString prefix = itemData->name().left(c);
0881             QString name = itemData->name().mid(c + 1);
0882 
0883             QTreeWidgetItem *parent = prefixes[prefix];
0884             if (!parent) {
0885                 parent = new QTreeWidgetItem(m_styles, QStringList() << prefix);
0886                 m_styles->expandItem(parent);
0887                 prefixes.insert(prefix, parent);
0888             }
0889             m_styles->addItem(parent, name, defaults.at(i), itemData);
0890         } else {
0891             m_styles->addItem(itemData->name(), defaults.at(i), itemData);
0892         }
0893         ++vec_it;
0894         ++i;
0895     }
0896 
0897     m_styles->resizeColumns();
0898 }
0899 
0900 void KateThemeConfigHighlightTab::updateColorPalette(const QColor &textColor)
0901 {
0902     QPalette p(m_styles->palette());
0903     p.setColor(QPalette::Base, m_colorTab->backgroundColor());
0904     p.setColor(QPalette::Highlight, m_colorTab->selectionColor());
0905     p.setColor(QPalette::Text, textColor);
0906     m_styles->setPalette(p);
0907 }
0908 
0909 void KateThemeConfigHighlightTab::reload()
0910 {
0911     m_styles->clear();
0912 
0913     m_hlDict.clear();
0914     m_uniqueAttributes.clear();
0915 
0916     hlChanged(hlCombo->currentIndex());
0917 }
0918 
0919 void KateThemeConfigHighlightTab::apply()
0920 {
0921     // handle all cached themes data
0922     for (const auto &themeIt : m_uniqueAttributes) {
0923         // get theme for key, skip invalid or read-only themes for writing
0924         const auto theme = KateHlManager::self()->repository().theme(themeIt.first);
0925         if (!theme.isValid() || theme.isReadOnly()) {
0926             continue;
0927         }
0928 
0929         // get current theme data from disk
0930         QJsonObject newThemeObject = jsonForTheme(theme);
0931 
0932         // look at all highlightings we have info stored, important: keep info we did load from file and not overwrite here!
0933         QJsonObject overrides = newThemeObject[QLatin1String("custom-styles")].toObject();
0934         for (const auto &highlightingIt : themeIt.second) {
0935             // start with stuff we know from the loaded json
0936             const QString definitionName = highlightingIt.first;
0937             QJsonObject styles = overrides[definitionName].toObject();
0938             for (const auto &attributeIt : highlightingIt.second) {
0939                 QJsonObject style;
0940                 KTextEditor::Attribute::Ptr p = attributeIt.second.first;
0941                 KTextEditor::Attribute::Ptr pDefault = attributeIt.second.second;
0942                 if (p->hasProperty(QTextFormat::ForegroundBrush) && p->foreground().color() != pDefault->foreground().color()) {
0943                     style[QLatin1String("text-color")] = hexName(p->foreground().color());
0944                 }
0945                 if (p->hasProperty(QTextFormat::BackgroundBrush) && p->background().color() != pDefault->background().color()) {
0946                     style[QLatin1String("background-color")] = hexName(p->background().color());
0947                 }
0948                 if (p->hasProperty(SelectedForeground) && p->selectedForeground().color() != pDefault->selectedForeground().color()) {
0949                     style[QLatin1String("selected-text-color")] = hexName(p->selectedForeground().color());
0950                 }
0951                 if (p->hasProperty(SelectedBackground) && p->selectedBackground().color() != pDefault->selectedBackground().color()) {
0952                     style[QLatin1String("selected-background-color")] = hexName(p->selectedBackground().color());
0953                 }
0954                 if (p->hasProperty(QTextFormat::FontWeight) && p->fontBold() != pDefault->fontBold()) {
0955                     style[QLatin1String("bold")] = p->fontBold();
0956                 }
0957                 if (p->hasProperty(QTextFormat::FontItalic) && p->fontItalic() != pDefault->fontItalic()) {
0958                     style[QLatin1String("italic")] = p->fontItalic();
0959                 }
0960                 if (p->hasProperty(QTextFormat::TextUnderlineStyle) && p->fontUnderline() != pDefault->fontUnderline()) {
0961                     style[QLatin1String("underline")] = p->fontUnderline();
0962                 }
0963                 if (p->hasProperty(QTextFormat::FontStrikeOut) && p->fontStrikeOut() != pDefault->fontStrikeOut()) {
0964                     style[QLatin1String("strike-through")] = p->fontStrikeOut();
0965                 }
0966 
0967                 // either set the new stuff or erase the old entry we might have set from the loaded json
0968                 if (!style.isEmpty()) {
0969                     styles[attributeIt.first] = style;
0970                 } else {
0971                     styles.remove(attributeIt.first);
0972                 }
0973             }
0974 
0975             // either set the new stuff or erase the old entry we might have set from the loaded json
0976             if (!styles.isEmpty()) {
0977                 overrides[definitionName] = styles;
0978             } else {
0979                 overrides.remove(definitionName);
0980             }
0981         }
0982 
0983         // we set even empty overrides, to ensure we overwrite stuff!
0984         newThemeObject[QLatin1String("custom-styles")] = overrides;
0985 
0986         // write json back to file
0987         writeJson(newThemeObject, theme.filePath());
0988     }
0989 }
0990 
0991 QList<int> KateThemeConfigHighlightTab::hlsForSchema(const QString &schema)
0992 {
0993     auto it = m_hlDict.find(schema);
0994     if (it != m_hlDict.end()) {
0995         return it.value().keys();
0996     }
0997     return {};
0998 }
0999 
1000 void 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 
1008     QWidget::showEvent(event);
1009 }
1010 // END KateThemeConfigHighlightTab
1011 
1012 // BEGIN KateThemeConfigPage -- Main dialog page
1013 KateThemeConfigPage::KateThemeConfigPage(QWidget *parent)
1014     : KateConfigPage(parent)
1015 {
1016     QHBoxLayout *layout = new QHBoxLayout(this);
1017     layout->setContentsMargins({});
1018 
1019     QTabWidget *tabWidget = new QTabWidget(this);
1020     layout->addWidget(tabWidget);
1021 
1022     auto *themeEditor = new QWidget(this);
1023     auto *themeChooser = new QWidget(this);
1024     tabWidget->addTab(themeChooser, i18n("Default Theme"));
1025     tabWidget->addTab(themeEditor, i18n("Theme Editor"));
1026     layoutThemeChooserTab(themeChooser);
1027     layoutThemeEditorTab(themeEditor);
1028 
1029     reload();
1030 }
1031 
1032 void KateThemeConfigPage::layoutThemeChooserTab(QWidget *tab)
1033 {
1034     QVBoxLayout *layout = new QVBoxLayout(tab);
1035     layout->setContentsMargins({});
1036 
1037     auto *comboLayout = new QHBoxLayout;
1038 
1039     auto lHl = new QLabel(i18n("Select theme:"), this);
1040     comboLayout->addWidget(lHl);
1041 
1042     defaultSchemaCombo = new QComboBox(this);
1043     comboLayout->addWidget(defaultSchemaCombo);
1044     defaultSchemaCombo->setEditable(false);
1045     lHl->setBuddy(defaultSchemaCombo);
1046     connect(defaultSchemaCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, &KateThemeConfigPage::slotChanged);
1047     comboLayout->addStretch();
1048 
1049     layout->addLayout(comboLayout);
1050 
1051     m_doc = new KTextEditor::DocumentPrivate;
1052     m_doc->setParent(this);
1053 
1054     const auto code = R"sample(/**
1055 * SPDX-FileCopyrightText: 2020 Christoph Cullmann <cullmann@kde.org>
1056 * SPDX-License-Identifier: MIT
1057 */
1058 
1059 // BEGIN
1060 #include <QString>
1061 #include <string>
1062 // END
1063 
1064 /**
1065 * TODO: improve documentation
1066 * @param magicArgument some magic argument
1067 * @return magic return value
1068 */
1069 int main(uint64_t magicArgument)
1070 {
1071     if (magicArgument > 1) {
1072         const std::string string = "source file: \"" __FILE__ "\"";
1073         const QString qString(QStringLiteral("test"));
1074         return qrand();
1075     }
1076 
1077     /* BUG: bogus integer constant inside next line */
1078     const double g = 1.1e12 * 0b01'01'01'01 - 43a + 0x11234 * 0234ULL - 'c' * 42;
1079     return g > 1.3f;
1080 })sample";
1081 
1082     m_doc->setText(QString::fromUtf8(code));
1083     m_doc->setHighlightingMode(QStringLiteral("C++"));
1084     m_themePreview = new KTextEditor::ViewPrivate(m_doc, this);
1085 
1086     layout->addWidget(m_themePreview);
1087 
1088     connect(defaultSchemaCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int idx) {
1089         const QString schema = defaultSchemaCombo->itemData(idx).toString();
1090         m_themePreview->renderer()->config()->setSchema(schema);
1091         if (schema.isEmpty()) {
1092             m_themePreview->renderer()->config()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1093         } else {
1094             m_themePreview->renderer()->config()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1095         }
1096     });
1097 }
1098 
1099 void KateThemeConfigPage::layoutThemeEditorTab(QWidget *tab)
1100 {
1101     QVBoxLayout *layout = new QVBoxLayout(tab);
1102     layout->setContentsMargins(0, 0, 0, 0);
1103 
1104     // header
1105     QHBoxLayout *headerLayout = new QHBoxLayout;
1106     layout->addLayout(headerLayout);
1107 
1108     QLabel *lHl = new QLabel(i18n("&Theme:"), this);
1109     headerLayout->addWidget(lHl);
1110 
1111     schemaCombo = new QComboBox(this);
1112     schemaCombo->setEditable(false);
1113     lHl->setBuddy(schemaCombo);
1114     headerLayout->addWidget(schemaCombo);
1115     connect(schemaCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, &KateThemeConfigPage::comboBoxIndexChanged);
1116 
1117     QPushButton *copyButton = new QPushButton(i18n("&Copy..."), this);
1118     headerLayout->addWidget(copyButton);
1119     connect(copyButton, &QPushButton::clicked, this, &KateThemeConfigPage::copyTheme);
1120 
1121     btndel = new QPushButton(i18n("&Delete"), this);
1122     headerLayout->addWidget(btndel);
1123     connect(btndel, &QPushButton::clicked, this, &KateThemeConfigPage::deleteSchema);
1124 
1125     QPushButton *btnexport = new QPushButton(i18n("Export..."), this);
1126     headerLayout->addWidget(btnexport);
1127     connect(btnexport, &QPushButton::clicked, this, &KateThemeConfigPage::exportFullSchema);
1128 
1129     QPushButton *btnimport = new QPushButton(i18n("Import..."), this);
1130     headerLayout->addWidget(btnimport);
1131     connect(btnimport, &QPushButton::clicked, this, &KateThemeConfigPage::importFullSchema);
1132 
1133     headerLayout->addStretch();
1134 
1135     // label to inform about read-only state
1136     m_readOnlyThemeLabel = new KMessageWidget(i18n("Bundled read-only theme. To modify the theme, please copy it."), this);
1137     m_readOnlyThemeLabel->setCloseButtonVisible(false);
1138     m_readOnlyThemeLabel->setMessageType(KMessageWidget::Information);
1139     m_readOnlyThemeLabel->hide();
1140     layout->addWidget(m_readOnlyThemeLabel);
1141 
1142     // tabs
1143     QTabWidget *tabWidget = new QTabWidget(this);
1144     layout->addWidget(tabWidget);
1145 
1146     m_colorTab = new KateThemeConfigColorTab();
1147     tabWidget->addTab(m_colorTab, i18n("Colors"));
1148     connect(m_colorTab, &KateThemeConfigColorTab::changed, this, &KateThemeConfigPage::slotChanged);
1149 
1150     m_defaultStylesTab = new KateThemeConfigDefaultStylesTab(m_colorTab);
1151     tabWidget->addTab(m_defaultStylesTab, i18n("Default Text Styles"));
1152     connect(m_defaultStylesTab, &KateThemeConfigDefaultStylesTab::changed, this, &KateThemeConfigPage::slotChanged);
1153 
1154     m_highlightTab = new KateThemeConfigHighlightTab(m_defaultStylesTab, m_colorTab);
1155     tabWidget->addTab(m_highlightTab, i18n("Highlighting Text Styles"));
1156     connect(m_highlightTab, &KateThemeConfigHighlightTab::changed, this, &KateThemeConfigPage::slotChanged);
1157 
1158     QHBoxLayout *footLayout = new QHBoxLayout;
1159     layout->addLayout(footLayout);
1160 }
1161 
1162 void KateThemeConfigPage::exportFullSchema()
1163 {
1164     // get save destination
1165     const QString currentSchemaName = m_currentSchema;
1166     const QString destName = QFileDialog::getSaveFileName(this,
1167                                                           i18n("Exporting color theme: %1", currentSchemaName),
1168                                                           currentSchemaName + QLatin1String(".theme"),
1169                                                           QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1170     if (destName.isEmpty()) {
1171         return;
1172     }
1173 
1174     // get current theme
1175     const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1176     const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1177 
1178     // ensure we overwrite
1179     if (QFile::exists(destName)) {
1180         QFile::remove(destName);
1181     }
1182 
1183     // export is easy, just copy the file 1:1
1184     QFile::copy(currentTheme.filePath(), destName);
1185 }
1186 
1187 void KateThemeConfigPage::importFullSchema()
1188 {
1189     const QString srcName =
1190         QFileDialog::getOpenFileName(this, i18n("Importing Color Theme"), QString(), QStringLiteral("%1 (*.theme)").arg(i18n("Color theme")));
1191     if (srcName.isEmpty()) {
1192         return;
1193     }
1194 
1195     // location to write theme files to
1196     const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1197 
1198     // construct file name for imported theme
1199     const QString themesFullFileName = themesPath + QStringLiteral("/") + QFileInfo(srcName).fileName();
1200 
1201     // if something might be overwritten, as the user
1202     if (QFile::exists(themesFullFileName)) {
1203         if (KMessageBox::warningContinueCancel(this,
1204                                                i18n("Importing will overwrite the existing theme file \"%1\". This can not be undone.", themesFullFileName),
1205                                                i18n("Possible Data Loss"),
1206                                                KGuiItem(i18n("Import Nevertheless")),
1207                                                KStandardGuiItem::cancel())
1208             != KMessageBox::Continue) {
1209             return;
1210         }
1211     }
1212 
1213     // copy theme file, we might need to create the local dir first
1214     QDir().mkpath(themesPath);
1215 
1216     // ensure we overwrite
1217     if (QFile::exists(themesFullFileName)) {
1218         QFile::remove(themesFullFileName);
1219     }
1220     QFile::copy(srcName, themesFullFileName);
1221 
1222     // reload themes DB & clear all attributes
1223     KateHlManager::self()->reload();
1224     for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1225         KateHlManager::self()->getHl(i)->clearAttributeArrays();
1226     }
1227 
1228     // KateThemeManager::update() sorts the schema alphabetically, hence the
1229     // schema indexes change. Thus, repopulate the schema list...
1230     refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1231 }
1232 
1233 void KateThemeConfigPage::apply()
1234 {
1235     // remember name + index
1236     const QString schemaName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1237 
1238     // first apply all tabs
1239     m_colorTab->apply();
1240     m_defaultStylesTab->apply();
1241     m_highlightTab->apply();
1242 
1243     // reload themes DB & clear all attributes
1244     KateHlManager::self()->reload();
1245     for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1246         KateHlManager::self()->getHl(i)->clearAttributeArrays();
1247     }
1248 
1249     // than reload the whole stuff, special handle auto selection == empty theme name
1250     const auto defaultTheme = defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString();
1251     if (defaultTheme.isEmpty()) {
1252         KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, true);
1253     } else {
1254         KateRendererConfig::global()->setValue(KateRendererConfig::AutoColorThemeSelection, false);
1255         KateRendererConfig::global()->setSchema(defaultTheme);
1256     }
1257     KateRendererConfig::global()->reloadSchema();
1258 
1259     // KateThemeManager::update() sorts the schema alphabetically, hence the
1260     // schema indexes change. Thus, repopulate the schema list...
1261     refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString());
1262     schemaChanged(schemaName);
1263 }
1264 
1265 void KateThemeConfigPage::reload()
1266 {
1267     // reinitialize combo boxes
1268     refillCombos(KateRendererConfig::global()->schema(), KateRendererConfig::global()->schema());
1269 
1270     // finally, activate the current schema again
1271     schemaChanged(schemaCombo->itemData(schemaCombo->currentIndex()).toString());
1272 
1273     // all tabs need to reload to discard all the cached data, as the index
1274     // mapping may have changed
1275     m_colorTab->reload();
1276     m_defaultStylesTab->reload();
1277     m_highlightTab->reload();
1278 }
1279 
1280 void KateThemeConfigPage::refillCombos(const QString &schemaName, const QString &defaultSchemaName)
1281 {
1282     schemaCombo->blockSignals(true);
1283     defaultSchemaCombo->blockSignals(true);
1284 
1285     // reinitialize combo boxes
1286     schemaCombo->clear();
1287     defaultSchemaCombo->clear();
1288     defaultSchemaCombo->addItem(i18n("Follow System Color Scheme"), QString());
1289     defaultSchemaCombo->insertSeparator(1);
1290     const auto themes = KateHlManager::self()->sortedThemes();
1291     for (const auto &theme : themes) {
1292         schemaCombo->addItem(theme.translatedName(), theme.name());
1293         defaultSchemaCombo->addItem(theme.translatedName(), theme.name());
1294     }
1295 
1296     // set the correct indexes again, fallback to always existing default theme
1297     int schemaIndex = schemaCombo->findData(schemaName);
1298     if (schemaIndex == -1) {
1299         schemaIndex = schemaCombo->findData(
1300             KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name());
1301     }
1302 
1303     // set the correct indexes again, fallback to auto-selection
1304     int defaultSchemaIndex = 0;
1305     if (!KateRendererConfig::global()->value(KateRendererConfig::AutoColorThemeSelection).toBool()) {
1306         defaultSchemaIndex = defaultSchemaCombo->findData(defaultSchemaName);
1307         if (defaultSchemaIndex == -1) {
1308             defaultSchemaIndex = 0;
1309         }
1310     }
1311 
1312     Q_ASSERT(schemaIndex != -1);
1313     Q_ASSERT(defaultSchemaIndex != -1);
1314 
1315     defaultSchemaCombo->setCurrentIndex(defaultSchemaIndex);
1316     schemaCombo->setCurrentIndex(schemaIndex);
1317 
1318     schemaCombo->blockSignals(false);
1319     defaultSchemaCombo->blockSignals(false);
1320 
1321     m_themePreview->renderer()->config()->setSchema(defaultSchemaName);
1322 }
1323 
1324 void KateThemeConfigPage::reset()
1325 {
1326     // reload themes DB & clear all attributes
1327     KateHlManager::self()->reload();
1328     for (int i = 0; i < KateHlManager::self()->modeList().size(); ++i) {
1329         KateHlManager::self()->getHl(i)->clearAttributeArrays();
1330     }
1331 
1332     // reload the view
1333     reload();
1334 }
1335 
1336 void KateThemeConfigPage::defaults()
1337 {
1338     reset();
1339 }
1340 
1341 void KateThemeConfigPage::deleteSchema()
1342 {
1343     const int comboIndex = schemaCombo->currentIndex();
1344     const QString schemaNameToDelete = schemaCombo->itemData(comboIndex).toString();
1345 
1346     // KSyntaxHighlighting themes can not be deleted, skip invalid themes, too
1347     const auto theme = KateHlManager::self()->repository().theme(schemaNameToDelete);
1348     if (!theme.isValid() || theme.isReadOnly()) {
1349         return;
1350     }
1351 
1352     // ask the user again, this can't be undone
1353     if (KMessageBox::warningContinueCancel(this,
1354                                            i18n("Do you really want to delete the theme \"%1\"? This can not be undone.", schemaNameToDelete),
1355                                            i18n("Possible Data Loss"),
1356                                            KGuiItem(i18n("Delete Nevertheless")),
1357                                            KStandardGuiItem::cancel())
1358         != KMessageBox::Continue) {
1359         return;
1360     }
1361 
1362     // purge the theme file
1363     QFile::remove(theme.filePath());
1364 
1365     // reset syntax manager repo to flush deleted theme
1366     KateHlManager::self()->reload();
1367 
1368     // fallback to Default schema + auto
1369     schemaCombo->setCurrentIndex(schemaCombo->findData(
1370         QVariant(KTextEditor::EditorPrivate::self()->hlManager()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme).name())));
1371     if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(schemaNameToDelete)) {
1372         defaultSchemaCombo->setCurrentIndex(0);
1373     }
1374 
1375     // remove schema from combo box
1376     schemaCombo->removeItem(comboIndex);
1377     defaultSchemaCombo->removeItem(comboIndex);
1378 
1379     // Reload the color tab, since it uses cached schemas
1380     m_colorTab->reload();
1381 }
1382 
1383 bool KateThemeConfigPage::copyTheme()
1384 {
1385     // get current theme data as template
1386     const QString currentThemeName = schemaCombo->itemData(schemaCombo->currentIndex()).toString();
1387     const auto currentTheme = KateHlManager::self()->repository().theme(currentThemeName);
1388 
1389     // location to write theme files to
1390     const QString themesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/themes");
1391 
1392     // get sane name
1393     QString schemaName;
1394     QString themeFileName;
1395     while (schemaName.isEmpty()) {
1396         QInputDialog newNameDialog(this);
1397         newNameDialog.setInputMode(QInputDialog::TextInput);
1398         newNameDialog.setWindowTitle(i18n("Copy theme"));
1399         newNameDialog.setLabelText(i18n("Name for copy of color theme \"%1\":", currentThemeName));
1400         newNameDialog.setTextValue(currentThemeName);
1401         if (newNameDialog.exec() == QDialog::Rejected) {
1402             return false;
1403         }
1404         schemaName = newNameDialog.textValue();
1405 
1406         // try if schema already around => if yes, retry name input
1407         // we try for duplicated file names, too
1408         themeFileName = themesPath + QStringLiteral("/") + schemaName + QStringLiteral(".theme");
1409         if (KateHlManager::self()->repository().theme(schemaName).isValid() || QFile::exists(themeFileName)) {
1410             KMessageBox::information(this,
1411                                      i18n("<p>The theme \"%1\" already exists.</p><p>Please choose a different theme name.</p>", schemaName),
1412                                      i18n("Copy Theme"));
1413             schemaName.clear();
1414         }
1415     }
1416 
1417     // get json for current theme
1418     QJsonObject newThemeObject = jsonForTheme(currentTheme);
1419     QJsonObject metaData;
1420     metaData[QLatin1String("revision")] = 1;
1421     metaData[QLatin1String("name")] = schemaName;
1422     newThemeObject[QLatin1String("metadata")] = metaData;
1423 
1424     // write to new theme file, we might need to create the local dir first
1425     QDir().mkpath(themesPath);
1426     if (!writeJson(newThemeObject, themeFileName)) {
1427         return false;
1428     }
1429 
1430     // reset syntax manager repo to find new theme
1431     KateHlManager::self()->reload();
1432 
1433     // append items to combo boxes
1434     schemaCombo->addItem(schemaName, QVariant(schemaName));
1435     defaultSchemaCombo->addItem(schemaName, QVariant(schemaName));
1436 
1437     // finally, activate new schema (last item in the list)
1438     schemaCombo->setCurrentIndex(schemaCombo->count() - 1);
1439     return true;
1440 }
1441 
1442 void KateThemeConfigPage::schemaChanged(const QString &schema)
1443 {
1444     // we can't delete read-only themes, e.g. the stuff shipped inside Qt resources or system wide installed
1445     const auto theme = KateHlManager::self()->repository().theme(schema);
1446     btndel->setEnabled(!theme.isReadOnly());
1447     m_readOnlyThemeLabel->setVisible(theme.isReadOnly());
1448 
1449     // propagate changed schema to all tabs
1450     m_colorTab->schemaChanged(schema);
1451     m_defaultStylesTab->schemaChanged(schema);
1452     m_highlightTab->schemaChanged(schema);
1453 
1454     // save current schema index
1455     m_currentSchema = schema;
1456 }
1457 
1458 void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex)
1459 {
1460     schemaChanged(schemaCombo->itemData(currentIndex).toString());
1461 }
1462 
1463 QString KateThemeConfigPage::name() const
1464 {
1465     return i18n("Color Themes");
1466 }
1467 
1468 QString KateThemeConfigPage::fullName() const
1469 {
1470     return i18n("Color Themes");
1471 }
1472 
1473 QIcon KateThemeConfigPage::icon() const
1474 {
1475     return QIcon::fromTheme(QStringLiteral("preferences-desktop-color"));
1476 }
1477 
1478 // END KateThemeConfigPage
1479 
1480 #include "moc_katethemeconfig.cpp"