File indexing completed on 2025-03-16 08:11:24
0001 /* 0002 This source file is part of Konsole, a terminal emulator. 0003 0004 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 0008 This program is distributed in the hope that it will be useful, 0009 but WITHOUT ANY WARRANTY; without even the implied warranty of 0010 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0011 GNU General Public License for more details. 0012 0013 You should have received a copy of the GNU General Public License 0014 along with this program; if not, write to the Free Software 0015 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0016 02110-1301 USA. 0017 */ 0018 0019 // Own 0020 #include "ColorScheme.h" 0021 #include "tools.h" 0022 0023 // Qt 0024 #include <QBrush> 0025 #include <QDir> 0026 #include <QFile> 0027 #include <QFileInfo> 0028 #include <QRandomGenerator> 0029 #include <QRegularExpression> 0030 #include <QSettings> 0031 #include <QtDebug> 0032 0033 // KDE 0034 // #include <KColorScheme> 0035 // #include <KConfig> 0036 // #include <KLocale> 0037 // #include <KDebug> 0038 // #include <KConfigGroup> 0039 // #include <KStandardDirs> 0040 0041 using namespace Konsole; 0042 0043 constexpr std::array<ColorEntry, TABLE_COLORS> DEFAULT_TABLE = 0044 // The following are almost IBM standard color codes, with some slight 0045 // gamma correction for the dim colors to compensate for bright X screens. 0046 // It contains the 8 ansiterm/xterm colors in 2 intensities. 0047 {ColorEntry(QColor(0x00, 0x00, 0x00), false), 0048 ColorEntry(QColor(0xFF, 0xFF, 0xFF), true), // Dfore, Dback 0049 ColorEntry(QColor(0x00, 0x00, 0x00), false), 0050 ColorEntry(QColor(0xB2, 0x18, 0x18), false), // Black, Red 0051 ColorEntry(QColor(0x18, 0xB2, 0x18), false), 0052 ColorEntry(QColor(0xB2, 0x68, 0x18), false), // Green, Yellow 0053 ColorEntry(QColor(0x18, 0x18, 0xB2), false), 0054 ColorEntry(QColor(0xB2, 0x18, 0xB2), false), // Blue, Magenta 0055 ColorEntry(QColor(0x18, 0xB2, 0xB2), false), 0056 ColorEntry(QColor(0xB2, 0xB2, 0xB2), false), // Cyan, White 0057 // intensive 0058 ColorEntry(QColor(0x00, 0x00, 0x00), false), 0059 ColorEntry(QColor(0xFF, 0xFF, 0xFF), true), 0060 ColorEntry(QColor(0x68, 0x68, 0x68), false), 0061 ColorEntry(QColor(0xFF, 0x54, 0x54), false), 0062 ColorEntry(QColor(0x54, 0xFF, 0x54), false), 0063 ColorEntry(QColor(0xFF, 0xFF, 0x54), false), 0064 ColorEntry(QColor(0x54, 0x54, 0xFF), false), 0065 ColorEntry(QColor(0xFF, 0x54, 0xFF), false), 0066 ColorEntry(QColor(0x54, 0xFF, 0xFF), false), 0067 ColorEntry(QColor(0xFF, 0xFF, 0xFF), false)}; 0068 0069 std::array<ColorEntry, TABLE_COLORS> Konsole::defaultColorTable() 0070 { 0071 return DEFAULT_TABLE; 0072 } 0073 0074 constexpr std::array<QStringView, TABLE_COLORS> COLOR_NAMES = {u"Foreground", 0075 u"Background", 0076 u"Color0", 0077 u"Color1", 0078 u"Color2", 0079 u"Color3", 0080 u"Color4", 0081 u"Color5", 0082 u"Color6", 0083 u"Color7", 0084 u"ForegroundIntense", 0085 u"BackgroundIntense", 0086 u"Color0Intense", 0087 u"Color1Intense", 0088 u"Color2Intense", 0089 u"Color3Intense", 0090 u"Color4Intense", 0091 u"Color5Intense", 0092 u"Color6Intense", 0093 u"Color7Intense"}; 0094 0095 std::array<const char *, TABLE_COLORS> TRANSLATED_COLOR_NAMES = {QT_TR_NOOP("Foreground"), 0096 QT_TR_NOOP("Background"), 0097 QT_TR_NOOP("Color 1"), 0098 QT_TR_NOOP("Color 2"), 0099 QT_TR_NOOP("Color 3"), 0100 QT_TR_NOOP("Color 4"), 0101 QT_TR_NOOP("Color 5"), 0102 QT_TR_NOOP("Color 6"), 0103 QT_TR_NOOP("Color 7"), 0104 QT_TR_NOOP("Color 8"), 0105 QT_TR_NOOP("Foreground (Intense)"), 0106 QT_TR_NOOP("Background (Intense)"), 0107 QT_TR_NOOP("Color 1 (Intense)"), 0108 QT_TR_NOOP("Color 2 (Intense)"), 0109 QT_TR_NOOP("Color 3 (Intense)"), 0110 QT_TR_NOOP("Color 4 (Intense)"), 0111 QT_TR_NOOP("Color 5 (Intense)"), 0112 QT_TR_NOOP("Color 6 (Intense)"), 0113 QT_TR_NOOP("Color 7 (Intense)"), 0114 QT_TR_NOOP("Color 8 (Intense)")}; 0115 0116 ColorScheme::ColorScheme() 0117 { 0118 _table = {}; 0119 _randomTable = {}; 0120 _opacity = 1.0; 0121 } 0122 0123 ColorScheme::ColorScheme(const ColorScheme &other) 0124 : _opacity(other._opacity) 0125 { 0126 setName(other.name()); 0127 setDescription(other.description()); 0128 0129 _table = other._table; 0130 _randomTable = other._randomTable; 0131 } 0132 ColorScheme::~ColorScheme() 0133 { 0134 } 0135 0136 void ColorScheme::setDescription(const QString &description) 0137 { 0138 _description = description; 0139 } 0140 QString ColorScheme::description() const 0141 { 0142 return _description; 0143 } 0144 0145 void ColorScheme::setName(const QString &name) 0146 { 0147 _name = name; 0148 } 0149 QString ColorScheme::name() const 0150 { 0151 return _name; 0152 } 0153 0154 void ColorScheme::setColorTableEntry(int index, const ColorEntry &entry) 0155 { 0156 Q_ASSERT(index >= 0 && index < TABLE_COLORS); 0157 0158 if (!_table) { 0159 _table = std::vector<ColorEntry>(DEFAULT_TABLE.begin(), DEFAULT_TABLE.end()); 0160 } 0161 0162 // we always have a value set at this point, see above 0163 _table.value()[index] = entry; 0164 } 0165 ColorEntry ColorScheme::colorEntry(int index, uint randomSeed) const 0166 { 0167 Q_ASSERT(index >= 0 && index < TABLE_COLORS); 0168 0169 ColorEntry entry = colorTable()[index]; 0170 0171 if (randomSeed != 0 && _randomTable.has_value() && !_randomTable->data()[index].isNull()) { 0172 const RandomizationRange &range = _randomTable->data()[index]; 0173 0174 int hueDifference = range.hue ? QRandomGenerator::system()->bounded(range.hue) - range.hue / 2 : 0; 0175 int saturationDifference = range.saturation ? (QRandomGenerator::system()->bounded(range.saturation)) - range.saturation / 2 : 0; 0176 int valueDifference = range.value ? (QRandomGenerator::system()->bounded(range.value)) - range.value / 2 : 0; 0177 0178 QColor &color = entry.color; 0179 0180 int newHue = qAbs((color.hue() + hueDifference) % MAX_HUE); 0181 int newValue = qMin(qAbs(color.value() + valueDifference), 255); 0182 int newSaturation = qMin(qAbs(color.saturation() + saturationDifference), 255); 0183 0184 color.setHsv(newHue, newSaturation, newValue); 0185 } 0186 0187 return entry; 0188 } 0189 0190 std::array<ColorEntry, TABLE_COLORS> ColorScheme::getColorTable(uint randomSeed) const 0191 { 0192 std::array<ColorEntry, TABLE_COLORS> table; 0193 for (int i = 0; i < TABLE_COLORS; i++) 0194 table[i] = colorEntry(i, randomSeed); 0195 0196 return table; 0197 } 0198 0199 bool ColorScheme::randomizedBackgroundColor() const 0200 { 0201 return _randomTable.has_value() ? !_randomTable->data()[1].isNull() : false; 0202 } 0203 0204 void ColorScheme::setRandomizedBackgroundColor(bool randomize) 0205 { 0206 // the hue of the background colour is allowed to be randomly 0207 // adjusted as much as possible. 0208 // 0209 // the value and saturation are left alone to maintain read-ability 0210 if (randomize) { 0211 setRandomizationRange(1 /* background color index */, MAX_HUE, 255, 0); 0212 } else { 0213 if (_randomTable) 0214 setRandomizationRange(1 /* background color index */, 0, 0, 0); 0215 } 0216 } 0217 0218 void ColorScheme::setRandomizationRange(int index, quint16 hue, quint8 saturation, quint8 value) 0219 { 0220 Q_ASSERT(hue <= MAX_HUE); 0221 Q_ASSERT(index >= 0 && index < TABLE_COLORS); 0222 0223 if (!_randomTable) 0224 _randomTable = std::vector<RandomizationRange>(TABLE_COLORS); 0225 0226 _randomTable->data()[index].hue = hue; 0227 _randomTable->data()[index].value = value; 0228 _randomTable->data()[index].saturation = saturation; 0229 } 0230 0231 const std::span<const ColorEntry> ColorScheme::colorTable() const 0232 { 0233 if (_table.has_value()) 0234 return *_table; 0235 else 0236 return std::span(DEFAULT_TABLE); 0237 } 0238 0239 QColor ColorScheme::foregroundColor() const 0240 { 0241 return colorTable()[0].color; 0242 } 0243 0244 QColor ColorScheme::backgroundColor() const 0245 { 0246 return colorTable()[1].color; 0247 } 0248 0249 bool ColorScheme::hasDarkBackground() const 0250 { 0251 // value can range from 0 - 255, with larger values indicating higher brightness. 0252 // so 127 is in the middle, anything less is deemed 'dark' 0253 return backgroundColor().value() < 127; 0254 } 0255 void ColorScheme::setOpacity(qreal opacity) 0256 { 0257 _opacity = opacity; 0258 } 0259 qreal ColorScheme::opacity() const 0260 { 0261 return _opacity; 0262 } 0263 0264 void ColorScheme::read(const QString &fileName) 0265 { 0266 QSettings s(fileName, QSettings::IniFormat); 0267 s.beginGroup(QLatin1String("General")); 0268 0269 _description = s.value(QLatin1String("Description"), QObject::tr("Un-named Color Scheme")).toString(); 0270 _opacity = s.value(QLatin1String("Opacity"), qreal(1.0)).toDouble(); 0271 s.endGroup(); 0272 0273 for (int i = 0; i < TABLE_COLORS; i++) { 0274 readColorEntry(&s, i); 0275 } 0276 } 0277 0278 QString ColorScheme::colorNameForIndex(int index) 0279 { 0280 Q_ASSERT(index >= 0 && index < TABLE_COLORS); 0281 0282 return COLOR_NAMES.at(index).toString(); 0283 } 0284 QString ColorScheme::translatedColorNameForIndex(int index) 0285 { 0286 Q_ASSERT(index >= 0 && index < TABLE_COLORS); 0287 0288 return QObject::tr(TRANSLATED_COLOR_NAMES.at(index)); 0289 } 0290 0291 void ColorScheme::readColorEntry(QSettings *s, int index) 0292 { 0293 QString colorName = colorNameForIndex(index); 0294 0295 s->beginGroup(colorName); 0296 0297 ColorEntry entry; 0298 0299 QVariant colorValue = s->value(QLatin1String("Color")); 0300 QString colorStr; 0301 int r, g, b; 0302 bool ok = false; 0303 // XXX: Undocumented(?) QSettings behavior: values with commas are parsed 0304 // as QStringList and others QString 0305 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0306 if (colorValue.type() == QVariant::StringList) 0307 #else 0308 if (colorValue.metaType().id() == QMetaType::QStringList) 0309 #endif 0310 { 0311 QStringList rgbList = colorValue.toStringList(); 0312 colorStr = rgbList.join(QLatin1Char(',')); 0313 if (rgbList.size() == 3) { 0314 bool parse_ok; 0315 0316 ok = true; 0317 r = rgbList[0].toInt(&parse_ok); 0318 ok = ok && parse_ok && (r >= 0 && r <= 0xff); 0319 g = rgbList[1].toInt(&parse_ok); 0320 ok = ok && parse_ok && (g >= 0 && g <= 0xff); 0321 b = rgbList[2].toInt(&parse_ok); 0322 ok = ok && parse_ok && (b >= 0 && b <= 0xff); 0323 } 0324 } else { 0325 colorStr = colorValue.toString(); 0326 QRegularExpression hexColorPattern(QLatin1String("^#[0-9a-f]{6}$"), QRegularExpression::CaseInsensitiveOption); 0327 if (hexColorPattern.match(colorStr).hasMatch()) { 0328 // Parsing is always ok as already matched by the regexp 0329 r = QStringView(colorStr).mid(1, 2).toInt(nullptr, 16); 0330 g = QStringView(colorStr).mid(3, 2).toInt(nullptr, 16); 0331 b = QStringView(colorStr).mid(5, 2).toInt(nullptr, 16); 0332 ok = true; 0333 } 0334 } 0335 if (!ok) { 0336 qWarning().nospace() << "Invalid color value " << colorStr << " for " << colorName << ". Fallback to black."; 0337 r = g = b = 0; 0338 } 0339 entry.color = QColor(r, g, b); 0340 0341 entry.transparent = s->value(QLatin1String("Transparent"), false).toBool(); 0342 0343 // Deprecated key from KDE 4.0 which set 'Bold' to true to force 0344 // a color to be bold or false to use the current format 0345 // 0346 // TODO - Add a new tri-state key which allows for bold, normal or 0347 // current format 0348 if (s->contains(QLatin1String("Bold"))) 0349 entry.fontWeight = s->value(QLatin1String("Bold"), false).toBool() ? ColorEntry::Bold : ColorEntry::UseCurrentFormat; 0350 0351 quint16 hue = s->value(QLatin1String("MaxRandomHue"), 0).toInt(); 0352 quint8 value = s->value(QLatin1String("MaxRandomValue"), 0).toInt(); 0353 quint8 saturation = s->value(QLatin1String("MaxRandomSaturation"), 0).toInt(); 0354 0355 setColorTableEntry(index, entry); 0356 0357 if (hue != 0 || value != 0 || saturation != 0) 0358 setRandomizationRange(index, hue, saturation, value); 0359 0360 s->endGroup(); 0361 } 0362 0363 // 0364 // Work In Progress - A color scheme for use on KDE setups for users 0365 // with visual disabilities which means that they may have trouble 0366 // reading text with the supplied color schemes. 0367 // 0368 // This color scheme uses only the 'safe' colors defined by the 0369 // KColorScheme class. 0370 // 0371 // A complication this introduces is that each color provided by 0372 // KColorScheme is defined as a 'background' or 'foreground' color. 0373 // Only foreground colors are allowed to be used to render text and 0374 // only background colors are allowed to be used for backgrounds. 0375 // 0376 // The ColorEntry and TerminalDisplay classes do not currently 0377 // support this restriction. 0378 // 0379 // Requirements: 0380 // - A color scheme which uses only colors from the KColorScheme class 0381 // - Ability to restrict which colors the TerminalDisplay widget 0382 // uses as foreground and background color 0383 // - Make use of KGlobalSettings::allowDefaultBackgroundImages() as 0384 // a hint to determine whether this accessible color scheme should 0385 // be used by default. 0386 // 0387 // 0388 // -- Robert Knight <robertknight@gmail.com> 21/07/2007 0389 // 0390 AccessibleColorScheme::AccessibleColorScheme() 0391 : ColorScheme() 0392 { 0393 #if 0 0394 // It's not finished in konsole and it breaks Qt4 compilation as well 0395 // basic attributes 0396 setName("accessible"); 0397 setDescription(QObject::tr("Accessible Color Scheme")); 0398 0399 // setup colors 0400 const int ColorRoleCount = 8; 0401 0402 const KColorScheme colorScheme(QPalette::Active); 0403 0404 QBrush colors[ColorRoleCount] = 0405 { 0406 colorScheme.foreground( colorScheme.NormalText ), 0407 colorScheme.background( colorScheme.NormalBackground ), 0408 0409 colorScheme.foreground( colorScheme.InactiveText ), 0410 colorScheme.foreground( colorScheme.ActiveText ), 0411 colorScheme.foreground( colorScheme.LinkText ), 0412 colorScheme.foreground( colorScheme.VisitedText ), 0413 colorScheme.foreground( colorScheme.NegativeText ), 0414 colorScheme.foreground( colorScheme.NeutralText ) 0415 }; 0416 0417 for ( int i = 0 ; i < TABLE_COLORS ; i++ ) 0418 { 0419 ColorEntry entry; 0420 entry.color = colors[ i % ColorRoleCount ].color(); 0421 0422 setColorTableEntry( i , entry ); 0423 } 0424 #endif 0425 } 0426 0427 ColorSchemeManager::ColorSchemeManager() 0428 : _haveLoadedAll(false) 0429 { 0430 } 0431 0432 ColorSchemeManager::~ColorSchemeManager() = default; 0433 0434 void ColorSchemeManager::loadAllColorSchemes() 0435 { 0436 // qDebug() << "loadAllColorSchemes"; 0437 int failed = 0; 0438 0439 QList<QString> nativeColorSchemes = listColorSchemes(); 0440 QListIterator<QString> nativeIter(nativeColorSchemes); 0441 while (nativeIter.hasNext()) { 0442 if (!loadColorScheme(nativeIter.next())) 0443 failed++; 0444 } 0445 0446 /*if ( failed > 0 ) 0447 qDebug() << "failed to load " << failed << " color schemes.";*/ 0448 0449 _haveLoadedAll = true; 0450 } 0451 QList<ColorScheme *> ColorSchemeManager::allColorSchemes() 0452 { 0453 if (!_haveLoadedAll) { 0454 loadAllColorSchemes(); 0455 } 0456 0457 QList<ColorScheme *> schemes; 0458 for (const auto &[k, scheme] : _colorSchemes) { 0459 schemes.push_back(scheme.get()); 0460 } 0461 return schemes; 0462 } 0463 0464 #if 0 0465 void ColorSchemeManager::addColorScheme(ColorScheme* scheme) 0466 { 0467 _colorSchemes.insert(scheme->name(),scheme); 0468 0469 // save changes to disk 0470 QString path = KGlobal::dirs()->saveLocation("data","konsole/") + scheme->name() + ".colorscheme"; 0471 KConfig config(path , KConfig::NoGlobals); 0472 0473 scheme->write(config); 0474 } 0475 #endif 0476 0477 bool ColorSchemeManager::loadCustomColorScheme(const QString &path) 0478 { 0479 if (path.endsWith(QLatin1String(".colorscheme"))) 0480 return loadColorScheme(path); 0481 0482 return false; 0483 } 0484 0485 void ColorSchemeManager::addCustomColorSchemeDir(const QString &custom_dir) 0486 { 0487 add_custom_color_scheme_dir(custom_dir); 0488 } 0489 0490 bool ColorSchemeManager::loadColorScheme(const QString &filePath) 0491 { 0492 if (!filePath.endsWith(QLatin1String(".colorscheme")) || !QFile::exists(filePath)) 0493 return false; 0494 0495 QFileInfo info(filePath); 0496 0497 const QString &schemeName = info.baseName(); 0498 0499 auto scheme = std::make_unique<ColorScheme>(); 0500 scheme->setName(schemeName); 0501 scheme->read(filePath); 0502 0503 if (scheme->name().isEmpty()) { 0504 // qDebug() << "Color scheme in" << filePath << "does not have a valid name and was not loaded."; 0505 scheme.reset(); 0506 return false; 0507 } 0508 0509 if (!_colorSchemes.contains(schemeName)) { 0510 _colorSchemes.insert_or_assign(schemeName, std::move(scheme)); 0511 } else { 0512 /*qDebug() << "color scheme with name" << schemeName << "has already been" << 0513 "found, ignoring.";*/ 0514 0515 scheme.reset(); 0516 } 0517 0518 return true; 0519 } 0520 QList<QString> ColorSchemeManager::listColorSchemes() 0521 { 0522 QList<QString> ret; 0523 for (const QString &scheme_dir : colorSchemesDirs()) { 0524 const QString dname(scheme_dir); 0525 QDir dir(dname); 0526 QStringList filters; 0527 filters << QLatin1String("*.colorscheme"); 0528 dir.setNameFilters(filters); 0529 const QStringList list = dir.entryList(filters); 0530 for (const QString &i : list) 0531 ret << dname + QLatin1Char('/') + i; 0532 } 0533 return ret; 0534 // return KGlobal::dirs()->findAllResources("data", 0535 // "konsole/*.colorscheme", 0536 // KStandardDirs::NoDuplicates); 0537 } 0538 const ColorScheme ColorSchemeManager::_defaultColorScheme; 0539 const ColorScheme *ColorSchemeManager::defaultColorScheme() const 0540 { 0541 return &_defaultColorScheme; 0542 } 0543 bool ColorSchemeManager::deleteColorScheme(const QString &name) 0544 { 0545 Q_ASSERT(_colorSchemes.contains(name)); 0546 0547 // lookup the path and delete 0548 QString path = findColorSchemePath(name); 0549 if (QFile::remove(path)) { 0550 _colorSchemes.erase(name); 0551 return true; 0552 } else { 0553 // qDebug() << "Failed to remove color scheme -" << path; 0554 return false; 0555 } 0556 } 0557 QString ColorSchemeManager::findColorSchemePath(const QString &name) const 0558 { 0559 // QString path = KStandardDirs::locate("data","konsole/"+name+".colorscheme"); 0560 const QStringList dirs = colorSchemesDirs(); 0561 if (dirs.isEmpty()) 0562 return QString(); 0563 0564 const QString dir = dirs.first(); 0565 QString path(dir + QLatin1Char('/') + name + QLatin1String(".colorscheme")); 0566 if (!path.isEmpty()) 0567 return path; 0568 0569 // path = KStandardDirs::locate("data","konsole/"+name+".schema"); 0570 path = dir + QLatin1Char('/') + name + QLatin1String(".schema"); 0571 0572 return path; 0573 } 0574 const ColorScheme *ColorSchemeManager::findColorScheme(const QString &name) 0575 { 0576 if (name.isEmpty()) 0577 return defaultColorScheme(); 0578 0579 if (_colorSchemes.contains(name)) 0580 return _colorSchemes[name].get(); 0581 else { 0582 // look for this color scheme 0583 QString path = findColorSchemePath(name); 0584 if (!path.isEmpty() && loadColorScheme(path)) { 0585 return findColorScheme(name); 0586 } 0587 0588 // qDebug() << "Could not find color scheme - " << name; 0589 0590 return nullptr; 0591 } 0592 } 0593 Q_GLOBAL_STATIC(ColorSchemeManager, theColorSchemeManager) 0594 ColorSchemeManager *ColorSchemeManager::instance() 0595 { 0596 return theColorSchemeManager; 0597 }