File indexing completed on 2024-04-21 03:57:44

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 "katestyletreewidget.h"
0019 #include "katesyntaxmanager.h"
0020 #include "kateview.h"
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <KMessageWidget>
0025 
0026 #include <QComboBox>
0027 #include <QFileDialog>
0028 #include <QGridLayout>
0029 #include <QInputDialog>
0030 #include <QJsonObject>
0031 #include <QLabel>
0032 #include <QMetaEnum>
0033 #include <QPushButton>
0034 #include <QShowEvent>
0035 #include <QTabWidget>
0036 
0037 // END
0038 
0039 /**
0040  * Return the translated name of default style @p n.
0041  */
0042 static inline QString defaultStyleName(KSyntaxHighlighting::Theme::TextStyle style)
0043 {
0044     using namespace KTextEditor;
0045     switch (style) {
0046     case KSyntaxHighlighting::Theme::TextStyle::Normal:
0047         return i18nc("@item:intable Text context", "Normal");
0048     case KSyntaxHighlighting::Theme::TextStyle::Keyword:
0049         return i18nc("@item:intable Text context", "Keyword");
0050     case KSyntaxHighlighting::Theme::TextStyle::Function:
0051         return i18nc("@item:intable Text context", "Function");
0052     case KSyntaxHighlighting::Theme::TextStyle::Variable:
0053         return i18nc("@item:intable Text context", "Variable");
0054     case KSyntaxHighlighting::Theme::TextStyle::ControlFlow:
0055         return i18nc("@item:intable Text context", "Control Flow");
0056     case KSyntaxHighlighting::Theme::TextStyle::Operator:
0057         return i18nc("@item:intable Text context", "Operator");
0058     case KSyntaxHighlighting::Theme::TextStyle::BuiltIn:
0059         return i18nc("@item:intable Text context", "Built-in");
0060     case KSyntaxHighlighting::Theme::TextStyle::Extension:
0061         return i18nc("@item:intable Text context", "Extension");
0062     case KSyntaxHighlighting::Theme::TextStyle::Preprocessor:
0063         return i18nc("@item:intable Text context", "Preprocessor");
0064     case KSyntaxHighlighting::Theme::TextStyle::Attribute:
0065         return i18nc("@item:intable Text context", "Attribute");
0066 
0067     case KSyntaxHighlighting::Theme::TextStyle::Char:
0068         return i18nc("@item:intable Text context", "Character");
0069     case KSyntaxHighlighting::Theme::TextStyle::SpecialChar:
0070         return i18nc("@item:intable Text context", "Special Character");
0071     case KSyntaxHighlighting::Theme::TextStyle::String:
0072         return i18nc("@item:intable Text context", "String");
0073     case KSyntaxHighlighting::Theme::TextStyle::VerbatimString:
0074         return i18nc("@item:intable Text context", "Verbatim String");
0075     case KSyntaxHighlighting::Theme::TextStyle::SpecialString:
0076         return i18nc("@item:intable Text context", "Special String");
0077     case KSyntaxHighlighting::Theme::TextStyle::Import:
0078         return i18nc("@item:intable Text context", "Imports, Modules, Includes");
0079 
0080     case KSyntaxHighlighting::Theme::TextStyle::DataType:
0081         return i18nc("@item:intable Text context", "Data Type");
0082     case KSyntaxHighlighting::Theme::TextStyle::DecVal:
0083         return i18nc("@item:intable Text context", "Decimal/Value");
0084     case KSyntaxHighlighting::Theme::TextStyle::BaseN:
0085         return i18nc("@item:intable Text context", "Base-N Integer");
0086     case KSyntaxHighlighting::Theme::TextStyle::Float:
0087         return i18nc("@item:intable Text context", "Floating Point");
0088     case KSyntaxHighlighting::Theme::TextStyle::Constant:
0089         return i18nc("@item:intable Text context", "Constant");
0090 
0091     case KSyntaxHighlighting::Theme::TextStyle::Comment:
0092         return i18nc("@item:intable Text context", "Comment");
0093     case KSyntaxHighlighting::Theme::TextStyle::Documentation:
0094         return i18nc("@item:intable Text context", "Documentation");
0095     case KSyntaxHighlighting::Theme::TextStyle::Annotation:
0096         return i18nc("@item:intable Text context", "Annotation");
0097     case KSyntaxHighlighting::Theme::TextStyle::CommentVar:
0098         return i18nc("@item:intable Text context", "Comment Variable");
0099     case KSyntaxHighlighting::Theme::TextStyle::RegionMarker:
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 KSyntaxHighlighting::Theme::TextStyle::Information:
0103         return i18nc("@item:intable Text context", "Information");
0104     case KSyntaxHighlighting::Theme::TextStyle::Warning:
0105         return i18nc("@item:intable Text context", "Warning");
0106     case KSyntaxHighlighting::Theme::TextStyle::Alert:
0107         return i18nc("@item:intable Text context", "Alert");
0108 
0109     case KSyntaxHighlighting::Theme::TextStyle::Others:
0110         return i18nc("@item:intable Text context", "Others");
0111     case KSyntaxHighlighting::Theme::TextStyle::Error:
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 QList<KateColorItem> colorItemList(const KSyntaxHighlighting::Theme &theme)
0175 {
0176     QList<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         QList<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 = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount();
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 = static_cast<KSyntaxHighlighting::Theme::TextStyle>(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)KSyntaxHighlighting::Theme::TextStyle::Normal; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Attribute; ++i) {
0571         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(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)KSyntaxHighlighting::Theme::TextStyle::DataType; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Constant; ++i) {
0578         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(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)KSyntaxHighlighting::Theme::TextStyle::Char; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Import; ++i) {
0585         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(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)KSyntaxHighlighting::Theme::TextStyle::Comment; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Alert; ++i) {
0592         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(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)KSyntaxHighlighting::Theme::TextStyle::Others; i <= (int)KSyntaxHighlighting::Theme::TextStyle::Error; ++i) {
0599         m_defaultStyles->addItem(parent, defaultStyleName(static_cast<KSyntaxHighlighting::Theme::TextStyle>(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 = QMetaEnum::fromType<KSyntaxHighlighting::Theme::TextStyle>().keyCount();
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((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, &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(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, QList<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     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 
1033 void KateThemeConfigPage::layoutThemeChooserTab(QWidget *tab)
1034 {
1035     QVBoxLayout *layout = new QVBoxLayout(tab);
1036     layout->setContentsMargins({});
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 */
1070 int 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 
1100 void 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 
1163 void 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 
1188 void 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)) {
1204         if (KMessageBox::warningContinueCancel(this,
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")),
1208                                                KStandardGuiItem::cancel())
1209             != KMessageBox::Continue) {
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 
1234 void 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 
1266 void 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 
1281 void 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 
1325 void 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 
1337 void KateThemeConfigPage::defaults()
1338 {
1339     reset();
1340 }
1341 
1342 void 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
1354     if (KMessageBox::warningContinueCancel(this,
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")),
1358                                            KStandardGuiItem::cancel())
1359         != KMessageBox::Continue) {
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 
1384 bool 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)) {
1411             KMessageBox::information(this,
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 
1443 void 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 
1459 void KateThemeConfigPage::comboBoxIndexChanged(int currentIndex)
1460 {
1461     schemaChanged(schemaCombo->itemData(currentIndex).toString());
1462 }
1463 
1464 QString KateThemeConfigPage::name() const
1465 {
1466     return i18n("Color Themes");
1467 }
1468 
1469 QString KateThemeConfigPage::fullName() const
1470 {
1471     return i18n("Color Themes");
1472 }
1473 
1474 QIcon KateThemeConfigPage::icon() const
1475 {
1476     return QIcon::fromTheme(QStringLiteral("preferences-desktop-color"));
1477 }
1478 
1479 // END KateThemeConfigPage
1480 
1481 #include "moc_katethemeconfig.cpp"