Bookshelf: Clean up emblem rendering re-using code from grid view

This commit is contained in:
Kovid Goyal 2026-01-23 00:07:50 +05:30
parent c633a18d0f
commit 3947e501c6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 60 additions and 52 deletions

View File

@ -224,6 +224,43 @@ def handle_enter_press(self, ev, special_action=None, has_edit_cell=True):
return False
def render_emblem(book_id, rule, rule_index, cache, mi, db, formatter, template_cache, column_name='cover_grid'):
ans = cache[book_id].get(rule, False)
if ans is not False:
return ans, mi
ans = None
if mi is None:
mi = db.get_proxy_metadata(book_id)
ans = formatter.safe_format(rule, mi, '', mi, column_name=f'{column_name}{rule_index}', template_cache=template_cache) or None
cache[book_id][rule] = ans
return ans, mi
def cached_emblem(sz: int, cache: dict[str, QPixmap | QIcon], name: str, raw_icon=None):
ans = cache.get(name, False)
if ans is not False:
return ans
ans = None
if raw_icon is not None:
ans = raw_icon
elif name == ':ondevice':
ans = QIcon.cached_icon('ok.png')
elif name:
d = themed_icon_name(os.path.join(config_dir, 'cc_icons'), name)
if d is not None:
ans = QIcon(d)
if ans is None:
ans = QIcon(os.path.join(config_dir, 'cc_icons', name))
if ans is not None and not ans.is_ok():
ans = None
if ans is not None and sz:
ans = ans.pixmap(sz, sz)
if ans.isNull():
ans = None
cache[name] = ans
return ans
def image_to_data(image): # {{{
# Although this function is no longer used in this file, it is used in
# other places in calibre. Don't delete it.
@ -644,39 +681,6 @@ class CoverDelegate(QStyledItemDelegate):
traceback.print_exc()
return '', is_stars
def render_emblem(self, book_id, rule, rule_index, cache, mi, db, formatter, template_cache):
ans = cache[book_id].get(rule, False)
if ans is not False:
return ans, mi
ans = None
if mi is None:
mi = db.get_proxy_metadata(book_id)
ans = formatter.safe_format(rule, mi, '', mi, column_name=f'cover_grid{rule_index}', template_cache=template_cache) or None
cache[book_id][rule] = ans
return ans, mi
def cached_emblem(self, cache, name, raw_icon=None):
ans = cache.get(name, False)
if ans is not False:
return ans
sz = self.emblem_size
ans = None
if raw_icon is not None:
ans = raw_icon.pixmap(sz, sz)
elif name == ':ondevice':
ans = QIcon.cached_icon('ok.png').pixmap(sz, sz)
elif name:
pmap = None
d = themed_icon_name(os.path.join(config_dir, 'cc_icons'), name)
if d is not None:
pmap = QIcon(d).pixmap(sz, sz)
if pmap is None:
pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz)
if not pmap.isNull():
ans = pmap
cache[name] = ans
return ans
def paint(self, painter, option, index):
with clip_border_radius(painter, option.rect):
QStyledItemDelegate.paint(self, painter, option, empty_index) # draw the hover and selection highlights
@ -707,16 +711,16 @@ class CoverDelegate(QStyledItemDelegate):
if self.emblem_size > 0:
mi = None
for i, (kind, column, rule) in enumerate(emblem_rules):
icon_name, mi = self.render_emblem(book_id, rule, i, m.cover_grid_emblem_cache, mi, db, m.formatter, m.cover_grid_template_cache)
icon_name, mi = render_emblem(book_id, rule, i, m.cover_grid_emblem_cache, mi, db, m.formatter, m.cover_grid_template_cache)
if icon_name is not None:
for one_icon in filter(None, (i.strip() for i in icon_name.split(':'))):
pixmap = self.cached_emblem(m.cover_grid_bitmap_cache, one_icon)
pixmap = cached_emblem(self.emblem_size, m.cover_grid_bitmap_cache, one_icon)
if pixmap is not None:
emblems.append(pixmap)
if marked:
emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':marked', m.marked_icon))
emblems.insert(0, cached_emblem(self.emblem_size, m.cover_grid_bitmap_cache, ':marked', m.marked_icon))
if on_device:
emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':ondevice'))
emblems.insert(0, cached_emblem(self.emblem_size, m.cover_grid_bitmap_cache, ':ondevice'))
painter.save()
right_adjust = 0

View File

@ -74,10 +74,12 @@ from calibre.ebooks.metadata import authors_to_string, rating_to_stars
from calibre.gui2 import config, gprefs, resolve_bookshelf_color
from calibre.gui2.library.alternate_views import (
ClickStartData,
cached_emblem,
double_click_action,
handle_enter_press,
handle_selection_click,
handle_selection_drag,
render_emblem,
selection_for_rows,
setup_dnd_interface,
)
@ -1517,7 +1519,6 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
# Cover template caching
self.template_inited = False
self.emblem_rules = []
self.template_cache = {}
self.template_is_empty = {}
self.first_line_renderer = self.build_template_renderer('title', '{title}')
self.second_line_renderer = self.build_template_renderer('authors', '')
@ -1591,7 +1592,6 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
def db_pref(key):
return db.new_api.pref(key, get_default_from_defaults=True)
self.template_cache = {}
title = db_pref('bookshelf_title_template') or ''
self.first_line_renderer = self.build_template_renderer('title', title)
authors = db_pref('bookshelf_author_template') or ''
@ -1609,6 +1609,8 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
self.init_template(db)
if self.template_is_empty[column_name]:
return ''
if not (m := self.model()):
return ''
match template:
case '{title}':
return db.field_for('title', book_id)
@ -1620,21 +1622,21 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
return db.field_for('sort', book_id)
mi = db.get_proxy_metadata(book_id)
rslt = mi.formatter.safe_format(
template, mi, TEMPLATE_ERROR, mi, column_name=column_name, template_cache=self.template_cache)
template, mi, TEMPLATE_ERROR, mi, column_name=column_name, template_cache=m.bookshelf_template_cache)
return rslt or ''
def render_emblem(self, book_id: int) -> str:
if not (db := self.dbref()):
return ''
if not (m := self.model()):
return
db = m.db.new_api
self.init_template(db)
if not self.emblem_rules:
return ''
mi = db.get_proxy_metadata(book_id)
for (x,y,t) in self.emblem_rules:
rslt = mi.formatter.safe_format(
t, mi, TEMPLATE_ERROR, mi, column_name='bookshelf_emblem', template_cache=self.template_cache)
if rslt:
return rslt
mi = None
for i, (kind, column, rule) in enumerate(self.emblem_rules):
icon_name, mi = render_emblem(book_id, rule, i, m.bookshelf_emblem_cache, mi, db, m.formatter, m.bookshelf_template_cache, column_name='bookshelf')
if icon_name:
return icon_name
return ''
def refresh_settings(self):
@ -1855,10 +1857,8 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
device_connected = get_gui().device_connected is not None
on_device = device_connected and db.field_for('ondevice', book_id)
if on_device:
if getattr(self, 'on_device_icon', None) is None:
self.on_device_icon = QIcon.cached_icon('ok.png')
which = above if below else below
which.append(self.on_device_icon)
which.append(cached_emblem(0, m.bookshelf_bitmap_cache, ':ondevice'))
custom = self.render_emblem(book_id)
if custom:
match gprefs['bookshelf_emblem_position']:
@ -1872,7 +1872,8 @@ class BookshelfView(MomentumScrollMixin, QAbstractScrollArea):
which = bottom
case _:
which = above if below and not above else below
which.append(QIcon.cached_icon(custom))
if icon := cached_emblem(0, m.bookshelf_bitmap_cache, custom):
which.append(icon)
def draw_horizontal(emblems: list[QIcon], position: str) -> None:
if not emblems:

View File

@ -315,11 +315,14 @@ class BooksModel(QAbstractTableModel): # {{{
self.icon_cache = defaultdict(dict)
self.icon_bitmap_cache = {}
self.cover_grid_emblem_cache = defaultdict(dict)
self.bookshelf_emblem_cache = defaultdict(dict)
self.cover_grid_bitmap_cache = {}
self.bookshelf_bitmap_cache = {}
self.color_row_fmt_cache = None
self.color_template_cache = {}
self.icon_template_cache = {}
self.cover_grid_template_cache = {}
self.bookshelf_template_cache = {}
def set_row_height(self, height):
self.row_height = height