UI for customizing palettes

Needs integration in to calibre preferences
This commit is contained in:
Kovid Goyal 2024-02-04 22:24:42 +05:30
parent 085862b062
commit b47e0d5125
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 267 additions and 11 deletions

View File

@ -427,6 +427,10 @@ def create_defs():
defs['show_notes_in_tag_browser'] = False defs['show_notes_in_tag_browser'] = False
defs['icons_on_right_in_tag_browser'] = True defs['icons_on_right_in_tag_browser'] = True
defs['cover_browser_narrow_view_position'] = 'automatic' defs['cover_browser_narrow_view_position'] = 'automatic'
defs['dark_palette_name'] = ''
defs['light_palette_name'] = ''
defs['dark_palettes'] = {}
defs['light_palettes'] = {}
def migrate_tweak(tweak_name, pref_name): def migrate_tweak(tweak_name, pref_name):
# If the tweak has been changed then leave the tweak in the file so # If the tweak has been changed then leave the tweak in the file so

View File

@ -0,0 +1,191 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
from qt.core import (
QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, QLabel, QPalette, QScrollArea,
QSize, QSizePolicy, QTabWidget, QVBoxLayout, QWidget, pyqtSignal,
)
from calibre.gui2 import Application, gprefs
from calibre.gui2.palette import (
default_dark_palette, default_light_palette, palette_colors, palette_from_dict,
)
from calibre.gui2.widgets2 import ColorButton, Dialog
class Color(QWidget):
changed = pyqtSignal()
def __init__(self, key: str, desc: str, parent: 'PaletteColors', palette: QPalette, default_palette: QPalette, mode_name: str, group=''):
super().__init__(parent)
self.key = key
self.setting_key = (key + '-' + group) if group else key
self.mode_name = mode_name
self.default_palette = default_palette
self.color_key = QPalette.ColorGroup.Disabled if group == 'disabled' else QPalette.ColorGroup.Active, getattr(QPalette.ColorRole, key)
self.initial_color = palette.color(*self.color_key)
self.l = l = QHBoxLayout(self)
self.button = b = ColorButton(self.initial_color.name(), self)
b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
b.color_changed.connect(self.color_changed)
l.addWidget(b)
self.la = la = QLabel(desc)
la.setBuddy(b)
l.addWidget(la)
def restore_defaults(self):
self.button.color = self.default_palette.color(*self.color_key)
def color_changed(self):
self.changed.emit()
self.la.setStyleSheet('QLabel { font-style: italic }')
@property
def value(self):
ans = self.button.color
if ans != self.default_palette.color(*self.color_key):
return ans
class PaletteColors(QWidget):
def __init__(self, palette: QPalette, default_palette: QPalette, mode_name: str, parent=None):
super().__init__(parent)
self.link_colors = {}
self.mode_name = mode_name
self.foreground_colors = {}
self.background_colors = {}
self.default_palette = default_palette
for key, desc in palette_colors().items():
if 'Text' in key:
self.foreground_colors[key] = desc
elif 'Link' in key:
self.link_colors[key] = desc
else:
self.background_colors[key] = desc
self.l = l = QVBoxLayout(self)
self.colors = []
def header(text):
ans = QLabel(text)
f = ans.font()
f.setBold(True)
ans.setFont(f)
return ans
def c(x, desc):
w = Color(x, desc, self, palette, default_palette, mode_name)
l.addWidget(w)
self.colors.append(w)
l.addWidget(header(_('Background colors')))
for x, desc in self.background_colors.items():
c(x, desc)
l.addWidget(header(_('Foreground (text) colors')))
for x, desc in self.foreground_colors.items():
c(x, desc)
l.addWidget(header(_('Foreground (text) colors when disabled')))
for x, desc in self.foreground_colors.items():
c(x, desc)
l.addWidget(header(_('Link colors')))
for x, desc in self.link_colors.items():
c(x, desc)
@property
def value(self):
ans = {}
for w in self.colors:
v = w.value
if v is not None:
ans[w.setting_key] = w.value
return ans
def restore_defaults(self):
for w in self.colors:
w.restore_defaults()
class PaletteWidget(QWidget):
def __init__(self, mode_name='light', parent=None):
super().__init__(parent)
self.mode_name = mode_name
self.mode_title = {'dark': _('dark'), 'light': _('light')}[mode_name]
self.l = l = QVBoxLayout(self)
self.la = la = QLabel(_('These colors will be used for the calibre interface when calibre is in "{}" mode').format(self.mode_title))
l.addWidget(la)
la.setWordWrap(True)
self.use_custom = uc = QCheckBox(_('Use a &custom color scheme'))
uc.setChecked(bool(gprefs[f'{mode_name}_palette_name']))
l.addWidget(uc)
uc.toggled.connect(self.use_custom_toggled)
pdata = gprefs[f'{mode_name}_palettes'].get('__current__', {})
default_palette = default_dark_palette() if mode_name == 'dark' else default_light_palette()
palette = palette_from_dict(pdata, default_palette)
self.sa = sa = QScrollArea(self)
l.addWidget(sa)
self.palette_colors = pc = PaletteColors(palette, default_palette, mode_name, self)
sa.setWidget(pc)
self.use_custom_toggled()
def sizeHint(self):
return QSize(800, 600)
def use_custom_toggled(self):
self.palette_colors.setEnabled(self.use_custom.isChecked())
def apply_settings(self):
val = self.palette_colors.value
v = gprefs[f'{self.mode_name}_palettes']
v['__current__'] = val
gprefs[f'{self.mode_name}_palettes'] = v
gprefs[f'{self.mode_name}_palette_name'] = '__current__' if self.use_custom.isChecked() else ''
def restore_defaults(self):
self.use_custom.setChecked(False)
self.palette_colors.restore_defaults()
class PaletteConfig(Dialog):
def __init__(self, parent=None):
super().__init__(_('Customize the colors used by calibre'), 'customize-palette', parent=parent)
def setup_ui(self):
self.l = l = QVBoxLayout(self)
self.tabs = tabs = QTabWidget(self)
l.addWidget(tabs)
self.light_tab = lt = PaletteWidget(parent=self)
tabs.addTab(lt, _('&Light mode colors'))
self.dark_tab = dt = PaletteWidget('dark', parent=self)
tabs.addTab(dt, _('&Dark mode colors'))
l.addWidget(self.bb)
b = self.bb.addButton(_('Restore &defaults'), QDialogButtonBox.ButtonRole.ActionRole)
b.clicked.connect(self.restore_defaults)
def apply_settings(self):
with gprefs:
self.light_tab.apply_settings()
self.dark_tab.apply_settings()
def restore_defaults(self):
self.light_tab.restore_defaults()
self.dark_tab.restore_defaults()
if __name__ == '__main__':
app = Application([])
d = PaletteConfig()
if d.exec() == QDialog.DialogCode.Accepted:
d.apply_settings()
del d
del app

View File

@ -3,10 +3,11 @@
import os import os
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager, suppress
from functools import lru_cache
from qt.core import ( from qt.core import (
QApplication, QByteArray, QColor, QDataStream, QIcon, QIODeviceBase, QObject, QApplication, QByteArray, QColor, QDataStream, QIcon, QIODeviceBase, QObject,
QPalette, QProxyStyle, QStyle, Qt QPalette, QProxyStyle, QStyle, Qt,
) )
from calibre.constants import DEBUG, dark_link_color, ismacos, iswindows from calibre.constants import DEBUG, dark_link_color, ismacos, iswindows
@ -64,7 +65,7 @@ QPalette.serialize_as_python = serialize_palette_as_python
QPalette.unserialize_from_bytes = unserialize_palette QPalette.unserialize_from_bytes = unserialize_palette
def dark_palette(): def default_dark_palette():
p = QPalette() p = QPalette()
disabled_color = QColor(127,127,127) disabled_color = QColor(127,127,127)
p.setColor(QPalette.ColorRole.Window, dark_color) p.setColor(QPalette.ColorRole.Window, dark_color)
@ -75,22 +76,22 @@ def dark_palette():
p.setColor(QPalette.ColorRole.ToolTipBase, dark_color) p.setColor(QPalette.ColorRole.ToolTipBase, dark_color)
p.setColor(QPalette.ColorRole.ToolTipText, dark_text_color) p.setColor(QPalette.ColorRole.ToolTipText, dark_text_color)
p.setColor(QPalette.ColorRole.Text, dark_text_color) p.setColor(QPalette.ColorRole.Text, dark_text_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color)
p.setColor(QPalette.ColorRole.Button, dark_color) p.setColor(QPalette.ColorRole.Button, dark_color)
p.setColor(QPalette.ColorRole.ButtonText, dark_text_color) p.setColor(QPalette.ColorRole.ButtonText, dark_text_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color)
p.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red) p.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
p.setColor(QPalette.ColorRole.Link, dark_link_color) p.setColor(QPalette.ColorRole.Link, dark_link_color)
p.setColor(QPalette.ColorRole.LinkVisited, Qt.GlobalColor.darkMagenta) p.setColor(QPalette.ColorRole.LinkVisited, Qt.GlobalColor.darkMagenta)
p.setColor(QPalette.ColorRole.Highlight, QColor(0x0b, 0x45, 0xc4)) p.setColor(QPalette.ColorRole.Highlight, QColor(0x0b, 0x45, 0xc4))
p.setColor(QPalette.ColorRole.HighlightedText, dark_text_color) p.setColor(QPalette.ColorRole.HighlightedText, dark_text_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, disabled_color) p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, disabled_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color)
return p return p
def light_palette(): # {{{ def default_light_palette():
p = QPalette() p = QPalette()
disabled_color = QColor(120,120,120) disabled_color = QColor(120,120,120)
p.setColor(QPalette.ColorRole.Window, light_color) p.setColor(QPalette.ColorRole.Window, light_color)
@ -101,21 +102,81 @@ def light_palette(): # {{{
p.setColor(QPalette.ColorRole.ToolTipBase, light_color) p.setColor(QPalette.ColorRole.ToolTipBase, light_color)
p.setColor(QPalette.ColorRole.ToolTipText, light_text_color) p.setColor(QPalette.ColorRole.ToolTipText, light_text_color)
p.setColor(QPalette.ColorRole.Text, light_text_color) p.setColor(QPalette.ColorRole.Text, light_text_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color)
p.setColor(QPalette.ColorRole.Button, light_color) p.setColor(QPalette.ColorRole.Button, light_color)
p.setColor(QPalette.ColorRole.ButtonText, light_text_color) p.setColor(QPalette.ColorRole.ButtonText, light_text_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color)
p.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red) p.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
p.setColor(QPalette.ColorRole.Link, light_link_color) p.setColor(QPalette.ColorRole.Link, light_link_color)
p.setColor(QPalette.ColorRole.LinkVisited, Qt.GlobalColor.magenta) p.setColor(QPalette.ColorRole.LinkVisited, Qt.GlobalColor.magenta)
p.setColor(QPalette.ColorRole.Highlight, QColor(48, 140, 198)) p.setColor(QPalette.ColorRole.Highlight, QColor(48, 140, 198))
p.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.white) p.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.white)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color)
p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, disabled_color) p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, disabled_color)
return p return p
# }}}
@lru_cache
def palette_colors():
return {
'WindowText': _('A general foreground color'),
'Text': _('The foreground color for text input widgets'),
'ButtonText': _('The foreground color for buttons'),
'PlaceholderText': _('Placeholder text in text input widgets'),
'ToolTipText': _('The foreground color for tool tips'),
'BrightText': _('A "bright" text color'),
'HighlightedText': _('The foreground color for highlighted items'),
'Window': _('A general background color'),
'Base': _('The background color for text input widgets'),
'Button': _('The background color for buttons'),
'AlternateBase': _('The background color for alternate rows in tables and lists'),
'ToolTipBase': _('The background color for tool tips'),
'Highlight': _('The background color for highlighted items'),
'Link': _('The color for links'),
'LinkVisited': _('The color for visited links'),
}
def palette_from_dict(data: dict[str, str], default_palette: QPalette) -> QPalette:
def s(key, group=QPalette.ColorGroup.All):
role = getattr(QPalette.ColorRole, key)
grp = ''
if group == QPalette.ColorGroup.Disabled:
grp = 'disabled-'
c = QColor.fromString(data.get(grp + key, ''))
if c.isValid():
p.setColor(group, role, c)
p = QPalette()
for key in palette_colors():
s(key)
for key in ('Text', 'ButtonText', 'HighlightedText'):
s(key, QPalette.ColorGroup.Disabled)
return p.resolve(default_palette)
def dark_palette():
from calibre.gui2 import gprefs
ans = default_dark_palette()
if gprefs['dark_palette_name']:
pdata = gprefs['dark_palettes'].get(gprefs['dark_palette_name'])
with suppress(Exception):
return palette_from_dict(pdata, ans)
return ans
def light_palette():
from calibre.gui2 import gprefs
ans = default_light_palette()
if gprefs['light_palette_name']:
pdata = gprefs['light_palettes'].get(gprefs['light_palette_name'])
with suppress(Exception):
return palette_from_dict(pdata, ans)
return ans
standard_pixmaps = { # {{{ standard_pixmaps = { # {{{