mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Cover grid: Allow configuring different backgrounds for light and dark mode in Preferences->Look & feel->Cover grid
This commit is contained in:
parent
04f7f6ca72
commit
3937e2bfea
@ -426,11 +426,11 @@ def create_defs():
|
||||
defs['cover_grid_width'] = 0
|
||||
defs['cover_grid_height'] = 0
|
||||
defs['cover_grid_spacing'] = 0
|
||||
defs['cover_grid_color'] = (80, 80, 80)
|
||||
defs['cover_grid_background'] = {
|
||||
'migrated': False, 'light': (80, 80, 80), 'dark': (45, 45, 45), 'light_texture': None, 'dark_texture': None}
|
||||
defs['cover_grid_cache_size_multiple'] = 5
|
||||
defs['cover_grid_disk_cache_size'] = 2500
|
||||
defs['cover_grid_show_title'] = False
|
||||
defs['cover_grid_texture'] = None
|
||||
defs['cover_corner_radius'] = 0
|
||||
defs['cover_corner_radius_unit'] = 'px'
|
||||
defs['show_vl_tabs'] = False
|
||||
@ -1805,3 +1805,26 @@ def clip_border_radius(painter, rect):
|
||||
yield
|
||||
finally:
|
||||
painter.restore()
|
||||
|
||||
|
||||
def resolve_grid_color(which='color', for_dark: bool | None = None, use_defaults: bool = False):
|
||||
if use_defaults:
|
||||
s = gprefs.defaults['cover_grid_background']
|
||||
else:
|
||||
s = gprefs['cover_grid_background']
|
||||
if not s['migrated']:
|
||||
s = s.copy()
|
||||
s['migrated'] = True
|
||||
legacy = gprefs.pop('cover_grid_color', None)
|
||||
if legacy is not None and tuple(legacy) != (80, 80, 80):
|
||||
s['light'] = s['dark'] = legacy
|
||||
legacy = gprefs.pop('cover_grid_texture', None)
|
||||
if legacy is not None:
|
||||
s['light_texture'] = s['dark_texture'] = legacy
|
||||
gprefs['cover_grid_background'] = s
|
||||
if for_dark is None:
|
||||
for_dark = QApplication.instance().is_dark_theme
|
||||
key = 'dark' if for_dark else 'light'
|
||||
if which == 'color':
|
||||
return s[key]
|
||||
return s[f'{key}_texture']
|
||||
|
@ -59,7 +59,7 @@ from qt.core import (
|
||||
from calibre import fit_image, human_readable, prepare_string_for_xml
|
||||
from calibre.constants import DEBUG, config_dir, islinux
|
||||
from calibre.ebooks.metadata import fmt_sidx, rating_to_stars
|
||||
from calibre.gui2 import clip_border_radius, config, empty_index, gprefs, rating_font
|
||||
from calibre.gui2 import clip_border_radius, config, empty_index, gprefs, rating_font, resolve_grid_color
|
||||
from calibre.gui2.dnd import path_from_qurl
|
||||
from calibre.gui2.gestures import GestureManager
|
||||
from calibre.gui2.library.caches import CoverCache, ThumbnailCache
|
||||
@ -794,6 +794,7 @@ class GridView(QListView):
|
||||
self.setItemDelegate(self.delegate)
|
||||
self.setSpacing(self.delegate.spacing)
|
||||
self.set_color()
|
||||
QApplication.instance().palette_changed.connect(self.set_color)
|
||||
self.ignore_render_requests = Event()
|
||||
dpr = self.device_pixel_ratio
|
||||
# Up the version number if anything changes in how images are stored in
|
||||
@ -880,8 +881,8 @@ class GridView(QListView):
|
||||
self.update(idx)
|
||||
|
||||
def set_color(self):
|
||||
r, g, b = gprefs['cover_grid_color']
|
||||
tex = gprefs['cover_grid_texture']
|
||||
r, g, b = resolve_grid_color()
|
||||
tex = resolve_grid_color(which='texture')
|
||||
pal = self.palette()
|
||||
bgcol = QColor(r, g, b)
|
||||
pal.setColor(QPalette.ColorRole.Base, bgcol)
|
||||
|
@ -365,18 +365,25 @@ class LazyConfigWidgetBase(ConfigWidgetBase):
|
||||
super().__init__(parent)
|
||||
self.lazy_init_called = False
|
||||
|
||||
def ensure_lazy_initialized(self):
|
||||
if not self.lazy_init_called:
|
||||
if hasattr(self, 'lazy_initialize'):
|
||||
self.lazy_initialize()
|
||||
self.lazy_init_called = True
|
||||
|
||||
def set_changed_signal(self, changed_signal):
|
||||
self.changed_signal.connect(changed_signal)
|
||||
|
||||
def restore_defaults(self):
|
||||
self.ensure_lazy_initialized()
|
||||
super().restore_defaults()
|
||||
|
||||
def showEvent(self, event):
|
||||
# called when the widget is actually displays. We can't do something like
|
||||
# lazy_genesis because Qt does "things" before showEvent() is called. In
|
||||
# particular, the register function doesn't work with combo boxes if
|
||||
# genesis isn't called before everything else. Why is a mystery.
|
||||
if not self.lazy_init_called:
|
||||
if hasattr(self, 'lazy_initialize'):
|
||||
self.lazy_initialize()
|
||||
self.lazy_init_called = True
|
||||
self.ensure_lazy_initialized()
|
||||
super().showEvent(event)
|
||||
|
||||
|
||||
@ -407,7 +414,7 @@ def init_gui():
|
||||
|
||||
|
||||
def show_config_widget(category, name, gui=None, show_restart_msg=False,
|
||||
parent=None, never_shutdown=False):
|
||||
parent=None, never_shutdown=False, callback=None):
|
||||
'''
|
||||
Show the preferences plugin identified by category and name
|
||||
|
||||
@ -457,6 +464,8 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
|
||||
w.initialize()
|
||||
w.do_on_child_tabs('initialize')
|
||||
d.restore_geometry(gprefs, conf_name)
|
||||
if callback is not None:
|
||||
callback(w)
|
||||
d.exec()
|
||||
d.save_geometry(gprefs, conf_name)
|
||||
rr = getattr(d, 'restart_required', False)
|
||||
@ -521,8 +530,8 @@ class TableWidgetWithMoveByKeyPress(QTableWidget):
|
||||
|
||||
# Testing {{{
|
||||
|
||||
def test_widget(category, name, gui=None):
|
||||
show_config_widget(category, name, gui=gui, show_restart_msg=True)
|
||||
def test_widget(category, name, gui=None, callback=None):
|
||||
show_config_widget(category, name, gui=gui, show_restart_msg=True, callback=callback)
|
||||
|
||||
|
||||
def test_all():
|
||||
|
@ -36,6 +36,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
def restore_defaults(self):
|
||||
ConfigWidgetBase.restore_defaults(self)
|
||||
for w in self.tabWidget.all_widgets:
|
||||
if hasattr(w, 'restore_defaults'):
|
||||
w.restore_defaults()
|
||||
self.changed_signal.emit()
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
|
@ -7,10 +7,26 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from threading import Thread
|
||||
|
||||
from qt.core import QBrush, QColor, QColorDialog, QDialog, QPainter, QPixmap, QPushButton, QSize, QSizePolicy, Qt, QTabWidget, QWidget, pyqtSignal
|
||||
from qt.core import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QColorDialog,
|
||||
QDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPainter,
|
||||
QPixmap,
|
||||
QPushButton,
|
||||
QSize,
|
||||
QSizePolicy,
|
||||
Qt,
|
||||
QTabWidget,
|
||||
QWidget,
|
||||
pyqtSignal,
|
||||
)
|
||||
|
||||
from calibre import human_readable
|
||||
from calibre.gui2 import gprefs, open_local_file, question_dialog
|
||||
from calibre.gui2 import gprefs, open_local_file, question_dialog, resolve_grid_color
|
||||
from calibre.gui2.library.alternate_views import CM_TO_INCH, auto_height
|
||||
from calibre.gui2.preferences import LazyConfigWidgetBase
|
||||
from calibre.gui2.preferences.look_feel_tabs.cover_grid_ui import Ui_Form
|
||||
@ -21,35 +37,128 @@ from calibre.utils.icu import sort_key
|
||||
|
||||
class Background(QWidget):
|
||||
|
||||
changed_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
self.bcol = QColor(*gprefs['cover_grid_color'])
|
||||
self.btex = gprefs['cover_grid_texture']
|
||||
self.update_brush()
|
||||
self.l = QHBoxLayout(self)
|
||||
self.l.setContentsMargins(0, 0, 0, 0)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.initialized = False
|
||||
self.changed_signal.connect(self.update_brush)
|
||||
|
||||
def load_from_gprefs(self, use_defaults=False):
|
||||
self.bcol_dark = QColor(*resolve_grid_color(for_dark=True, use_defaults=use_defaults))
|
||||
self.bcol_light = QColor(*resolve_grid_color(for_dark=False, use_defaults=use_defaults))
|
||||
self.btex_dark = resolve_grid_color('texture', for_dark=True, use_defaults=use_defaults)
|
||||
self.btex_light = resolve_grid_color('texture', for_dark=False, use_defaults=use_defaults)
|
||||
self.update_brush()
|
||||
|
||||
def lazy_initialize(self):
|
||||
if self.initialized:
|
||||
return
|
||||
self.initialized = True
|
||||
l = self.layout()
|
||||
text = (
|
||||
'<p style="text-align: center; color: {}"><b>{}</b><br>'
|
||||
'<a style="text-decoration: none" href="la://color.me">{}</a><br>'
|
||||
'<a style="text-decoration: none" href="la://texture.me">{}</a></p>')
|
||||
self.light_label = la = QLabel(text.format('black', _('Light'), _('Change color'), _('Change texture')))
|
||||
la.linkActivated.connect(self.light_link_activated)
|
||||
l.addWidget(la)
|
||||
self.dark_label = la = QLabel(text.format('white', _('Dark'), _('Change color'), _('Change texture')))
|
||||
la.linkActivated.connect(self.dark_link_activated)
|
||||
l.addWidget(la)
|
||||
self.load_from_gprefs()
|
||||
|
||||
def change_color(self, light=False):
|
||||
which = _('light') if light else _('dark')
|
||||
col = QColorDialog.getColor(self.bcol_light if light else self.bcol_dark,
|
||||
self, _('Choose {} background color for the Cover grid').format(which))
|
||||
|
||||
if col.isValid():
|
||||
if light:
|
||||
self.bcol_light = col
|
||||
else:
|
||||
self.bcol_dark = col
|
||||
btex = self.btex_light if light else self.btex_dark
|
||||
if btex:
|
||||
if question_dialog(
|
||||
self, _('Remove background image?'),
|
||||
_('There is currently a background image set, so the color'
|
||||
' you have chosen will not be visible. Remove the background image?')):
|
||||
if light:
|
||||
self.btex_light = None
|
||||
else:
|
||||
self.btex_dark = None
|
||||
self.changed_signal.emit()
|
||||
|
||||
def change_texture(self, light=False):
|
||||
from calibre.gui2.preferences.texture_chooser import TextureChooser
|
||||
btex = self.btex_light if light else self.btex_dark
|
||||
d = TextureChooser(parent=self, initial=btex)
|
||||
if d.exec() == QDialog.DialogCode.Accepted:
|
||||
if light:
|
||||
self.btex_light = d.texture
|
||||
else:
|
||||
self.btex_dark = d.texture
|
||||
self.changed_signal.emit()
|
||||
|
||||
def light_link_activated(self, url):
|
||||
if 'texture' in url:
|
||||
self.change_texture(light=True)
|
||||
else:
|
||||
self.change_color(light=True)
|
||||
|
||||
def dark_link_activated(self, url):
|
||||
if 'texture' in url:
|
||||
self.change_texture(light=False)
|
||||
else:
|
||||
self.change_color(light=False)
|
||||
|
||||
def commit(self):
|
||||
s = gprefs['cover_grid_background'].copy()
|
||||
s['light'] = tuple(self.bcol_light.getRgb())[:3]
|
||||
s['dark'] = tuple(self.bcol_dark.getRgb())[:3]
|
||||
s['light_texture'] = self.btex_light
|
||||
s['dark_texture'] = self.btex_dark
|
||||
gprefs['cover_grid_background'] = s
|
||||
|
||||
def restore_defaults(self):
|
||||
self.load_from_gprefs(use_defaults=True)
|
||||
|
||||
def update_brush(self):
|
||||
self.brush = QBrush(self.bcol)
|
||||
if self.btex:
|
||||
from calibre.gui2.preferences.texture_chooser import texture_path
|
||||
path = texture_path(self.btex)
|
||||
self.light_brush = QBrush(self.bcol_light)
|
||||
self.dark_brush = QBrush(self.bcol_dark)
|
||||
def dotex(path, brush):
|
||||
if path:
|
||||
p = QPixmap(path)
|
||||
try:
|
||||
dpr = self.devicePixelRatioF()
|
||||
except AttributeError:
|
||||
dpr = self.devicePixelRatio()
|
||||
p.setDevicePixelRatio(dpr)
|
||||
self.brush.setTexture(p)
|
||||
from calibre.gui2.preferences.texture_chooser import texture_path
|
||||
path = texture_path(path)
|
||||
if path:
|
||||
p = QPixmap(path)
|
||||
try:
|
||||
dpr = self.devicePixelRatioF()
|
||||
except AttributeError:
|
||||
dpr = self.devicePixelRatio()
|
||||
p.setDevicePixelRatio(dpr)
|
||||
brush.setTexture(p)
|
||||
dotex(self.btex_light, self.light_brush)
|
||||
dotex(self.btex_dark, self.dark_brush)
|
||||
self.update()
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(200, 120)
|
||||
|
||||
def paintEvent(self, ev):
|
||||
self.lazy_initialize()
|
||||
painter = QPainter(self)
|
||||
painter.fillRect(ev.rect(), self.brush)
|
||||
r = self.rect()
|
||||
light = r.adjusted(0, 0, -r.width()//2, 0)
|
||||
dark = r.adjusted(light.width(), 0, 0, 0)
|
||||
painter.fillRect(light, self.light_brush)
|
||||
painter.fillRect(dark, self.dark_brush)
|
||||
painter.end()
|
||||
super().paintEvent(ev)
|
||||
|
||||
|
||||
class CoverGridTab(QTabWidget, LazyConfigWidgetBase, Ui_Form):
|
||||
@ -87,18 +196,11 @@ class CoverGridTab(QTabWidget, LazyConfigWidgetBase, Ui_Form):
|
||||
|
||||
l = self.cg_background_box.layout()
|
||||
self.cg_bg_widget = w = Background(self)
|
||||
w.changed_signal.connect(self.changed_signal)
|
||||
l.addWidget(w, 0, 0, 3, 1)
|
||||
self.cover_grid_color_button = b = QPushButton(_('Change &color'), self)
|
||||
b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
l.addWidget(b, 0, 1)
|
||||
b.clicked.connect(self.change_cover_grid_color)
|
||||
self.cover_grid_texture_button = b = QPushButton(_('Change &background image'), self)
|
||||
b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
l.addWidget(b, 1, 1)
|
||||
b.clicked.connect(self.change_cover_grid_texture)
|
||||
self.cover_grid_default_appearance_button = b = QPushButton(_('Restore default &appearance'), self)
|
||||
b.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
l.addWidget(b, 2, 1)
|
||||
l.addWidget(b, 0, 1)
|
||||
b.clicked.connect(self.restore_cover_grid_appearance)
|
||||
self.cover_grid_empty_cache.clicked.connect(self.empty_cache)
|
||||
self.cover_grid_open_cache.clicked.connect(self.open_cg_cache)
|
||||
@ -117,8 +219,7 @@ class CoverGridTab(QTabWidget, LazyConfigWidgetBase, Ui_Form):
|
||||
self.grid_rules.lazy_initialize()
|
||||
self.lazy_init_called = True
|
||||
self.blockSignals(False)
|
||||
self.set_cg_color(gprefs['cover_grid_color'])
|
||||
self.set_cg_texture(gprefs['cover_grid_texture'])
|
||||
self.cg_bg_widget.lazy_initialize()
|
||||
self.update_aspect_ratio()
|
||||
|
||||
def show_current_cache_usage(self):
|
||||
@ -169,55 +270,30 @@ class CoverGridTab(QTabWidget, LazyConfigWidgetBase, Ui_Form):
|
||||
self.gui.grid_view.thumbnail_cache.empty()
|
||||
self.calc_cache_size()
|
||||
|
||||
def set_cg_color(self, val):
|
||||
self.cg_bg_widget.bcol = QColor(*val)
|
||||
self.cg_bg_widget.update_brush()
|
||||
|
||||
def set_cg_texture(self, val):
|
||||
self.cg_bg_widget.btex = val
|
||||
self.cg_bg_widget.update_brush()
|
||||
|
||||
def change_cover_grid_color(self):
|
||||
col = QColorDialog.getColor(self.cg_bg_widget.bcol,
|
||||
self.gui, _('Choose background color for the Cover grid'))
|
||||
if col.isValid():
|
||||
col = tuple(col.getRgb())[:3]
|
||||
self.set_cg_color(col)
|
||||
self.changed_signal.emit()
|
||||
if self.cg_bg_widget.btex:
|
||||
if question_dialog(
|
||||
self, _('Remove background image?'),
|
||||
_('There is currently a background image set, so the color'
|
||||
' you have chosen will not be visible. Remove the background image?')):
|
||||
self.set_cg_texture(None)
|
||||
|
||||
def change_cover_grid_texture(self):
|
||||
from calibre.gui2.preferences.texture_chooser import TextureChooser
|
||||
d = TextureChooser(parent=self, initial=self.cg_bg_widget.btex)
|
||||
if d.exec() == QDialog.DialogCode.Accepted:
|
||||
self.set_cg_texture(d.texture)
|
||||
self.changed_signal.emit()
|
||||
|
||||
def restore_cover_grid_appearance(self):
|
||||
self.set_cg_color(gprefs.defaults['cover_grid_color'])
|
||||
self.set_cg_texture(gprefs.defaults['cover_grid_texture'])
|
||||
self.cg_bg_widget.restore_defaults()
|
||||
self.changed_signal.emit()
|
||||
|
||||
def commit(self):
|
||||
with BusyCursor():
|
||||
self.grid_rules.commit()
|
||||
gprefs['cover_grid_color'] = tuple(self.cg_bg_widget.bcol.getRgb())[:3]
|
||||
gprefs['cover_grid_texture'] = self.cg_bg_widget.btex
|
||||
self.cg_bg_widget.commit()
|
||||
return LazyConfigWidgetBase.commit(self)
|
||||
|
||||
def restore_defaults(self):
|
||||
LazyConfigWidgetBase.restore_defaults(self)
|
||||
self.grid_rules.restore_defaults()
|
||||
self.set_cg_color(gprefs.defaults['cover_grid_color'])
|
||||
self.set_cg_texture(gprefs.defaults['cover_grid_texture'])
|
||||
self.cg_bg_widget.restore_defaults()
|
||||
self.changed_signal.emit()
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
gui.library_view.refresh_grid()
|
||||
gui.grid_view.refresh_settings()
|
||||
gui.update_auto_scroll_timeout()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
from calibre.gui2.preferences import test_widget
|
||||
app = Application([])
|
||||
test_widget('Interface', 'Look & Feel', callback=lambda w: w.sections_view.setCurrentRow(1))
|
||||
|
@ -730,6 +730,11 @@ class ScrollingTabWidget(QTabWidget):
|
||||
sw.setStyleSheet(f'#{name} {{ background: transparent }}')
|
||||
return sw
|
||||
|
||||
@property
|
||||
def all_widgets(self):
|
||||
for i in range(self.count()):
|
||||
yield self.widget(i).widget()
|
||||
|
||||
def indexOf(self, page):
|
||||
for i in range(self.count()):
|
||||
t = self.widget(i)
|
||||
|
Loading…
x
Reference in New Issue
Block a user