dag: add ability to checkout branches from the DAG viewer
[git-cola.git] / cola / themes.py
blob1ac6357a2cb187c1615fb6620450f61f608fec45
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 elif self.style_sheet == EStylesheet.FLAT:
41 return self.style_sheet_flat()
42 else:
43 window = app_palette.color(QtGui.QPalette.Window)
44 self.is_palette_dark = window.lightnessF() < 0.5
45 return style_sheet_default(app_palette)
47 def build_palette(self, app_palette):
48 QPalette = QtGui.QPalette
49 palette_dark = app_palette.color(QPalette.Base).lightnessF() < 0.5
50 if self.is_palette_dark is None:
51 self.is_palette_dark = palette_dark
53 if palette_dark and self.is_dark:
54 self.palette = app_palette
55 return app_palette
56 if not palette_dark and not self.is_dark:
57 self.palette = app_palette
58 return app_palette
59 if self.is_dark:
60 background = '#202025'
61 else:
62 background = '#edeef3'
64 bg_color = qtutils.css_color(background)
65 txt_color = qtutils.css_color('#777777')
66 palette = QPalette(bg_color)
67 palette.setColor(QPalette.Base, bg_color)
68 palette.setColor(QPalette.Disabled, QPalette.Text, txt_color)
69 self.background_color = background
70 self.palette = palette
71 return palette
73 def style_sheet_flat(self):
74 main_color = self.main_color
75 color = qtutils.css_color(main_color)
76 color_rgb = qtutils.rgb_css(color)
77 self.is_palette_dark = self.is_dark
79 if self.is_dark:
80 background = '#2e2f30'
81 field = '#383a3c'
82 grayed = '#06080a'
83 button_text = '#000000'
84 field_text = '#d0d0d0'
85 darker = qtutils.hsl_css(
86 color.hslHueF(), color.hslSaturationF() * 0.3, color.lightnessF() * 1.3
88 lighter = qtutils.hsl_css(
89 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.6
91 focus = qtutils.hsl_css(
92 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.7
94 else:
95 background = '#edeef3'
96 field = '#ffffff'
97 grayed = '#a2a2b0'
98 button_text = '#ffffff'
99 field_text = '#000000'
100 darker = qtutils.hsl_css(
101 color.hslHueF(), color.hslSaturationF(), color.lightnessF() * 0.4
103 lighter = qtutils.hsl_css(color.hslHueF(), color.hslSaturationF(), 0.92)
104 focus = color_rgb
106 self.disabled_text_color = grayed
107 self.text_color = field_text
108 self.highlight_color = lighter
109 self.background_color = background
111 return """
112 /* regular widgets */
114 background-color: %(background)s;
115 color: %(field_text)s;
116 selection-background-color: %(lighter)s;
117 alternate-background-color: %(field)s;
118 selection-color: %(field_text)s;
119 show-decoration-selected: 1;
120 spacing: 2px;
123 /* Focused widths get a thin border */
124 QTreeView:focus, QListView:focus,
125 QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
126 border-width: 1px;
127 border-style: solid;
128 border-color: %(focus)s;
131 QWidget:disabled {
132 border-color: %(grayed)s;
133 color: %(grayed)s;
135 QDockWidget > QFrame {
136 margin: 0 2px 2px 2px;
137 min-height: 40px;
139 QPlainTextEdit, QLineEdit, QTextEdit, QAbstractItemView,
140 QAbstractSpinBox {
141 background-color: %(field)s;
142 border-color: %(grayed)s;
143 border-style: solid;
144 border-width: 1px;
146 QAbstractItemView::item:selected {
147 background-color: %(lighter)s;
149 QAbstractItemView::item:hover {
150 background-color: %(lighter)s;
152 QLabel {
153 color: %(darker)s;
154 background-color: transparent;
156 DockTitleBarWidget {
157 padding-bottom: 4px;
160 /* buttons */
161 QPushButton[flat="false"] {
162 background-color: %(button)s;
163 color: %(button_text)s;
164 border-radius: 2px;
165 border-width: 0;
166 margin-bottom: 1px;
167 min-width: 55px;
168 padding: 4px 5px;
170 QPushButton[flat="true"], QToolButton {
171 background-color: transparent;
172 border-radius: 0px;
174 QPushButton[flat="true"] {
175 margin-bottom: 10px;
177 QPushButton:hover, QToolButton:hover {
178 background-color: %(darker)s;
180 QPushButton[flat="false"]:pressed, QToolButton:pressed {
181 background-color: %(darker)s;
182 margin: 1px 1px 2px 1px;
184 QPushButton:disabled {
185 background-color: %(grayed)s;
186 color: %(field)s;
187 padding-left: 5px;
188 padding-top: 5px;
190 QPushButton[flat="true"]:disabled {
191 background-color: transparent;
194 /*menus*/
195 QMenuBar {
196 background-color: %(background)s;
197 color: %(field_text)s;
198 border-width: 0;
199 padding: 1px;
201 QMenuBar::item {
202 background: transparent;
204 QMenuBar::item:selected {
205 background: %(button)s;
207 QMenuBar::item:pressed {
208 background: %(button)s;
210 QMenu {
211 background-color: %(field)s;
213 QMenu::separator {
214 background: %(background)s;
215 height: 1px;
218 /* combo box */
219 QComboBox {
220 background-color: %(field)s;
221 border-color: %(grayed)s;
222 border-style: solid;
223 color: %(field_text)s;
224 border-radius: 0px;
225 border-width: 1px;
226 margin-bottom: 1px;
227 padding: 0 5px;
229 QComboBox::drop-down {
230 border-color: %(field_text)s %(field)s %(field)s %(field)s;
231 border-style: solid;
232 subcontrol-position: right;
233 border-width: 4px 3px 0 3px;
234 height: 0;
235 margin-right: 5px;
236 width: 0;
238 QComboBox::drop-down:hover {
239 border-color: %(button)s %(field)s %(field)s %(field)s;
241 QComboBox:item {
242 background-color: %(button)s;
243 color: %(button_text)s;
244 border-width: 0;
245 height: 22px;
247 QComboBox:item:selected {
248 background-color: %(darker)s;
249 color: %(button_text)s;
251 QComboBox:item:checked {
252 background-color: %(darker)s;
253 color: %(button_text)s;
256 /* MainWindow separator */
257 QMainWindow::separator {
258 width: %(separator)spx;
259 height: %(separator)spx;
261 QMainWindow::separator:hover {
262 background: %(focus)s;
265 /* scroll bar */
266 QScrollBar {
267 background-color: %(field)s;
268 border: 0;
270 QScrollBar::handle {
271 background: %(background)s
273 QScrollBar::handle:hover {
274 background: %(button)s
276 QScrollBar:horizontal {
277 margin: 0 11px 0 11px;
278 height: 10px;
280 QScrollBar:vertical {
281 margin: 11px 0 11px 0;
282 width: 10px;
284 QScrollBar::add-line, QScrollBar::sub-line {
285 background: %(background)s;
286 subcontrol-origin: margin;
288 QScrollBar::sub-line:horizontal { /*required by a buggy Qt version*/
289 subcontrol-position: left;
291 QScrollBar::add-line:hover, QScrollBar::sub-line:hover {
292 background: %(button)s;
294 QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
295 width: 10px;
297 QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
298 height: 10px;
300 QScrollBar:left-arrow, QScrollBar::right-arrow,
301 QScrollBar::up-arrow, QScrollBar::down-arrow {
302 border-style: solid;
303 height: 0;
304 width: 0;
306 QScrollBar:right-arrow {
307 border-color: %(background)s %(background)s
308 %(background)s %(darker)s;
309 border-width: 3px 0 3px 4px;
311 QScrollBar:left-arrow {
312 border-color: %(background)s %(darker)s
313 %(background)s %(background)s;
314 border-width: 3px 4px 3px 0;
316 QScrollBar:up-arrow {
317 border-color: %(background)s %(background)s
318 %(darker)s %(background)s;
319 border-width: 0 3px 4px 3px;
321 QScrollBar:down-arrow {
322 border-color: %(darker)s %(background)s
323 %(background)s %(background)s;
324 border-width: 4px 3px 0 3px;
326 QScrollBar:right-arrow:hover {
327 border-color: %(button)s %(button)s
328 %(button)s %(darker)s;
330 QScrollBar:left-arrow:hover {
331 border-color: %(button)s %(darker)s
332 %(button)s %(button)s;
334 QScrollBar:up-arrow:hover {
335 border-color: %(button)s %(button)s
336 %(darker)s %(button)s;
338 QScrollBar:down-arrow:hover {
339 border-color: %(darker)s %(button)s
340 %(button)s %(button)s;
343 /* tab bar (stacked & docked widgets) */
344 QTabBar::tab {
345 background: transparent;
346 border-color: %(darker)s;
347 border-width: 1px;
348 margin: 1px;
349 padding: 3px 5px;
351 QTabBar::tab:selected {
352 background: %(grayed)s;
355 /* check box */
356 QCheckBox {
357 spacing: 8px;
358 margin: 4px;
359 background-color: transparent;
361 QCheckBox::indicator {
362 background-color: %(field)s;
363 border-color: %(darker)s;
364 border-style: solid;
365 subcontrol-position: left;
366 border-width: 1px;
367 height: 13px;
368 width: 13px;
370 QCheckBox::indicator:unchecked:hover {
371 background-color: %(button)s;
373 QCheckBox::indicator:unchecked:pressed {
374 background-color: %(darker)s;
376 QCheckBox::indicator:checked {
377 background-color: %(darker)s;
379 QCheckBox::indicator:checked:hover {
380 background-color: %(button)s;
382 QCheckBox::indicator:checked:pressed {
383 background-color: %(field)s;
386 /* radio checkbox */
387 QRadioButton {
388 spacing: 8px;
389 margin: 4px;
391 QRadioButton::indicator {
392 height: 0.75em;
393 width: 0.75em;
396 /* progress bar */
397 QProgressBar {
398 background-color: %(field)s;
399 border: 1px solid %(darker)s;
401 QProgressBar::chunk {
402 background-color: %(button)s;
403 width: 1px;
406 /* spin box */
407 QAbstractSpinBox::up-button, QAbstractSpinBox::down-button {
408 background-color: transparent;
410 QAbstractSpinBox::up-arrow, QAbstractSpinBox::down-arrow {
411 border-style: solid;
412 height: 0;
413 width: 0;
415 QAbstractSpinBox::up-arrow {
416 border-color: %(field)s %(field)s %(darker)s %(field)s;
417 border-width: 0 3px 4px 3px;
419 QAbstractSpinBox::up-arrow:hover {
420 border-color: %(field)s %(field)s %(button)s %(field)s;
421 border-width: 0 3px 4px 3px;
423 QAbstractSpinBox::down-arrow {
424 border-color: %(darker)s %(field)s %(field)s %(field)s;
425 border-width: 4px 3px 0 3px;
427 QAbstractSpinBox::down-arrow:hover {
428 border-color: %(button)s %(field)s %(field)s %(field)s;
429 border-width: 4px 3px 0 3px;
432 /* dialogs */
433 QDialog > QFrame {
434 margin: 2px 2px 2px 2px;
437 /* headers */
438 QHeaderView {
439 color: %(field_text)s;
440 border-style: solid;
441 border-width: 0 0 1px 0;
442 border-color: %(grayed)s;
444 QHeaderView::section {
445 border-style: solid;
446 border-right: 1px solid %(grayed)s;
447 background-color: %(background)s;
448 color: %(field_text)s;
449 padding-left: 4px;
452 /* headers */
453 QHeaderView {
454 color: %(field_text)s;
455 border-style: solid;
456 border-width: 0 0 1px 0;
457 border-color: %(grayed)s;
459 QHeaderView::section {
460 border-style: solid;
461 border-right: 1px solid %(grayed)s;
462 background-color: %(background)s;
463 color: %(field_text)s;
464 padding-left: 4px;
467 """ % dict(
468 background=background,
469 field=field,
470 button=color_rgb,
471 darker=darker,
472 lighter=lighter,
473 grayed=grayed,
474 button_text=button_text,
475 field_text=field_text,
476 separator=defs.separator,
477 focus=focus,
480 def style_sheet_custom(self, app_palette):
481 """Get custom style sheet.
482 File name is saved in variable self.name.
483 If user has deleted file, use default style"""
485 # check if path exists
486 filename = resources.config_home('themes', self.name + '.qss')
487 if not core.exists(filename):
488 return style_sheet_default(app_palette)
489 try:
490 return core.read(filename)
491 except (IOError, OSError) as err:
492 core.print_stderr(
493 'warning: unable to read custom theme %s: %s' % (filename, err)
495 return style_sheet_default(app_palette)
497 def get_palette(self):
498 """Get a QPalette for the current theme"""
499 if self.palette is None:
500 palette = qtutils.current_palette()
501 else:
502 palette = self.palette
503 return palette
505 def highlight_color_rgb(self):
506 """Return an rgb(r,g,b) css color value for the selection highlight"""
507 if self.highlight_color:
508 highlight_rgb = self.highlight_color
509 elif self.main_color:
510 highlight_rgb = qtutils.rgb_css(
511 qtutils.css_color(self.main_color).lighter()
513 else:
514 palette = self.get_palette()
515 color = palette.color(QtGui.QPalette.Highlight)
516 highlight_rgb = qtutils.rgb_css(color)
517 return highlight_rgb
519 def selection_color(self):
520 """Return a color suitable for selections"""
521 highlight = qtutils.css_color(self.highlight_color_rgb())
522 if highlight.lightnessF() > 0.7: # Avoid clamping light colors to white.
523 color = highlight
524 else:
525 color = highlight.lighter()
526 return color
528 def text_colors_rgb(self):
529 """Return a pair of rgb(r,g,b) css color values for text and selected text"""
530 if self.text_color:
531 text_rgb = self.text_color
532 highlight_text_rgb = self.text_color
533 else:
534 palette = self.get_palette()
535 color = palette.text().color()
536 text_rgb = qtutils.rgb_css(color)
538 color = palette.highlightedText().color()
539 highlight_text_rgb = qtutils.rgb_css(color)
540 return text_rgb, highlight_text_rgb
542 def disabled_text_color_rgb(self):
543 """Return an rgb(r,g,b) css color value for the disabled text"""
544 if self.disabled_text_color:
545 disabled_text_rgb = self.disabled_text_color
546 else:
547 palette = self.get_palette()
548 color = palette.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
549 disabled_text_rgb = qtutils.rgb_css(color)
550 return disabled_text_rgb
552 def background_color_rgb(self):
553 """Return an rgb(r,g,b) css color value for the window background"""
554 if self.background_color:
555 background_color = self.background_color
556 else:
557 palette = self.get_palette()
558 window = palette.color(QtGui.QPalette.Base)
559 background_color = qtutils.rgb_css(window)
560 return background_color
563 def style_sheet_default(palette):
564 window = palette.color(QtGui.QPalette.Window)
565 highlight = palette.color(QtGui.QPalette.Highlight)
566 shadow = palette.color(QtGui.QPalette.Shadow)
567 base = palette.color(QtGui.QPalette.Base)
569 window_rgb = qtutils.rgb_css(window)
570 highlight_rgb = qtutils.rgb_css(highlight)
571 shadow_rgb = qtutils.rgb_css(shadow)
572 base_rgb = qtutils.rgb_css(base)
574 return """
575 QCheckBox::indicator {
576 width: %(checkbox_size)spx;
577 height: %(checkbox_size)spx;
579 QCheckBox::indicator::unchecked {
580 border: %(checkbox_border)spx solid %(shadow_rgb)s;
581 background: %(base_rgb)s;
583 QCheckBox::indicator::checked {
584 image: url(%(checkbox_icon)s);
585 border: %(checkbox_border)spx solid %(shadow_rgb)s;
586 background: %(base_rgb)s;
589 QRadioButton::indicator {
590 width: %(radio_size)spx;
591 height: %(radio_size)spx;
593 QRadioButton::indicator::unchecked {
594 border: %(radio_border)spx solid %(shadow_rgb)s;
595 border-radius: %(radio_radius)spx;
596 background: %(base_rgb)s;
598 QRadioButton::indicator::checked {
599 image: url(%(radio_icon)s);
600 border: %(radio_border)spx solid %(shadow_rgb)s;
601 border-radius: %(radio_radius)spx;
602 background: %(base_rgb)s;
605 QSplitter::handle:hover {
606 background: %(highlight_rgb)s;
609 QMainWindow::separator {
610 background: %(window_rgb)s;
611 width: %(separator)spx;
612 height: %(separator)spx;
614 QMainWindow::separator:hover {
615 background: %(highlight_rgb)s;
618 """ % dict(
619 separator=defs.separator,
620 window_rgb=window_rgb,
621 highlight_rgb=highlight_rgb,
622 shadow_rgb=shadow_rgb,
623 base_rgb=base_rgb,
624 checkbox_border=defs.border,
625 checkbox_icon=icons.check_name(),
626 checkbox_size=defs.checkbox,
627 radio_border=defs.radio_border,
628 radio_icon=icons.dot_name(),
629 radio_radius=defs.radio // 2,
630 radio_size=defs.radio,
634 def get_all_themes():
635 themes = [
636 Theme(
637 'default',
638 N_('Default'),
639 False,
640 style_sheet=EStylesheet.DEFAULT,
641 main_color=None,
643 Theme(
644 'flat-light-blue',
645 N_('Flat light blue'),
646 False,
647 style_sheet=EStylesheet.FLAT,
648 main_color='#5271cc',
650 Theme(
651 'flat-light-red',
652 N_('Flat light red'),
653 False,
654 style_sheet=EStylesheet.FLAT,
655 main_color='#cc5452',
657 Theme(
658 'flat-light-grey',
659 N_('Flat light grey'),
660 False,
661 style_sheet=EStylesheet.FLAT,
662 main_color='#707478',
664 Theme(
665 'flat-light-green',
666 N_('Flat light green'),
667 False,
668 style_sheet=EStylesheet.FLAT,
669 main_color='#42a65c',
671 Theme(
672 'flat-dark-blue',
673 N_('Flat dark blue'),
674 True,
675 style_sheet=EStylesheet.FLAT,
676 main_color='#5271cc',
678 Theme(
679 'flat-dark-red',
680 N_('Flat dark red'),
681 True,
682 style_sheet=EStylesheet.FLAT,
683 main_color='#cc5452'
685 Theme(
686 'flat-dark-grey',
687 N_('Flat dark grey'),
688 True,
689 style_sheet=EStylesheet.FLAT,
690 main_color='#aaaaaa',
692 Theme(
693 'flat-dark-green',
694 N_('Flat dark green'),
695 True,
696 style_sheet=EStylesheet.FLAT,
697 main_color='#42a65c',
701 # check if themes path exists in user folder
702 path = resources.config_home('themes')
703 if not os.path.isdir(path):
704 return themes
706 # Gather Qt .qss stylesheet themes
707 try:
708 filenames = core.listdir(path)
709 except OSError:
710 return themes
712 for filename in filenames:
713 name, ext = os.path.splitext(filename)
714 if ext == '.qss':
715 themes.append(Theme(name, N_(name), False, EStylesheet.CUSTOM, None))
717 return themes
720 def options():
721 """Return a dictionary mapping display names to theme names"""
722 items = get_all_themes()
723 return [(item.hr_name, item.name) for item in items]
726 def find_theme(name):
727 themes = get_all_themes()
728 for item in themes:
729 if item.name == name:
730 return item
731 return themes[0]