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 "<b>Configure Highlighting</b>" " 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><SPACE></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"