File indexing completed on 2024-05-12 04:38:07
0001 /* 0002 SPDX-FileCopyrightText: 2009 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "colorcache.h" 0008 0009 #include "configurablecolors.h" 0010 #include "codehighlighting.h" 0011 0012 #include <KColorScheme> 0013 0014 #include "../../interfaces/icore.h" 0015 #include "../../interfaces/ilanguagecontroller.h" 0016 #include "../../interfaces/icompletionsettings.h" 0017 #include "../../interfaces/idocument.h" 0018 #include "../../interfaces/idocumentcontroller.h" 0019 #include "../interfaces/ilanguagesupport.h" 0020 #include "../duchain/duchain.h" 0021 #include "../duchain/duchainlock.h" 0022 #include <debug.h> 0023 #include "widgetcolorizer.h" 0024 0025 #include <KTextEditor/Document> 0026 #include <KTextEditor/View> 0027 #include <KTextEditor/ConfigInterface> 0028 #include <KSyntaxHighlighting/Definition> 0029 #include <KSyntaxHighlighting/Format> 0030 0031 #define ifDebug(x) 0032 0033 namespace KDevelop { 0034 ColorCache* ColorCache::m_self = nullptr; 0035 0036 CodeHighlightingType highlightingTypeFromName(const QString& name) 0037 { 0038 if (name == QLatin1String("Class")) { 0039 return CodeHighlightingType::Class; 0040 } else if (name == QLatin1String("Local Member Variable")) { 0041 return CodeHighlightingType::LocalClassMember; 0042 } else if (name == QLatin1String("Local Member Function")) { 0043 return CodeHighlightingType::LocalMemberFunction; 0044 } else if (name == QLatin1String("Inherited Member Variable")) { 0045 return CodeHighlightingType::InheritedClassMember; 0046 } else if (name == QLatin1String("Inherited Member Function")) { 0047 return CodeHighlightingType::InheritedMemberFunction; 0048 } else if (name == QLatin1String("Function")) { 0049 return CodeHighlightingType::Function; 0050 } else if (name == QLatin1String("Function Argument")) { 0051 return CodeHighlightingType::FunctionVariable; 0052 } else if (name == QLatin1String("Type Alias")) { 0053 return CodeHighlightingType::TypeAlias; 0054 } else if (name == QLatin1String("Forward Declaration")) { 0055 return CodeHighlightingType::ForwardDeclaration; 0056 } else if (name == QLatin1String("Namespace")) { 0057 return CodeHighlightingType::Namespace; 0058 } else if (name == QLatin1String("Local Variable")) { 0059 return CodeHighlightingType::LocalVariable; 0060 } else if (name == QLatin1String("Global Variable")) { 0061 return CodeHighlightingType::GlobalVariable; 0062 } else if (name == QLatin1String("Member Variable")) { 0063 return CodeHighlightingType::MemberVariable; 0064 } else if (name == QLatin1String("Namespace Variable")) { 0065 return CodeHighlightingType::NamespaceVariable; 0066 } else if (name == QLatin1String("Enumeration")) { 0067 return CodeHighlightingType::Enum; 0068 } else if (name == QLatin1String("Enumerator")) { 0069 return CodeHighlightingType::Enumerator; 0070 } else if (name == QLatin1String("Macro")) { 0071 return CodeHighlightingType::Macro; 0072 } else if (name == QLatin1String("Macro Function")) { 0073 return CodeHighlightingType::MacroFunctionLike; 0074 } 0075 return CodeHighlightingType::Error; 0076 } 0077 0078 ColorCache::ColorCache(QObject* parent) 0079 : QObject(parent) 0080 , m_defaultColors(new ConfigurableHighlightingColors) 0081 , m_validColorCount(0) 0082 , m_colorOffset(0) 0083 , m_localColorRatio(0) 0084 , m_globalColorRatio(0) 0085 , m_globalColorSource(ICompletionSettings::GlobalColorSource::AutoGenerated) 0086 , m_boldDeclarations(true) 0087 { 0088 Q_ASSERT(m_self == nullptr); 0089 0090 updateColorsFromScheme(); // default / fallback 0091 updateColorsFromSettings(); 0092 0093 connect(ICore::self()->languageController()->completionSettings(), &ICompletionSettings::settingsChanged, 0094 this, &ColorCache::updateColorsFromSettings, Qt::QueuedConnection); 0095 0096 connect(ICore::self()->documentController(), &IDocumentController::documentActivated, 0097 this, &ColorCache::slotDocumentActivated); 0098 0099 bool hadDoc = tryActiveDocument(); 0100 0101 updateInternal(); 0102 0103 m_self = this; 0104 0105 if (!hadDoc) { 0106 // try to update later on again 0107 QMetaObject::invokeMethod(this, "tryActiveDocument", Qt::QueuedConnection); 0108 } 0109 } 0110 0111 bool ColorCache::tryActiveDocument() 0112 { 0113 KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); 0114 if (view) { 0115 updateColorsFromView(view); 0116 return true; 0117 } 0118 return false; 0119 } 0120 0121 ColorCache::~ColorCache() 0122 { 0123 m_self = nullptr; 0124 delete m_defaultColors; 0125 m_defaultColors = nullptr; 0126 } 0127 0128 ColorCache* ColorCache::self() 0129 { 0130 if (!m_self) { 0131 m_self = new ColorCache; 0132 } 0133 return m_self; 0134 } 0135 0136 void ColorCache::generateColors() 0137 { 0138 // Primary colors taken from: http://colorbrewer2.org/?type=qualitative&scheme=Paired&n=12 0139 const QColor colors[] = { 0140 {"#b15928"}, {"#ff7f00"}, {"#b2df8a"}, {"#33a02c"}, {"#a6cee3"}, 0141 {"#1f78b4"}, {"#6a3d9a"}, {"#cab2d6"}, {"#e31a1c"}, {"#fb9a99"} 0142 }; 0143 const int colorCount = std::extent<decltype(colors)>::value; 0144 0145 // Supplementary colors generated by: http://tools.medialab.sciences-po.fr/iwanthue/ 0146 const QColor supplementaryColors[] = { 0147 {"#D33B67"}, {"#5EC764"}, {"#6CC82D"}, {"#995729"}, {"#FB4D84"}, 0148 {"#4B8828"}, {"#D847D0"}, {"#B56AC5"}, {"#E96F0C"}, {"#DC7161"}, 0149 {"#4D7279"}, {"#01AAF1"}, {"#D2A237"}, {"#F08CA5"}, {"#C83E93"}, 0150 {"#5D7DF7"}, {"#EFBB51"}, {"#108BBB"}, {"#5C84B8"}, {"#02F8BC"}, 0151 {"#A5A9F7"}, {"#F28E64"}, {"#A461E6"}, {"#6372D3"} 0152 }; 0153 const int supplementaryColorCount = std::extent<decltype(supplementaryColors)>::value; 0154 0155 m_colors.clear(); 0156 m_colors.reserve(colorCount + supplementaryColorCount); 0157 0158 for (const auto& color: colors) { 0159 m_colors.append(blendLocalColor(color)); 0160 } 0161 0162 m_primaryColorCount = m_colors.count(); 0163 0164 for (const auto& color: supplementaryColors) { 0165 m_colors.append(blendLocalColor(color)); 0166 } 0167 0168 m_validColorCount = m_colors.count(); 0169 } 0170 0171 void ColorCache::slotDocumentActivated() 0172 { 0173 KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); 0174 ifDebug(qCDebug(LANGUAGE) << "doc activated:" << doc; ) 0175 if (view) { 0176 updateColorsFromView(view); 0177 } 0178 } 0179 0180 void ColorCache::slotViewSettingsChanged() 0181 { 0182 auto* view = qobject_cast<KTextEditor::View*>(sender()); 0183 Q_ASSERT(view); 0184 0185 ifDebug(qCDebug(LANGUAGE) << "settings changed" << view; ) 0186 updateColorsFromView(view); 0187 } 0188 0189 void ColorCache::updateColorsFromView(KTextEditor::View* view) 0190 { 0191 if (!view) { 0192 // yeah, the HighlightInterface methods returning an Attribute 0193 // require a View... kill me for that mess 0194 return; 0195 } 0196 0197 QColor foreground(QColor::Invalid); 0198 QColor background(QColor::Invalid); 0199 0200 KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); 0201 foreground = style->foreground().color(); 0202 if (style->hasProperty(QTextFormat::BackgroundBrush)) { 0203 background = style->background().color(); 0204 } 0205 0206 // FIXME: this is in kateview 0207 // qCDebug(LANGUAGE) << "got foreground:" << foreground.name() << "old is:" << m_foregroundColor.name(); 0208 //NOTE: this slot is defined in KatePart > 4.4, see ApiDocs of the ConfigInterface 0209 0210 // the signal is not defined in ConfigInterface, but according to the docs it should be 0211 // can't use new signal slot syntax here, since ConfigInterface is not a QObject 0212 if (KTextEditor::View* view = m_view.data()) { 0213 Q_ASSERT(qobject_cast<KTextEditor::ConfigInterface*>(view)); 0214 // we only listen to a single view, i.e. the active one 0215 disconnect(view, &KTextEditor::View::configChanged, this, &ColorCache::slotViewSettingsChanged); 0216 } 0217 Q_ASSERT(qobject_cast<KTextEditor::ConfigInterface*>(view)); 0218 connect(view, &KTextEditor::View::configChanged, this, &ColorCache::slotViewSettingsChanged); 0219 m_view = view; 0220 0221 bool anyAttrChanged = false; 0222 if (!foreground.isValid()) { 0223 // fallback to colorscheme variant 0224 ifDebug(qCDebug(LANGUAGE) << "updating from scheme"; ) 0225 updateColorsFromScheme(); 0226 } else if (m_foregroundColor != foreground || m_backgroundColor != background) { 0227 m_foregroundColor = foreground; 0228 m_backgroundColor = background; 0229 m_defaultColors->reset(this, view); 0230 anyAttrChanged = true; 0231 } 0232 0233 anyAttrChanged |= updateColorsFromTheme(view->theme()); 0234 0235 if (anyAttrChanged) { 0236 ifDebug(qCDebug(LANGUAGE) << "updating from document"; ) 0237 update(); 0238 } 0239 } 0240 0241 bool ColorCache::updateColorsFromTheme(const KSyntaxHighlighting::Theme& theme) 0242 { 0243 if (m_globalColorSource != ICompletionSettings::GlobalColorSource::FromTheme) 0244 return false; 0245 0246 // from ktexteditor/src/syntax/kateextendedattribute.h 0247 static const int SelectedBackground = QTextFormat::UserProperty + 2; 0248 0249 const auto schemeDefinition = m_schemeRepo.definitionForName(QStringLiteral("Semantic Colors")); 0250 const auto formats = schemeDefinition.formats(); 0251 0252 bool anyAttrChanged = false; 0253 for (const auto& format : formats) { 0254 const auto type = highlightingTypeFromName(format.name()); 0255 if (type == CodeHighlightingType::Error) { 0256 continue; 0257 } 0258 const auto attr = m_defaultColors->attribute(type); 0259 Q_ASSERT(attr); 0260 0261 auto forwardProperty = [&](auto formatGetter, auto attrProperty, auto attrSetter) { 0262 auto formatProperty = (format.*formatGetter)(theme); 0263 if (attrProperty != formatProperty) { 0264 (attr.data()->*attrSetter)(formatProperty); 0265 anyAttrChanged = true; 0266 } 0267 }; 0268 0269 using namespace KSyntaxHighlighting; 0270 using namespace KTextEditor; 0271 forwardProperty(&Format::isBold, attr->fontBold(), &Attribute::setFontBold); 0272 forwardProperty(&Format::isItalic, attr->fontItalic(), &Attribute::setFontItalic); 0273 forwardProperty(&Format::isUnderline, attr->fontUnderline(), &Attribute::setFontUnderline); 0274 forwardProperty(&Format::isStrikeThrough, attr->fontStrikeOut(), &Attribute::setFontStrikeOut); 0275 forwardProperty(&Format::textColor, attr->foreground().color(), &Attribute::setForeground); 0276 forwardProperty(&Format::selectedTextColor, attr->selectedForeground().color(), 0277 &Attribute::setSelectedForeground); 0278 0279 if (format.hasBackgroundColor(theme)) { 0280 forwardProperty(&Format::backgroundColor, attr->background().color(), &Attribute::setBackground); 0281 } else if (type == CodeHighlightingType::HighlightUses) { 0282 auto background = QColor::fromRgb(theme.editorColor(KSyntaxHighlighting::Theme::SearchHighlight)); 0283 if (attr->background().color() != background) { 0284 attr->setBackground(background); 0285 anyAttrChanged = true; 0286 } 0287 } else if (attr->background() != QBrush()) { 0288 attr->setBackground(QBrush()); 0289 anyAttrChanged = true; 0290 } 0291 0292 // from KSyntaxHighlighting::Format::isDefaultTextStyle 0293 if (format.selectedBackgroundColor(theme).rgba() != theme.selectedBackgroundColor(KSyntaxHighlighting::Theme::Normal)) { 0294 forwardProperty(&Format::selectedBackgroundColor, attr->selectedBackground().color(), 0295 &Attribute::setSelectedBackground); 0296 } else if (attr->hasProperty(SelectedBackground)) { 0297 attr->clearProperty(SelectedBackground); 0298 anyAttrChanged = true; 0299 } 0300 } 0301 return anyAttrChanged; 0302 } 0303 0304 void ColorCache::updateColorsFromScheme() 0305 { 0306 KColorScheme scheme(QPalette::Normal, KColorScheme::View); 0307 0308 QColor foreground = scheme.foreground(KColorScheme::NormalText).color(); 0309 QColor background = scheme.background(KColorScheme::NormalBackground).color(); 0310 0311 if (foreground != m_foregroundColor || background != m_backgroundColor) { 0312 m_foregroundColor = foreground; 0313 m_backgroundColor = background; 0314 update(); 0315 } 0316 } 0317 0318 void ColorCache::updateColorsFromSettings() 0319 { 0320 auto settings = ICore::self()->languageController()->completionSettings(); 0321 0322 const auto globalColorSource = settings->globalColorSource(); 0323 const auto globalColorSourceChanged = globalColorSource != m_globalColorSource; 0324 m_globalColorSource = globalColorSource; 0325 0326 const auto globalRatio = settings->globalColorizationLevel(); 0327 const auto globalRatioChanged = globalRatio != m_globalColorRatio; 0328 m_globalColorRatio = globalRatio; 0329 0330 const auto localRatio = settings->localColorizationLevel(); 0331 const auto localRatioChanged = localRatio != m_localColorRatio; 0332 m_localColorRatio = localRatio; 0333 0334 const auto boldDeclartions = settings->boldDeclarations(); 0335 const auto boldDeclarationsChanged = boldDeclartions != m_boldDeclarations; 0336 m_boldDeclarations = boldDeclartions; 0337 0338 if (m_view && (globalRatioChanged || globalColorSourceChanged)) { 0339 updateDefaultColorsFromSource(); 0340 } 0341 0342 if (globalColorSourceChanged || globalRatioChanged || localRatioChanged || boldDeclarationsChanged) { 0343 update(); 0344 } 0345 } 0346 0347 void ColorCache::updateDefaultColorsFromSource() 0348 { 0349 switch (m_globalColorSource) { 0350 case ICompletionSettings::GlobalColorSource::AutoGenerated: 0351 m_defaultColors->reset(this, m_view.data()); 0352 break; 0353 case ICompletionSettings::GlobalColorSource::FromTheme: 0354 updateColorsFromTheme(m_view->theme()); 0355 break; 0356 } 0357 } 0358 0359 void ColorCache::update() 0360 { 0361 if (!m_self) { 0362 ifDebug(qCDebug(LANGUAGE) << "not updating - still initializating"; ) 0363 // don't update on startup, updateInternal is called directly there 0364 return; 0365 } 0366 0367 QMetaObject::invokeMethod(this, "updateInternal", Qt::QueuedConnection); 0368 } 0369 0370 void ColorCache::updateInternal() 0371 { 0372 ifDebug(qCDebug(LANGUAGE) << "update internal" << m_self; ) 0373 generateColors(); 0374 0375 if (!m_self) { 0376 // don't do anything else fancy on startup 0377 return; 0378 } 0379 0380 emit colorsGotChanged(); 0381 0382 // rehighlight open documents 0383 if (!ICore::self() || ICore::self()->shuttingDown()) { 0384 return; 0385 } 0386 const auto documents = ICore::self()->documentController()->openDocuments(); 0387 for (IDocument* doc : documents) { 0388 const auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); 0389 for (const auto lang : languages) { 0390 ReferencedTopDUContext top; 0391 { 0392 DUChainReadLocker lock; 0393 top = lang->standardContext(doc->url()); 0394 } 0395 0396 if (top) { 0397 if (ICodeHighlighting* highlighting = lang->codeHighlighting()) { 0398 highlighting->highlightDUChain(top); 0399 } 0400 } 0401 } 0402 } 0403 } 0404 0405 QColor ColorCache::blend(QColor color, uchar ratio) const 0406 { 0407 Q_ASSERT(m_backgroundColor.isValid()); 0408 Q_ASSERT(m_foregroundColor.isValid()); 0409 return WidgetColorizer::blendForeground(color, float( ratio ) / float( 0xff ), m_foregroundColor, 0410 m_backgroundColor); 0411 } 0412 0413 QColor ColorCache::blendBackground(QColor color, uchar ratio) const 0414 { 0415 return WidgetColorizer::blendBackground(color, float( ratio ) / float( 0xff ), m_foregroundColor, 0416 m_backgroundColor); 0417 } 0418 0419 QColor ColorCache::blendGlobalColor(QColor color) const 0420 { 0421 return blend(color, m_globalColorRatio); 0422 } 0423 0424 QColor ColorCache::blendLocalColor(QColor color) const 0425 { 0426 return blend(color, m_localColorRatio); 0427 } 0428 0429 ConfigurableHighlightingColors* ColorCache::defaultColors() const 0430 { 0431 return m_defaultColors; 0432 } 0433 0434 QColor ColorCache::generatedColor(uint num) const 0435 { 0436 return num > ( uint )m_colors.size() ? foregroundColor() : m_colors[num]; 0437 } 0438 0439 uint ColorCache::validColorCount() const 0440 { 0441 return m_validColorCount; 0442 } 0443 0444 uint ColorCache::primaryColorCount() const 0445 { 0446 return m_primaryColorCount; 0447 } 0448 0449 QColor ColorCache::foregroundColor() const 0450 { 0451 return m_foregroundColor; 0452 } 0453 } 0454 0455 #include "moc_colorcache.cpp"