File indexing completed on 2024-05-05 07:59:50
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 "<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 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><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(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"