pylint: rename variables for readability
[git-cola.git] / cola / themes.py
blob6e8c2319d48957fbe924c40f6e1e196eb68d82b4
1 """Themes generators"""
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 import os
5 from qtpy import QtGui
7 from .i18n import N_
8 from .widgets import defs
9 from . import core
10 from . import icons
11 from . import qtutils
12 from . import resources
15 class EStylesheet(object):
16 DEFAULT = 1
17 FLAT = 2
18 CUSTOM = 3 # Files located in ~/.config/git-cola/themes/*.qss
21 class Theme(object):
22 def __init__(
23 self, name, hr_name, is_dark, style_sheet=EStylesheet.DEFAULT, main_color=None
25 self.name = name
26 self.hr_name = hr_name
27 self.is_dark = is_dark
28 self.is_palette_dark = None
29 self.style_sheet = style_sheet
30 self.main_color = main_color
31 self.disabled_text_color = None
32 self.text_color = None
33 self.highlight_color = None
34 self.background_color = None
35 self.palette = None
37 def build_style_sheet(self, app_palette):
38 if self.style_sheet == EStylesheet.CUSTOM:
39 return self.style_sheet_custom(app_palette)
40 if self.style_sheet == EStylesheet.FLAT:
41 return self.style_sheet_flat()
42 window = app_palette.color(QtGui.QPalette.Window)
43 self.is_palette_dark = window.lightnessF() < 0.5
44 return style_sheet_default(app_palette)
46 def build_palette(self, app_palette):
47 QPalette = QtGui.QPalette
48 palette_dark = app_palette.color(QPalette.Base).lightnessF() < 0.5
49 if self.is_palette_dark is None:
50 self.is_palette_dark = palette_dark
52 if palette_dark and self.is_dark:
53 self.palette = app_palette
54 return app_palette
55 if not palette_dark and not self.is_dark:
56 self.palette = app_palette
57 return app_palette
58 if self.is_dark:
59 background = '#202025'
60 else:
61 background = '#edeef3'
63 bg_color = qtutils.css_color(background)
64 txt_color = qtutils.css_color('#777777')
65 palette = QPalette(bg_color)
66 palette.setColor(QPalette.Base, bg_color)
67 palette.setColor(QPalette.Disabled, QPalette.Text, txt_color)
68 self.background_color = background
69 self.palette = palette
70 return palette
72 def style_sheet_flat(self):
73 main_color = self.main_color
74 color = qtutils.css_color(main_color)
75 color_rgb = qtutils.rgb_css(color)
76 self.is_palette_dark = self.is_dark
78 if self.is_dark:
79 background = '#2e2f30'
80 field = '#383a3c'
81 grayed = '#06080a'
82 button_text = '#000000'
83 field_text = '#d0d0d0'
84 darker = qtutils.hsl_css(
85 color.hslHueF(), color.hslSaturationF() * 0.3, color.lightnessF() * 1.3
87 lighter = qtutils.hsl_css(
88 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.6
90 focus = qtutils.hsl_css(
91 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.7
93 else:
94 background = '#edeef3'
95 field = '#ffffff'
96 grayed = '#a2a2b0'
97 button_text = '#ffffff'
98 field_text = '#000000'
99 darker = qtutils.hsl_css(
100 color.hslHueF(), color.hslSaturationF(), color.lightnessF() * 0.4
102 lighter = qtutils.hsl_css(color.hslHueF(), color.hslSaturationF(), 0.92)
103 focus = color_rgb
105 self.disabled_text_color = grayed
106 self.text_color = field_text
107 self.highlight_color = lighter
108 self.background_color = background
110 return """
111 /* regular widgets */
113 background-color: %(background)s;
114 color: %(field_text)s;
115 selection-background-color: %(lighter)s;
116 alternate-background-color: %(field)s;
117 selection-color: %(field_text)s;
118 show-decoration-selected: 1;
119 spacing: 2px;
122 /* Focused widths get a thin border */
123 QTreeView:focus, QListView:focus,
124 QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
125 border-width: 1px;
126 border-style: solid;
127 border-color: %(focus)s;
130 QWidget:disabled {
131 border-color: %(grayed)s;
132 color: %(grayed)s;
134 QDockWidget > QFrame {
135 margin: 0 2px 2px 2px;
136 min-height: 40px;
138 QPlainTextEdit, QLineEdit, QTextEdit, QAbstractItemView,
139 QAbstractSpinBox {
140 background-color: %(field)s;
141 border-color: %(grayed)s;
142 border-style: solid;
143 border-width: 1px;
145 QAbstractItemView::item:selected {
146 background-color: %(lighter)s;
148 QAbstractItemView::item:hover {
149 background-color: %(lighter)s;
151 QLabel {
152 color: %(darker)s;
153 background-color: transparent;
155 DockTitleBarWidget {
156 padding-bottom: 4px;
159 /* buttons */
160 QPushButton[flat="false"] {
161 background-color: %(button)s;
162 color: %(button_text)s;
163 border-radius: 2px;
164 border-width: 0;
165 margin-bottom: 1px;
166 min-width: 55px;
167 padding: 4px 5px;
169 QPushButton[flat="true"], QToolButton {
170 background-color: transparent;
171 border-radius: 0px;
173 QPushButton[flat="true"] {
174 margin-bottom: 10px;
176 QPushButton:hover, QToolButton:hover {
177 background-color: %(darker)s;
179 QPushButton[flat="false"]:pressed, QToolButton:pressed {
180 background-color: %(darker)s;
181 margin: 1px 1px 2px 1px;
183 QPushButton:disabled {
184 background-color: %(grayed)s;
185 color: %(field)s;
186 padding-left: 5px;
187 padding-top: 5px;
189 QPushButton[flat="true"]:disabled {
190 background-color: transparent;
193 /*menus*/
194 QMenuBar {
195 background-color: %(background)s;
196 color: %(field_text)s;
197 border-width: 0;
198 padding: 1px;
200 QMenuBar::item {
201 background: transparent;
203 QMenuBar::item:selected {
204 background: %(button)s;
206 QMenuBar::item:pressed {
207 background: %(button)s;
209 QMenu {
210 background-color: %(field)s;
212 QMenu::separator {
213 background: %(background)s;
214 height: 1px;
217 /* combo box */
218 QComboBox {
219 background-color: %(field)s;
220 border-color: %(grayed)s;
221 border-style: solid;
222 color: %(field_text)s;
223 border-radius: 0px;
224 border-width: 1px;
225 margin-bottom: 1px;
226 padding: 0 5px;
228 QComboBox::drop-down {
229 border-color: %(field_text)s %(field)s %(field)s %(field)s;
230 border-style: solid;
231 subcontrol-position: right;
232 border-width: 4px 3px 0 3px;
233 height: 0;
234 margin-right: 5px;
235 width: 0;
237 QComboBox::drop-down:hover {
238 border-color: %(button)s %(field)s %(field)s %(field)s;
240 QComboBox:item {
241 background-color: %(button)s;
242 color: %(button_text)s;
243 border-width: 0;
244 height: 22px;
246 QComboBox:item:selected {
247 background-color: %(darker)s;
248 color: %(button_text)s;
250 QComboBox:item:checked {
251 background-color: %(darker)s;
252 color: %(button_text)s;
255 /* MainWindow separator */
256 QMainWindow::separator {
257 width: %(separator)spx;
258 height: %(separator)spx;
260 QMainWindow::separator:hover {
261 background: %(focus)s;
264 /* scroll bar */
265 QScrollBar {
266 background-color: %(field)s;
267 border: 0;
269 QScrollBar::handle {
270 background: %(background)s
272 QScrollBar::handle:hover {
273 background: %(button)s
275 QScrollBar:horizontal {
276 margin: 0 11px 0 11px;
277 height: 10px;
279 QScrollBar:vertical {
280 margin: 11px 0 11px 0;
281 width: 10px;
283 QScrollBar::add-line, QScrollBar::sub-line {
284 background: %(background)s;
285 subcontrol-origin: margin;
287 QScrollBar::sub-line:horizontal { /*required by a buggy Qt version*/
288 subcontrol-position: left;
290 QScrollBar::add-line:hover, QScrollBar::sub-line:hover {
291 background: %(button)s;
293 QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
294 width: 10px;
296 QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
297 height: 10px;
299 QScrollBar:left-arrow, QScrollBar::right-arrow,
300 QScrollBar::up-arrow, QScrollBar::down-arrow {
301 border-style: solid;
302 height: 0;
303 width: 0;
305 QScrollBar:right-arrow {
306 border-color: %(background)s %(background)s
307 %(background)s %(darker)s;
308 border-width: 3px 0 3px 4px;
310 QScrollBar:left-arrow {
311 border-color: %(background)s %(darker)s
312 %(background)s %(background)s;
313 border-width: 3px 4px 3px 0;
315 QScrollBar:up-arrow {
316 border-color: %(background)s %(background)s
317 %(darker)s %(background)s;
318 border-width: 0 3px 4px 3px;
320 QScrollBar:down-arrow {
321 border-color: %(darker)s %(background)s
322 %(background)s %(background)s;
323 border-width: 4px 3px 0 3px;
325 QScrollBar:right-arrow:hover {
326 border-color: %(button)s %(button)s
327 %(button)s %(darker)s;
329 QScrollBar:left-arrow:hover {
330 border-color: %(button)s %(darker)s
331 %(button)s %(button)s;
333 QScrollBar:up-arrow:hover {
334 border-color: %(button)s %(button)s
335 %(darker)s %(button)s;
337 QScrollBar:down-arrow:hover {
338 border-color: %(darker)s %(button)s
339 %(button)s %(button)s;
342 /* tab bar (stacked & docked widgets) */
343 QTabBar::tab {
344 background: transparent;
345 border-color: %(darker)s;
346 border-width: 1px;
347 margin: 1px;
348 padding: 3px 5px;
350 QTabBar::tab:selected {
351 background: %(grayed)s;
354 /* check box */
355 QCheckBox {
356 spacing: 8px;
357 margin: 4px;
358 background-color: transparent;
360 QCheckBox::indicator {
361 background-color: %(field)s;
362 border-color: %(darker)s;
363 border-style: solid;
364 subcontrol-position: left;
365 border-width: 1px;
366 height: 13px;
367 width: 13px;
369 QCheckBox::indicator:unchecked:hover {
370 background-color: %(button)s;
372 QCheckBox::indicator:unchecked:pressed {
373 background-color: %(darker)s;
375 QCheckBox::indicator:checked {
376 background-color: %(darker)s;
378 QCheckBox::indicator:checked:hover {
379 background-color: %(button)s;
381 QCheckBox::indicator:checked:pressed {
382 background-color: %(field)s;
385 /* radio checkbox */
386 QRadioButton {
387 spacing: 8px;
388 margin: 4px;
390 QRadioButton::indicator {
391 height: 0.75em;
392 width: 0.75em;
395 /* progress bar */
396 QProgressBar {
397 background-color: %(field)s;
398 border: 1px solid %(darker)s;
400 QProgressBar::chunk {
401 background-color: %(button)s;
402 width: 1px;
405 /* spin box */
406 QAbstractSpinBox::up-button, QAbstractSpinBox::down-button {
407 background-color: transparent;
409 QAbstractSpinBox::up-arrow, QAbstractSpinBox::down-arrow {
410 border-style: solid;
411 height: 0;
412 width: 0;
414 QAbstractSpinBox::up-arrow {
415 border-color: %(field)s %(field)s %(darker)s %(field)s;
416 border-width: 0 3px 4px 3px;
418 QAbstractSpinBox::up-arrow:hover {
419 border-color: %(field)s %(field)s %(button)s %(field)s;
420 border-width: 0 3px 4px 3px;
422 QAbstractSpinBox::down-arrow {
423 border-color: %(darker)s %(field)s %(field)s %(field)s;
424 border-width: 4px 3px 0 3px;
426 QAbstractSpinBox::down-arrow:hover {
427 border-color: %(button)s %(field)s %(field)s %(field)s;
428 border-width: 4px 3px 0 3px;
431 /* dialogs */
432 QDialog > QFrame {
433 margin: 2px 2px 2px 2px;
436 /* headers */
437 QHeaderView {
438 color: %(field_text)s;
439 border-style: solid;
440 border-width: 0 0 1px 0;
441 border-color: %(grayed)s;
443 QHeaderView::section {
444 border-style: solid;
445 border-right: 1px solid %(grayed)s;
446 background-color: %(background)s;
447 color: %(field_text)s;
448 padding-left: 4px;
451 /* headers */
452 QHeaderView {
453 color: %(field_text)s;
454 border-style: solid;
455 border-width: 0 0 1px 0;
456 border-color: %(grayed)s;
458 QHeaderView::section {
459 border-style: solid;
460 border-right: 1px solid %(grayed)s;
461 background-color: %(background)s;
462 color: %(field_text)s;
463 padding-left: 4px;
466 """ % dict(
467 background=background,
468 field=field,
469 button=color_rgb,
470 darker=darker,
471 lighter=lighter,
472 grayed=grayed,
473 button_text=button_text,
474 field_text=field_text,
475 separator=defs.separator,
476 focus=focus,
479 def style_sheet_custom(self, app_palette):
480 """Get custom style sheet.
481 File name is saved in variable self.name.
482 If user has deleted file, use default style"""
484 # check if path exists
485 filename = resources.config_home('themes', self.name + '.qss')
486 if not core.exists(filename):
487 return style_sheet_default(app_palette)
488 try:
489 return core.read(filename)
490 except (IOError, OSError) as err:
491 core.print_stderr(
492 'warning: unable to read custom theme %s: %s' % (filename, err)
494 return style_sheet_default(app_palette)
496 def get_palette(self):
497 """Get a QPalette for the current theme"""
498 if self.palette is None:
499 palette = qtutils.current_palette()
500 else:
501 palette = self.palette
502 return palette
504 def highlight_color_rgb(self):
505 """Return an rgb(r,g,b) css color value for the selection highlight"""
506 if self.highlight_color:
507 highlight_rgb = self.highlight_color
508 elif self.main_color:
509 highlight_rgb = qtutils.rgb_css(
510 qtutils.css_color(self.main_color).lighter()
512 else:
513 palette = self.get_palette()
514 color = palette.color(QtGui.QPalette.Highlight)
515 highlight_rgb = qtutils.rgb_css(color)
516 return highlight_rgb
518 def selection_color(self):
519 """Return a color suitable for selections"""
520 highlight = qtutils.css_color(self.highlight_color_rgb())
521 if highlight.lightnessF() > 0.7: # Avoid clamping light colors to white.
522 color = highlight
523 else:
524 color = highlight.lighter()
525 return color
527 def text_colors_rgb(self):
528 """Return a pair of rgb(r,g,b) css color values for text and selected text"""
529 if self.text_color:
530 text_rgb = self.text_color
531 highlight_text_rgb = self.text_color
532 else:
533 palette = self.get_palette()
534 color = palette.text().color()
535 text_rgb = qtutils.rgb_css(color)
537 color = palette.highlightedText().color()
538 highlight_text_rgb = qtutils.rgb_css(color)
539 return text_rgb, highlight_text_rgb
541 def disabled_text_color_rgb(self):
542 """Return an rgb(r,g,b) css color value for the disabled text"""
543 if self.disabled_text_color:
544 disabled_text_rgb = self.disabled_text_color
545 else:
546 palette = self.get_palette()
547 color = palette.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
548 disabled_text_rgb = qtutils.rgb_css(color)
549 return disabled_text_rgb
551 def background_color_rgb(self):
552 """Return an rgb(r,g,b) css color value for the window background"""
553 if self.background_color:
554 background_color = self.background_color
555 else:
556 palette = self.get_palette()
557 window = palette.color(QtGui.QPalette.Base)
558 background_color = qtutils.rgb_css(window)
559 return background_color
562 def style_sheet_default(palette):
563 window = palette.color(QtGui.QPalette.Window)
564 highlight = palette.color(QtGui.QPalette.Highlight)
565 shadow = palette.color(QtGui.QPalette.Shadow)
566 base = palette.color(QtGui.QPalette.Base)
568 window_rgb = qtutils.rgb_css(window)
569 highlight_rgb = qtutils.rgb_css(highlight)
570 shadow_rgb = qtutils.rgb_css(shadow)
571 base_rgb = qtutils.rgb_css(base)
573 return """
574 QCheckBox::indicator {
575 width: %(checkbox_size)spx;
576 height: %(checkbox_size)spx;
578 QCheckBox::indicator::unchecked {
579 border: %(checkbox_border)spx solid %(shadow_rgb)s;
580 background: %(base_rgb)s;
582 QCheckBox::indicator::checked {
583 image: url(%(checkbox_icon)s);
584 border: %(checkbox_border)spx solid %(shadow_rgb)s;
585 background: %(base_rgb)s;
588 QRadioButton::indicator {
589 width: %(radio_size)spx;
590 height: %(radio_size)spx;
592 QRadioButton::indicator::unchecked {
593 border: %(radio_border)spx solid %(shadow_rgb)s;
594 border-radius: %(radio_radius)spx;
595 background: %(base_rgb)s;
597 QRadioButton::indicator::checked {
598 image: url(%(radio_icon)s);
599 border: %(radio_border)spx solid %(shadow_rgb)s;
600 border-radius: %(radio_radius)spx;
601 background: %(base_rgb)s;
604 QSplitter::handle:hover {
605 background: %(highlight_rgb)s;
608 QMainWindow::separator {
609 background: %(window_rgb)s;
610 width: %(separator)spx;
611 height: %(separator)spx;
613 QMainWindow::separator:hover {
614 background: %(highlight_rgb)s;
617 """ % dict(
618 separator=defs.separator,
619 window_rgb=window_rgb,
620 highlight_rgb=highlight_rgb,
621 shadow_rgb=shadow_rgb,
622 base_rgb=base_rgb,
623 checkbox_border=defs.border,
624 checkbox_icon=icons.check_name(),
625 checkbox_size=defs.checkbox,
626 radio_border=defs.radio_border,
627 radio_icon=icons.dot_name(),
628 radio_radius=defs.radio // 2,
629 radio_size=defs.radio,
633 def get_all_themes():
634 themes = [
635 Theme(
636 'default',
637 N_('Default'),
638 False,
639 style_sheet=EStylesheet.DEFAULT,
640 main_color=None,
642 Theme(
643 'flat-light-blue',
644 N_('Flat light blue'),
645 False,
646 style_sheet=EStylesheet.FLAT,
647 main_color='#5271cc',
649 Theme(
650 'flat-light-red',
651 N_('Flat light red'),
652 False,
653 style_sheet=EStylesheet.FLAT,
654 main_color='#cc5452',
656 Theme(
657 'flat-light-grey',
658 N_('Flat light grey'),
659 False,
660 style_sheet=EStylesheet.FLAT,
661 main_color='#707478',
663 Theme(
664 'flat-light-green',
665 N_('Flat light green'),
666 False,
667 style_sheet=EStylesheet.FLAT,
668 main_color='#42a65c',
670 Theme(
671 'flat-dark-blue',
672 N_('Flat dark blue'),
673 True,
674 style_sheet=EStylesheet.FLAT,
675 main_color='#5271cc',
677 Theme(
678 'flat-dark-red',
679 N_('Flat dark red'),
680 True,
681 style_sheet=EStylesheet.FLAT,
682 main_color='#cc5452',
684 Theme(
685 'flat-dark-grey',
686 N_('Flat dark grey'),
687 True,
688 style_sheet=EStylesheet.FLAT,
689 main_color='#aaaaaa',
691 Theme(
692 'flat-dark-green',
693 N_('Flat dark green'),
694 True,
695 style_sheet=EStylesheet.FLAT,
696 main_color='#42a65c',
700 # check if themes path exists in user folder
701 path = resources.config_home('themes')
702 if not os.path.isdir(path):
703 return themes
705 # Gather Qt .qss stylesheet themes
706 try:
707 filenames = core.listdir(path)
708 except OSError:
709 return themes
711 for filename in filenames:
712 name, ext = os.path.splitext(filename)
713 if ext == '.qss':
714 themes.append(Theme(name, N_(name), False, EStylesheet.CUSTOM, None))
716 return themes
719 def options():
720 """Return a dictionary mapping display names to theme names"""
721 items = get_all_themes()
722 return [(item.hr_name, item.name) for item in items]
725 def find_theme(name):
726 themes = get_all_themes()
727 for item in themes:
728 if item.name == name:
729 return item
730 return themes[0]