Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / konsole / src / ColorScheme.cpp
blob549273ec7c649ac6b508aa8581aff5c9b1f116c6
1 /*
2 This source file is part of Konsole, a terminal emulator.
4 Copyright 2007-2008 by Robert Knight <robertknight@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
22 // Own
23 #include "ColorScheme.h"
25 // Qt
26 #include <QtGui/QBrush>
27 #include <QtCore/QFile>
28 #include <QtCore/QFileInfo>
30 // KDE
31 #include <KColorScheme>
32 #include <KConfig>
33 #include <KLocale>
34 #include <KDebug>
35 #include <KConfigGroup>
36 #include <KStandardDirs>
38 using namespace Konsole;
40 const ColorEntry ColorScheme::defaultTable[TABLE_COLORS] =
41 // The following are almost IBM standard color codes, with some slight
42 // gamma correction for the dim colors to compensate for bright X screens.
43 // It contains the 8 ansiterm/xterm colors in 2 intensities.
45 ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry(
46 QColor(0xFF,0xFF,0xFF), 1), // Dfore, Dback
47 ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry(
48 QColor(0xB2,0x18,0x18), 0), // Black, Red
49 ColorEntry( QColor(0x18,0xB2,0x18), 0), ColorEntry(
50 QColor(0xB2,0x68,0x18), 0), // Green, Yellow
51 ColorEntry( QColor(0x18,0x18,0xB2), 0), ColorEntry(
52 QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta
53 ColorEntry( QColor(0x18,0xB2,0xB2), 0), ColorEntry(
54 QColor(0xB2,0xB2,0xB2), 0), // Cyan, White
55 // intensive
56 ColorEntry( QColor(0x00,0x00,0x00), 0), ColorEntry(
57 QColor(0xFF,0xFF,0xFF), 1),
58 ColorEntry( QColor(0x68,0x68,0x68), 0), ColorEntry(
59 QColor(0xFF,0x54,0x54), 0),
60 ColorEntry( QColor(0x54,0xFF,0x54), 0), ColorEntry(
61 QColor(0xFF,0xFF,0x54), 0),
62 ColorEntry( QColor(0x54,0x54,0xFF), 0), ColorEntry(
63 QColor(0xFF,0x54,0xFF), 0),
64 ColorEntry( QColor(0x54,0xFF,0xFF), 0), ColorEntry(
65 QColor(0xFF,0xFF,0xFF), 0)
68 const char* ColorScheme::colorNames[TABLE_COLORS] =
70 "Foreground",
71 "Background",
72 "Color0",
73 "Color1",
74 "Color2",
75 "Color3",
76 "Color4",
77 "Color5",
78 "Color6",
79 "Color7",
80 "ForegroundIntense",
81 "BackgroundIntense",
82 "Color0Intense",
83 "Color1Intense",
84 "Color2Intense",
85 "Color3Intense",
86 "Color4Intense",
87 "Color5Intense",
88 "Color6Intense",
89 "Color7Intense"
91 const char* ColorScheme::translatedColorNames[TABLE_COLORS] =
93 I18N_NOOP("Foreground"),
94 I18N_NOOP("Background"),
95 I18N_NOOP("Color 1"),
96 I18N_NOOP("Color 2"),
97 I18N_NOOP("Color 3"),
98 I18N_NOOP("Color 4"),
99 I18N_NOOP("Color 5"),
100 I18N_NOOP("Color 6"),
101 I18N_NOOP("Color 7"),
102 I18N_NOOP("Color 8"),
103 I18N_NOOP("Foreground (Intense)"),
104 I18N_NOOP("Background (Intense)"),
105 I18N_NOOP("Color 1 (Intense)"),
106 I18N_NOOP("Color 2 (Intense)"),
107 I18N_NOOP("Color 3 (Intense)"),
108 I18N_NOOP("Color 4 (Intense)"),
109 I18N_NOOP("Color 5 (Intense)"),
110 I18N_NOOP("Color 6 (Intense)"),
111 I18N_NOOP("Color 7 (Intense)"),
112 I18N_NOOP("Color 8 (Intense)")
115 ColorScheme::ColorScheme()
117 _table = 0;
118 _randomTable = 0;
119 _opacity = 1.0;
121 ColorScheme::ColorScheme(const ColorScheme& other)
122 : _opacity(other._opacity)
123 ,_table(0)
124 ,_randomTable(0)
126 setName(other.name());
127 setDescription(other.description());
129 if ( other._table != 0 )
131 for ( int i = 0 ; i < TABLE_COLORS ; i++ )
132 setColorTableEntry(i,other._table[i]);
135 if ( other._randomTable != 0 )
137 for ( int i = 0 ; i < TABLE_COLORS ; i++ )
139 const RandomizationRange& range = other._randomTable[i];
140 setRandomizationRange(i,range.hue,range.saturation,range.value);
144 ColorScheme::~ColorScheme()
146 delete[] _table;
147 delete[] _randomTable;
150 void ColorScheme::setDescription(const QString& description) { _description = description; }
151 QString ColorScheme::description() const { return _description; }
153 void ColorScheme::setName(const QString& name) { _name = name; }
154 QString ColorScheme::name() const { return _name; }
156 void ColorScheme::setColorTableEntry(int index , const ColorEntry& entry)
158 Q_ASSERT( index >= 0 && index < TABLE_COLORS );
160 if ( !_table )
162 _table = new ColorEntry[TABLE_COLORS];
164 for (int i=0;i<TABLE_COLORS;i++)
165 _table[i] = defaultTable[i];
168 _table[index] = entry;
170 ColorEntry ColorScheme::colorEntry(int index , uint randomSeed) const
172 Q_ASSERT( index >= 0 && index < TABLE_COLORS );
174 if ( randomSeed != 0 )
175 qsrand(randomSeed);
177 ColorEntry entry = colorTable()[index];
179 if ( randomSeed != 0 &&
180 _randomTable != 0 &&
181 !_randomTable[index].isNull() )
183 const RandomizationRange& range = _randomTable[index];
186 int hueDifference = range.hue ? (qrand() % range.hue) - range.hue/2 : 0;
187 int saturationDifference = range.saturation ? (qrand() % range.saturation) - range.saturation/2 : 0;
188 int valueDifference = range.value ? (qrand() % range.value) - range.value/2 : 0;
190 QColor& color = entry.color;
192 int newHue = qAbs( (color.hue() + hueDifference) % MAX_HUE );
193 int newValue = qMin( qAbs(color.value() + valueDifference) , 255 );
194 int newSaturation = qMin( qAbs(color.saturation() + saturationDifference) , 255 );
196 color.setHsv(newHue,newSaturation,newValue);
199 return entry;
201 void ColorScheme::getColorTable(ColorEntry* table , uint randomSeed) const
203 for ( int i = 0 ; i < TABLE_COLORS ; i++ )
204 table[i] = colorEntry(i,randomSeed);
206 bool ColorScheme::randomizedBackgroundColor() const
208 return _randomTable == 0 ? false : !_randomTable[1].isNull();
210 void ColorScheme::setRandomizedBackgroundColor(bool randomize)
212 // the hue of the background colour is allowed to be randomly
213 // adjusted as much as possible.
215 // the value and saturation are left alone to maintain read-ability
216 if ( randomize )
218 setRandomizationRange( 1 /* background color index */ , MAX_HUE , 255 , 0 );
220 else
222 if ( _randomTable )
223 setRandomizationRange( 1 /* background color index */ , 0 , 0 , 0 );
227 void ColorScheme::setRandomizationRange( int index , quint16 hue , quint8 saturation ,
228 quint8 value )
230 Q_ASSERT( hue <= MAX_HUE );
231 Q_ASSERT( index >= 0 && index < TABLE_COLORS );
233 if ( _randomTable == 0 )
234 _randomTable = new RandomizationRange[TABLE_COLORS];
236 _randomTable[index].hue = hue;
237 _randomTable[index].value = value;
238 _randomTable[index].saturation = saturation;
241 const ColorEntry* ColorScheme::colorTable() const
243 if ( _table )
244 return _table;
245 else
246 return defaultTable;
248 QColor ColorScheme::foregroundColor() const
250 return colorTable()[0].color;
252 QColor ColorScheme::backgroundColor() const
254 return colorTable()[1].color;
256 bool ColorScheme::hasDarkBackground() const
258 // value can range from 0 - 255, with larger values indicating higher brightness.
259 // so 127 is in the middle, anything less is deemed 'dark'
260 return backgroundColor().value() < 127;
262 void ColorScheme::setOpacity(qreal opacity) { _opacity = opacity; }
263 qreal ColorScheme::opacity() const { return _opacity; }
265 void ColorScheme::read(KConfig& config)
267 KConfigGroup configGroup = config.group("General");
269 QString description = configGroup.readEntry("Description", I18N_NOOP("Un-named Color Scheme"));
271 _description = i18n(description.toUtf8());
272 _opacity = configGroup.readEntry("Opacity",qreal(1.0));
274 for (int i=0 ; i < TABLE_COLORS ; i++)
276 readColorEntry(config,i);
279 void ColorScheme::write(KConfig& config) const
281 KConfigGroup configGroup = config.group("General");
283 configGroup.writeEntry("Description",_description);
284 configGroup.writeEntry("Opacity",_opacity);
286 for (int i=0 ; i < TABLE_COLORS ; i++)
288 RandomizationRange random = _randomTable != 0 ? _randomTable[i] : RandomizationRange();
289 writeColorEntry(config,colorNameForIndex(i),colorTable()[i],random);
293 QString ColorScheme::colorNameForIndex(int index)
295 Q_ASSERT( index >= 0 && index < TABLE_COLORS );
297 return QString(colorNames[index]);
299 QString ColorScheme::translatedColorNameForIndex(int index)
301 Q_ASSERT( index >= 0 && index < TABLE_COLORS );
303 return i18n(translatedColorNames[index]);
305 void ColorScheme::readColorEntry(KConfig& config , int index)
307 KConfigGroup configGroup(&config,colorNameForIndex(index));
309 ColorEntry entry;
311 entry.color = configGroup.readEntry("Color",QColor());
312 entry.transparent = configGroup.readEntry("Transparent",false);
314 // Deprecated key from KDE 4.0 which set 'Bold' to true to force
315 // a color to be bold or false to use the current format
317 // TODO - Add a new tri-state key which allows for bold, normal or
318 // current format
319 if (configGroup.hasKey("Bold"))
320 entry.fontWeight = configGroup.readEntry("Bold",false) ? ColorEntry::Bold :
321 ColorEntry::UseCurrentFormat;
323 quint16 hue = configGroup.readEntry("MaxRandomHue",0);
324 quint8 value = configGroup.readEntry("MaxRandomValue",0);
325 quint8 saturation = configGroup.readEntry("MaxRandomSaturation",0);
327 setColorTableEntry( index , entry );
329 if ( hue != 0 || value != 0 || saturation != 0 )
330 setRandomizationRange( index , hue , saturation , value );
332 void ColorScheme::writeColorEntry(KConfig& config , const QString& colorName, const ColorEntry& entry , const RandomizationRange& random) const
334 KConfigGroup configGroup(&config,colorName);
336 configGroup.writeEntry("Color",entry.color);
337 configGroup.writeEntry("Transparency",(bool)entry.transparent);
338 if (entry.fontWeight != ColorEntry::UseCurrentFormat)
340 configGroup.writeEntry("Bold",entry.fontWeight == ColorEntry::Bold);
343 // record randomization if this color has randomization or
344 // if one of the keys already exists
345 if ( !random.isNull() || configGroup.hasKey("MaxRandomHue") )
347 configGroup.writeEntry("MaxRandomHue",(int)random.hue);
348 configGroup.writeEntry("MaxRandomValue",(int)random.value);
349 configGroup.writeEntry("MaxRandomSaturation",(int)random.saturation);
355 // Work In Progress - A color scheme for use on KDE setups for users
356 // with visual disabilities which means that they may have trouble
357 // reading text with the supplied color schemes.
359 // This color scheme uses only the 'safe' colors defined by the
360 // KColorScheme class.
362 // A complication this introduces is that each color provided by
363 // KColorScheme is defined as a 'background' or 'foreground' color.
364 // Only foreground colors are allowed to be used to render text and
365 // only background colors are allowed to be used for backgrounds.
367 // The ColorEntry and TerminalDisplay classes do not currently
368 // support this restriction.
370 // Requirements:
371 // - A color scheme which uses only colors from the KColorScheme class
372 // - Ability to restrict which colors the TerminalDisplay widget
373 // uses as foreground and background color
374 // - Make use of KGlobalSettings::allowDefaultBackgroundImages() as
375 // a hint to determine whether this accessible color scheme should
376 // be used by default.
379 // -- Robert Knight <robertknight@gmail.com> 21/07/2007
381 AccessibleColorScheme::AccessibleColorScheme()
382 : ColorScheme()
384 // basic attributes
385 setName("accessible");
386 setDescription(i18n("Accessible Color Scheme"));
388 // setup colors
389 const int ColorRoleCount = 8;
391 const KColorScheme colorScheme(QPalette::Active);
393 QBrush colors[ColorRoleCount] =
395 colorScheme.foreground( colorScheme.NormalText ),
396 colorScheme.background( colorScheme.NormalBackground ),
398 colorScheme.foreground( colorScheme.InactiveText ),
399 colorScheme.foreground( colorScheme.ActiveText ),
400 colorScheme.foreground( colorScheme.LinkText ),
401 colorScheme.foreground( colorScheme.VisitedText ),
402 colorScheme.foreground( colorScheme.NegativeText ),
403 colorScheme.foreground( colorScheme.NeutralText )
406 for ( int i = 0 ; i < TABLE_COLORS ; i++ )
408 ColorEntry entry;
409 entry.color = colors[ i % ColorRoleCount ].color();
411 setColorTableEntry( i , entry );
415 KDE3ColorSchemeReader::KDE3ColorSchemeReader( QIODevice* device ) :
416 _device(device)
419 ColorScheme* KDE3ColorSchemeReader::read()
421 Q_ASSERT( _device->openMode() == QIODevice::ReadOnly ||
422 _device->openMode() == QIODevice::ReadWrite );
424 ColorScheme* scheme = new ColorScheme();
426 QRegExp comment("#.*$");
427 while ( !_device->atEnd() )
429 QString line(_device->readLine());
430 line.replace(comment,QString());
431 line = line.simplified();
433 if ( line.isEmpty() )
434 continue;
436 if ( line.startsWith("color") )
438 if (!readColorLine(line,scheme))
439 kWarning() << "Failed to read KDE 3 color scheme line" << line;
441 else if ( line.startsWith("title") )
443 if (!readTitleLine(line,scheme))
444 kWarning() << "Failed to read KDE 3 color scheme title line" << line;
446 else
448 kWarning() << "KDE 3 color scheme contains an unsupported feature, '" <<
449 line << "'";
453 return scheme;
455 bool KDE3ColorSchemeReader::readColorLine(const QString& line,ColorScheme* scheme)
457 QStringList list = line.split(QChar(' '));
459 if (list.count() != 7)
460 return false;
461 if (list.first() != "color")
462 return false;
464 int index = list[1].toInt();
465 int red = list[2].toInt();
466 int green = list[3].toInt();
467 int blue = list[4].toInt();
468 int transparent = list[5].toInt();
469 int bold = list[6].toInt();
471 const int MAX_COLOR_VALUE = 255;
473 if( (index < 0 || index >= TABLE_COLORS )
474 || (red < 0 || red > MAX_COLOR_VALUE )
475 || (blue < 0 || blue > MAX_COLOR_VALUE )
476 || (green < 0 || green > MAX_COLOR_VALUE )
477 || (transparent != 0 && transparent != 1 )
478 || (bold != 0 && bold != 1) )
479 return false;
481 ColorEntry entry;
482 entry.color = QColor(red,green,blue);
483 entry.transparent = ( transparent != 0 );
484 entry.fontWeight = ( bold != 0 ) ? ColorEntry::Bold : ColorEntry::UseCurrentFormat;
486 scheme->setColorTableEntry(index,entry);
487 return true;
489 bool KDE3ColorSchemeReader::readTitleLine(const QString& line,ColorScheme* scheme)
491 if( !line.startsWith("title") )
492 return false;
494 int spacePos = line.indexOf(' ');
495 if( spacePos == -1 )
496 return false;
498 QString description = line.mid(spacePos+1);
500 scheme->setDescription(i18n(description.toUtf8()));
501 return true;
503 ColorSchemeManager::ColorSchemeManager()
504 : _haveLoadedAll(false)
507 ColorSchemeManager::~ColorSchemeManager()
509 QHashIterator<QString,const ColorScheme*> iter(_colorSchemes);
510 while (iter.hasNext())
512 iter.next();
513 delete iter.value();
516 void ColorSchemeManager::loadAllColorSchemes()
518 int success = 0;
519 int failed = 0;
521 QList<QString> nativeColorSchemes = listColorSchemes();
523 QListIterator<QString> nativeIter(nativeColorSchemes);
524 while ( nativeIter.hasNext() )
526 if ( loadColorScheme( nativeIter.next() ) )
527 success++;
528 else
529 failed++;
532 QList<QString> kde3ColorSchemes = listKDE3ColorSchemes();
533 QListIterator<QString> kde3Iter(kde3ColorSchemes);
534 while ( kde3Iter.hasNext() )
536 if ( loadKDE3ColorScheme( kde3Iter.next() ) )
537 success++;
538 else
539 failed++;
542 if ( failed > 0 )
543 kWarning() << "failed to load " << failed << " color schemes.";
545 _haveLoadedAll = true;
547 QList<const ColorScheme*> ColorSchemeManager::allColorSchemes()
549 if ( !_haveLoadedAll )
551 loadAllColorSchemes();
554 return _colorSchemes.values();
556 bool ColorSchemeManager::loadKDE3ColorScheme(const QString& filePath)
558 QFile file(filePath);
559 if (!filePath.endsWith(".schema") || !file.open(QIODevice::ReadOnly))
560 return false;
562 KDE3ColorSchemeReader reader(&file);
563 ColorScheme* scheme = reader.read();
564 scheme->setName(QFileInfo(file).baseName());
565 file.close();
567 if (scheme->name().isEmpty())
569 kWarning() << "color scheme name is not valid.";
570 delete scheme;
571 return false;
574 QFileInfo info(filePath);
576 if ( !_colorSchemes.contains(info.baseName()) )
577 _colorSchemes.insert(scheme->name(),scheme);
578 else
580 kDebug() << "color scheme with name" << scheme->name() << "has already been" <<
581 "found, ignoring.";
582 delete scheme;
585 return true;
587 void ColorSchemeManager::addColorScheme(ColorScheme* scheme)
589 _colorSchemes.insert(scheme->name(),scheme);
591 // save changes to disk
592 QString path = KGlobal::dirs()->saveLocation("data","konsole/") + scheme->name() + ".colorscheme";
593 KConfig config(path , KConfig::NoGlobals);
595 scheme->write(config);
597 bool ColorSchemeManager::loadColorScheme(const QString& filePath)
599 if ( !filePath.endsWith(".colorscheme") || !QFile::exists(filePath) )
600 return false;
602 QFileInfo info(filePath);
604 KConfig config(filePath , KConfig::NoGlobals);
605 ColorScheme* scheme = new ColorScheme();
606 scheme->setName(info.baseName());
607 scheme->read(config);
609 if (scheme->name().isEmpty())
611 kWarning() << "Color scheme in" << filePath << "does not have a valid name and was not loaded.";
612 delete scheme;
613 return false;
616 if ( !_colorSchemes.contains(info.baseName()) )
618 _colorSchemes.insert(scheme->name(),scheme);
620 else
622 kDebug() << "color scheme with name" << scheme->name() << "has already been" <<
623 "found, ignoring.";
625 delete scheme;
628 return true;
630 QList<QString> ColorSchemeManager::listKDE3ColorSchemes()
632 return KGlobal::dirs()->findAllResources("data",
633 "konsole/*.schema",
634 KStandardDirs::NoDuplicates);
637 QList<QString> ColorSchemeManager::listColorSchemes()
639 return KGlobal::dirs()->findAllResources("data",
640 "konsole/*.colorscheme",
641 KStandardDirs::NoDuplicates);
643 const ColorScheme ColorSchemeManager::_defaultColorScheme;
644 const ColorScheme* ColorSchemeManager::defaultColorScheme() const
646 return &_defaultColorScheme;
648 bool ColorSchemeManager::deleteColorScheme(const QString& name)
650 Q_ASSERT( _colorSchemes.contains(name) );
652 // lookup the path and delete
653 QString path = findColorSchemePath(name);
654 if ( QFile::remove(path) )
656 _colorSchemes.remove(name);
657 return true;
659 else
661 kWarning() << "Failed to remove color scheme -" << path;
662 return false;
665 QString ColorSchemeManager::findColorSchemePath(const QString& name) const
667 QString path = KStandardDirs::locate("data","konsole/"+name+".colorscheme");
669 if ( !path.isEmpty() )
670 return path;
672 path = KStandardDirs::locate("data","konsole/"+name+".schema");
674 return path;
676 const ColorScheme* ColorSchemeManager::findColorScheme(const QString& name)
678 if ( name.isEmpty() )
679 return defaultColorScheme();
681 if ( _colorSchemes.contains(name) )
682 return _colorSchemes[name];
683 else
685 // look for this color scheme
686 QString path = findColorSchemePath(name);
687 if ( !path.isEmpty() && loadColorScheme(path) )
689 return findColorScheme(name);
691 else
693 if (!path.isEmpty() && loadKDE3ColorScheme(path))
694 return findColorScheme(name);
697 kWarning() << "Could not find color scheme - " << name;
699 return 0;
702 K_GLOBAL_STATIC( ColorSchemeManager , theColorSchemeManager )
703 ColorSchemeManager* ColorSchemeManager::instance()
705 return theColorSchemeManager;