diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py index 2b35128272..207a606da1 100644 --- a/src/calibre/gui2/library/alternate_views.py +++ b/src/calibre/gui2/library/alternate_views.py @@ -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 diff --git a/src/calibre/gui2/library/bookshelf_view.py b/src/calibre/gui2/library/bookshelf_view.py index a94d099542..f53eb15eff 100644 --- a/src/calibre/gui2/library/bookshelf_view.py +++ b/src/calibre/gui2/library/bookshelf_view.py @@ -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: diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 63775ceb77..abcf0fcde8 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -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