From 501d1b93ffa7d064fe93e8dd033cf996f4ee3ce8 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 18 Jan 2026 16:05:26 +0100
Subject: [PATCH] Bookshelf: allow custom background
---
src/calibre/gui2/__init__.py | 8 ++++
src/calibre/gui2/library/bookshelf_view.py | 37 +++++++++++++++++--
.../look_feel_tabs/bookshelf_view.py | 3 ++
.../look_feel_tabs/bookshelf_view.ui | 20 +++++++++-
4 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 64a4f7faef..7eff9507d1 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -507,6 +507,10 @@ def create_defs():
defs['bookshelf_start_with_divider'] = False
defs['bookshelf_divider_style'] = 'text'
defs['bookshelf_theme_override'] = 'none'
+ defs['bookshelf_use_custom_background'] = False
+ defs['bookshelf_custom_background'] = {
+ 'light': (255, 255, 255), 'dark': (64, 64, 64), 'light_texture': None, 'dark_texture': None
+ }
# Migrate beta bookshelf_thumbnail
if isinstance(btv := gprefs.get('bookshelf_thumbnail'), bool):
@@ -1796,3 +1800,7 @@ def resolve_custom_background(name: str ,which='color', for_dark: bool | None =
def resolve_grid_color(which='color', for_dark: bool | None = None, use_defaults: bool = False):
return resolve_custom_background('cover_grid_background', which=which, for_dark=for_dark, use_defaults=use_defaults)
+
+
+def resolve_bookshelf_color(which='color', for_dark: bool | None = None, use_defaults: bool = False):
+ return resolve_custom_background('bookshelf_custom_background', which=which, for_dark=for_dark, use_defaults=use_defaults)
diff --git a/src/calibre/gui2/library/bookshelf_view.py b/src/calibre/gui2/library/bookshelf_view.py
index ef4829b3ca..bbc3cd981b 100644
--- a/src/calibre/gui2/library/bookshelf_view.py
+++ b/src/calibre/gui2/library/bookshelf_view.py
@@ -5,6 +5,7 @@
# Imports {{{
import bisect
import math
+import os
import random
import struct
import weakref
@@ -62,13 +63,16 @@ from qt.core import (
QWidget,
pyqtProperty,
pyqtSignal,
+ qBlue,
+ qGreen,
+ qRed,
)
from xxhash import xxh3_64_intdigest
from calibre import fit_image
from calibre.db.cache import Cache
from calibre.ebooks.metadata import authors_to_string, rating_to_stars
-from calibre.gui2 import config, gprefs
+from calibre.gui2 import config, gprefs, resolve_bookshelf_color
from calibre.gui2.library.alternate_views import (
ClickStartData,
double_click_action,
@@ -1535,6 +1539,7 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
self.text_color_for_dark_background = dark_palette().color(QPalette.ColorRole.WindowText)
self.text_color_for_light_background = light_palette().color(QPalette.ColorRole.WindowText)
self.setPalette(dark_palette() if is_dark_theme() else light_palette())
+ self.set_custom_background()
def view_is_visible(self) -> bool:
'''Return if the bookshelf view is visible.'''
@@ -1767,6 +1772,30 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
draw_horizontal(top, 'top')
draw_horizontal(bottom, 'bottom')
+ def set_custom_background(self):
+ if not gprefs['bookshelf_use_custom_background']:
+ self.setStyleSheet('')
+ return
+ r, g, b = resolve_bookshelf_color(for_dark=is_dark_theme())
+ tex = resolve_bookshelf_color(for_dark=is_dark_theme(), which='texture')
+ pal = self.palette()
+ bgcol = QColor(r, g, b)
+ pal.setColor(QPalette.ColorRole.Base, bgcol)
+ self.setPalette(pal)
+ ss = f'background-color: {bgcol.name()}; border: 0px solid {bgcol.name()};'
+ if tex:
+ from calibre.gui2.preferences.texture_chooser import texture_path
+ path = texture_path(tex)
+ if path:
+ path = os.path.abspath(path).replace(os.sep, '/')
+ ss += f'background-image: url({path});'
+ ss += 'background-attachment: fixed;'
+ pm = QPixmap(path)
+ if not pm.isNull():
+ val = pm.scaled(1, 1).toImage().pixel(0, 0)
+ r, g, b = qRed(val), qGreen(val), qBlue(val)
+ self.setStyleSheet(f'QAbstractScrollArea {{ {ss} }}')
+
def paintEvent(self, ev: QPaintEvent) -> None:
'''Paint the bookshelf view.'''
if not self.view_is_visible():
@@ -1797,8 +1826,10 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
shelves.append((nshelf, shelf is not nshelf))
if not hasattr(self, 'case_renderer'):
self.case_renderer = RenderCase()
- painter.drawPixmap(
- QPoint(0, 0), self.case_renderer.background_as_pixmap(viewport_rect.width(), viewport_rect.height()))
+ if not gprefs['bookshelf_use_custom_background']:
+ painter.drawPixmap(
+ QPoint(0, 0), self.case_renderer.background_as_pixmap(viewport_rect.width(), viewport_rect.height()),
+ )
n = self.shelves_per_screen
for base in shelf_bases:
self.draw_shelf_base(painter, base, scroll_y, self.width(), base.idx % n)
diff --git a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py
index 23067816c7..c77c7ff0ee 100644
--- a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py
+++ b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.py
@@ -118,6 +118,9 @@ class BookshelfTab(QTabWidget, LazyConfigWidgetBase, Ui_Form):
(_('Dark'), 'dark'),
])
+ r('bookshelf_use_custom_background', gprefs)
+ self.background_box.link_config('bookshelf_custom_background')
+
self.config_cache.link(
self.gui.bookshelf_view.cover_cache,
'bookshelf_disk_cache_size', 'bookshelf_cache_size_multiple',
diff --git a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui
index 6f79452716..686fd04281 100644
--- a/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui
+++ b/src/calibre/gui2/preferences/look_feel_tabs/bookshelf_view.ui
@@ -340,6 +340,19 @@
-
+
+
+ Use a custom background instead of the one generated by calibre
+
+
+ Use a custom background
+
+
+
+ -
+
+
+ -
-
@@ -373,7 +386,7 @@
- -
+
-
Qt::Vertical
@@ -410,6 +423,11 @@
ConfigWidgetBase
calibre/gui2/preferences/look_feel_tabs.h
+
+ BackgroundConfig
+ ConfigWidgetBase
+ calibre/gui2/preferences/look_feel_tabs.h
+
CoverCacheConfig
ConfigWidgetBase