CHANGES: update the v4.0.2 release notes draft
[git-cola.git] / cola / themes.py
blobf499b1d4a0010a7926ecfcf8abbe192c39363e58
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.style_sheet = style_sheet
29 self.main_color = main_color
31 def build_style_sheet(self, app_palette):
32 if self.style_sheet == EStylesheet.CUSTOM:
33 return self.style_sheet_custom(app_palette)
34 elif self.style_sheet == EStylesheet.FLAT:
35 return self.style_sheet_flat()
36 else:
37 return self.style_sheet_default(app_palette)
39 def build_palette(self, app_palette):
40 QPalette = QtGui.QPalette
41 palette_dark = app_palette.color(QPalette.Base).lightnessF() < 0.5
43 if palette_dark and self.is_dark:
44 return app_palette
45 if not palette_dark and not self.is_dark:
46 return app_palette
47 if self.is_dark:
48 bg_color = QtGui.QColor('#202025')
49 else:
50 bg_color = QtGui.QColor('#edeef3')
52 txt_color = QtGui.QColor('#777')
53 palette = QPalette(bg_color)
54 palette.setColor(QPalette.Base, bg_color)
55 palette.setColor(QPalette.Disabled, QPalette.Text, txt_color)
56 return palette
58 @staticmethod
59 def style_sheet_default(palette):
60 window = palette.color(QtGui.QPalette.Window)
61 highlight = palette.color(QtGui.QPalette.Highlight)
62 shadow = palette.color(QtGui.QPalette.Shadow)
63 base = palette.color(QtGui.QPalette.Base)
65 window_rgb = qtutils.rgb_css(window)
66 highlight_rgb = qtutils.rgb_css(highlight)
67 shadow_rgb = qtutils.rgb_css(shadow)
68 base_rgb = qtutils.rgb_css(base)
70 return """
71 QCheckBox::indicator {
72 width: %(checkbox_size)spx;
73 height: %(checkbox_size)spx;
75 QCheckBox::indicator::unchecked {
76 border: %(checkbox_border)spx solid %(shadow_rgb)s;
77 background: %(base_rgb)s;
79 QCheckBox::indicator::checked {
80 image: url(%(checkbox_icon)s);
81 border: %(checkbox_border)spx solid %(shadow_rgb)s;
82 background: %(base_rgb)s;
85 QRadioButton::indicator {
86 width: %(radio_size)spx;
87 height: %(radio_size)spx;
89 QRadioButton::indicator::unchecked {
90 border: %(radio_border)spx solid %(shadow_rgb)s;
91 border-radius: %(radio_radius)spx;
92 background: %(base_rgb)s;
94 QRadioButton::indicator::checked {
95 image: url(%(radio_icon)s);
96 border: %(radio_border)spx solid %(shadow_rgb)s;
97 border-radius: %(radio_radius)spx;
98 background: %(base_rgb)s;
101 QSplitter::handle:hover {
102 background: %(highlight_rgb)s;
105 QMainWindow::separator {
106 background: %(window_rgb)s;
107 width: %(separator)spx;
108 height: %(separator)spx;
110 QMainWindow::separator:hover {
111 background: %(highlight_rgb)s;
114 """ % dict(
115 separator=defs.separator,
116 window_rgb=window_rgb,
117 highlight_rgb=highlight_rgb,
118 shadow_rgb=shadow_rgb,
119 base_rgb=base_rgb,
120 checkbox_border=defs.border,
121 checkbox_icon=icons.check_name(),
122 checkbox_size=defs.checkbox,
123 radio_border=defs.radio_border,
124 radio_icon=icons.dot_name(),
125 radio_radius=defs.radio // 2,
126 radio_size=defs.radio,
129 def style_sheet_flat(self):
130 main_color = self.main_color
131 color = QtGui.QColor(main_color)
132 color_rgb = qtutils.rgb_css(color)
134 if self.is_dark:
135 background = '#2e2f30'
136 field = '#383a3c'
137 grayed = '#06080a'
138 button_text = '#000000'
139 field_text = '#d0d0d0'
140 darker = qtutils.hsl_css(
141 color.hslHueF(), color.hslSaturationF() * 0.3, color.lightnessF() * 1.3
143 lighter = qtutils.hsl_css(
144 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.6
146 focus = qtutils.hsl_css(
147 color.hslHueF(), color.hslSaturationF() * 0.7, color.lightnessF() * 0.7
149 else:
150 background = '#edeef3'
151 field = '#ffffff'
152 grayed = '#a2a2b0'
153 button_text = '#ffffff'
154 field_text = '#000000'
155 darker = qtutils.hsl_css(
156 color.hslHueF(), color.hslSaturationF(), color.lightnessF() * 0.4
158 lighter = qtutils.hsl_css(color.hslHueF(), color.hslSaturationF() * 2, 0.92)
159 focus = color_rgb
161 return """
162 /* regular widgets */
164 background-color: %(background)s;
165 color: %(field_text)s;
166 selection-background-color: %(lighter)s;
167 alternate-background-color: %(field)s;
168 selection-color: %(field_text)s;
169 show-decoration-selected: 1;
170 spacing: 2px;
173 /* Focused widths get a thin border */
174 QTreeView:focus, QListView:focus,
175 QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
176 border-width: 1px;
177 border-style: solid;
178 border-color: %(focus)s;
181 QWidget:disabled {
182 border-color: %(grayed)s;
183 color: %(grayed)s;
185 QDockWidget > QFrame {
186 margin: 0 2px 2px 2px;
187 min-height: 40px;
189 QPlainTextEdit, QLineEdit, QTextEdit, QAbstractItemView,
190 QAbstractSpinBox {
191 background-color: %(field)s;
192 border-color: %(grayed)s;
193 border-style: solid;
194 border-width: 1px;
196 QAbstractItemView::item:selected {
197 background-color: %(lighter)s;
199 QAbstractItemView::item:hover {
200 background-color: %(lighter)s;
202 QLabel {
203 color: %(darker)s;
204 background-color: transparent;
206 DockTitleBarWidget {
207 padding-bottom: 4px;
210 /* buttons */
211 QPushButton[flat="false"] {
212 background-color: %(button)s;
213 color: %(button_text)s;
214 border-radius: 2px;
215 border-width: 0;
216 margin-bottom: 1px;
217 min-width: 55px;
218 padding: 4px 5px;
220 QPushButton[flat="true"], QToolButton {
221 background-color: transparent;
222 border-radius: 0px;
224 QPushButton[flat="true"] {
225 margin-bottom: 10px;
227 QPushButton:hover, QToolButton:hover {
228 background-color: %(darker)s;
230 QPushButton[flat="false"]:pressed, QToolButton:pressed {
231 background-color: %(darker)s;
232 margin: 1px 1px 2px 1px;
234 QPushButton:disabled {
235 background-color: %(grayed)s;
236 color: %(field)s;
237 padding-left: 5px;
238 padding-top: 5px;
240 QPushButton[flat="true"]:disabled {
241 background-color: transparent;
244 /*menus*/
245 QMenuBar {
246 background-color: %(background)s;
247 color: %(field_text)s;
248 border-width: 0;
249 padding: 1px;
251 QMenuBar::item {
252 background: transparent;
254 QMenuBar::item:selected {
255 background: %(button)s;
257 QMenuBar::item:pressed {
258 background: %(button)s;
260 QMenu {
261 background-color: %(field)s;
263 QMenu::separator {
264 background: %(background)s;
265 height: 1px;
268 /* combo box */
269 QComboBox {
270 background-color: %(field)s;
271 border-color: %(grayed)s;
272 border-style: solid;
273 color: %(field_text)s;
274 border-radius: 0px;
275 border-width: 1px;
276 margin-bottom: 1px;
277 padding: 0 5px;
279 QComboBox::drop-down {
280 border-color: %(field_text)s %(field)s %(field)s %(field)s;
281 border-style: solid;
282 subcontrol-position: right;
283 border-width: 4px 3px 0 3px;
284 height: 0;
285 margin-right: 5px;
286 width: 0;
288 QComboBox::drop-down:hover {
289 border-color: %(button)s %(field)s %(field)s %(field)s;
291 QComboBox:item {
292 background-color: %(button)s;
293 color: %(button_text)s;
294 border-width: 0;
295 height: 22px;
297 QComboBox:item:selected {
298 background-color: %(darker)s;
299 color: %(button_text)s;
301 QComboBox:item:checked {
302 background-color: %(darker)s;
303 color: %(button_text)s;
306 /* MainWindow separator */
307 QMainWindow::separator {
308 width: %(separator)spx;
309 height: %(separator)spx;
311 QMainWindow::separator:hover {
312 background: %(focus)s;
315 /* scroll bar */
316 QScrollBar {
317 background-color: %(field)s;
318 border: 0;
320 QScrollBar::handle {
321 background: %(background)s
323 QScrollBar::handle:hover {
324 background: %(button)s
326 QScrollBar:horizontal {
327 margin: 0 11px 0 11px;
328 height: 10px;
330 QScrollBar:vertical {
331 margin: 11px 0 11px 0;
332 width: 10px;
334 QScrollBar::add-line, QScrollBar::sub-line {
335 background: %(background)s;
336 subcontrol-origin: margin;
338 QScrollBar::sub-line:horizontal { /*required by a buggy Qt version*/
339 subcontrol-position: left;
341 QScrollBar::add-line:hover, QScrollBar::sub-line:hover {
342 background: %(button)s;
344 QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
345 width: 10px;
347 QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
348 height: 10px;
350 QScrollBar:left-arrow, QScrollBar::right-arrow,
351 QScrollBar::up-arrow, QScrollBar::down-arrow {
352 border-style: solid;
353 height: 0;
354 width: 0;
356 QScrollBar:right-arrow {
357 border-color: %(background)s %(background)s
358 %(background)s %(darker)s;
359 border-width: 3px 0 3px 4px;
361 QScrollBar:left-arrow {
362 border-color: %(background)s %(darker)s
363 %(background)s %(background)s;
364 border-width: 3px 4px 3px 0;
366 QScrollBar:up-arrow {
367 border-color: %(background)s %(background)s
368 %(darker)s %(background)s;
369 border-width: 0 3px 4px 3px;
371 QScrollBar:down-arrow {
372 border-color: %(darker)s %(background)s
373 %(background)s %(background)s;
374 border-width: 4px 3px 0 3px;
376 QScrollBar:right-arrow:hover {
377 border-color: %(button)s %(button)s
378 %(button)s %(darker)s;
380 QScrollBar:left-arrow:hover {
381 border-color: %(button)s %(darker)s
382 %(button)s %(button)s;
384 QScrollBar:up-arrow:hover {
385 border-color: %(button)s %(button)s
386 %(darker)s %(button)s;
388 QScrollBar:down-arrow:hover {
389 border-color: %(darker)s %(button)s
390 %(button)s %(button)s;
393 /* tab bar (stacked & docked widgets) */
394 QTabBar::tab {
395 background: transparent;
396 border-color: %(darker)s;
397 border-width: 1px;
398 margin: 1px;
399 padding: 3px 5px;
401 QTabBar::tab:selected {
402 background: %(grayed)s;
405 /* check box */
406 QCheckBox {
407 spacing: 8px;
408 margin: 4px;
409 background-color: transparent;
411 QCheckBox::indicator {
412 background-color: %(field)s;
413 border-color: %(darker)s;
414 border-style: solid;
415 subcontrol-position: left;
416 border-width: 1px;
417 height: 13px;
418 width: 13px;
420 QCheckBox::indicator:unchecked:hover {
421 background-color: %(button)s;
423 QCheckBox::indicator:unchecked:pressed {
424 background-color: %(darker)s;
426 QCheckBox::indicator:checked {
427 background-color: %(darker)s;
429 QCheckBox::indicator:checked:hover {
430 background-color: %(button)s;
432 QCheckBox::indicator:checked:pressed {
433 background-color: %(field)s;
436 /* radio checkbox */
437 QRadioButton {
438 spacing: 8px;
439 margin: 4px;
441 QRadioButton::indicator {
442 height: 0.75em;
443 width: 0.75em;
446 /* progress bar */
447 QProgressBar {
448 background-color: %(field)s;
449 border: 1px solid %(darker)s;
451 QProgressBar::chunk {
452 background-color: %(button)s;
453 width: 1px;
456 /* spin box */
457 QAbstractSpinBox::up-button, QAbstractSpinBox::down-button {
458 background-color: transparent;
460 QAbstractSpinBox::up-arrow, QAbstractSpinBox::down-arrow {
461 border-style: solid;
462 height: 0;
463 width: 0;
465 QAbstractSpinBox::up-arrow {
466 border-color: %(field)s %(field)s %(darker)s %(field)s;
467 border-width: 0 3px 4px 3px;
469 QAbstractSpinBox::up-arrow:hover {
470 border-color: %(field)s %(field)s %(button)s %(field)s;
471 border-width: 0 3px 4px 3px;
473 QAbstractSpinBox::down-arrow {
474 border-color: %(darker)s %(field)s %(field)s %(field)s;
475 border-width: 4px 3px 0 3px;
477 QAbstractSpinBox::down-arrow:hover {
478 border-color: %(button)s %(field)s %(field)s %(field)s;
479 border-width: 4px 3px 0 3px;
482 /* dialogs */
483 QDialog > QFrame {
484 margin: 2px 2px 2px 2px;
487 /* headers */
488 QHeaderView {
489 color: %(field_text)s;
490 border-style: solid;
491 border-width: 0 0 1px 0;
492 border-color: %(grayed)s;
494 QHeaderView::section {
495 border-style: solid;
496 border-right: 1px solid %(grayed)s;
497 background-color: %(background)s;
498 color: %(field_text)s;
499 padding-left: 4px;
502 /* headers */
503 QHeaderView {
504 color: %(field_text)s;
505 border-style: solid;
506 border-width: 0 0 1px 0;
507 border-color: %(grayed)s;
509 QHeaderView::section {
510 border-style: solid;
511 border-right: 1px solid %(grayed)s;
512 background-color: %(background)s;
513 color: %(field_text)s;
514 padding-left: 4px;
517 """ % dict(
518 background=background,
519 field=field,
520 button=color_rgb,
521 darker=darker,
522 lighter=lighter,
523 grayed=grayed,
524 button_text=button_text,
525 field_text=field_text,
526 separator=defs.separator,
527 focus=focus,
530 def style_sheet_custom(self, app_palette):
531 """Get custom style sheet.
532 File name is saved in variable self.name.
533 If user has deleted file, use default style"""
535 # check if path exists
536 filename = resources.config_home('themes', self.name + '.qss')
537 if not core.exists(filename):
538 return self.style_sheet_default(app_palette)
539 try:
540 return core.read(filename)
541 except (IOError, OSError) as err:
542 core.print_stderr(
543 'warning: unable to read custom theme %s: %s' % (filename, err)
545 return self.style_sheet_default(app_palette)
548 def get_all_themes():
549 themes = [
550 Theme('default', N_('Default'), False, EStylesheet.DEFAULT, None),
551 Theme(
552 'flat-light-blue', N_('Flat light blue'), False, EStylesheet.FLAT, '#5271cc'
554 Theme(
555 'flat-light-red', N_('Flat light red'), False, EStylesheet.FLAT, '#cc5452'
557 Theme(
558 'flat-light-grey', N_('Flat light grey'), False, EStylesheet.FLAT, '#707478'
560 Theme(
561 'flat-light-green',
562 N_('Flat light green'),
563 False,
564 EStylesheet.FLAT,
565 '#42a65c',
567 Theme(
568 'flat-dark-blue', N_('Flat dark blue'), True, EStylesheet.FLAT, '#5271cc'
570 Theme('flat-dark-red', N_('Flat dark red'), True, EStylesheet.FLAT, '#cc5452'),
571 Theme(
572 'flat-dark-grey', N_('Flat dark grey'), True, EStylesheet.FLAT, '#aaaaaa'
574 Theme(
575 'flat-dark-green', N_('Flat dark green'), True, EStylesheet.FLAT, '#42a65c'
579 # check if themes path exists in user folder
580 path = resources.config_home('themes')
581 if not os.path.isdir(path):
582 return themes
584 # Gather Qt .qss stylesheet themes
585 try:
586 filenames = core.listdir(path)
587 except OSError:
588 return themes
590 for filename in filenames:
591 name, ext = os.path.splitext(filename)
592 if ext == '.qss':
593 themes.append(Theme(name, N_(name), False, EStylesheet.CUSTOM, None))
595 return themes
598 def options():
599 """Return a dictionary mapping display names to theme names"""
600 items = get_all_themes()
601 return [(item.hr_name, item.name) for item in items]
604 def find_theme(name):
605 themes = get_all_themes()
606 for item in themes:
607 if item.name == name:
608 return item
609 return themes[0]