Cover grid: Option to draw emblems on top of cover in Preferences->Look & feel->Cover grid->Emblems

Fixes #3019 (Cover grid: replace show_emblems/draw_emblems_on_cover with emblem_style enum; add on-cover emblem rendering)
This commit is contained in:
copilot-swe-agent[bot] 2026-02-23 04:20:17 +00:00 committed by Kovid Goyal
parent 203a76edfe
commit deeb08929c
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 92 additions and 7 deletions

View File

@ -451,9 +451,9 @@ def create_defs():
defs['cb_preserve_aspect_ratio'] = False
defs['cb_double_click_to_activate'] = False
defs['gpm_template_editor_font_size'] = 10
defs['show_emblems'] = False
defs['emblem_size'] = 32
defs['emblem_position'] = 'left'
defs['emblem_style'] = 'none'
defs['metadata_diff_mark_rejected'] = False
defs['tag_browser_show_counts'] = True
defs['tag_browser_show_tooltips'] = True
@ -551,6 +551,15 @@ def create_defs():
migrate_tweak('metadata_edit_single_cc_label_length', 'edit_metadata_single_cc_label_length')
migrate_tweak('metadata_single_use_2_cols_for_custom_fields', 'edit_metadata_single_use_2_cols_for_custom_fields')
# Migrate show_emblems and draw_emblems_on_cover to emblem_style
show_emblems = gprefs.pop('show_emblems', None)
draw_emblems_on_cover = gprefs.pop('draw_emblems_on_cover', None)
if show_emblems is not None or draw_emblems_on_cover is not None:
if show_emblems:
gprefs['emblem_style'] = 'emboss' if draw_emblems_on_cover else 'gutter'
else:
gprefs['emblem_style'] = 'none'
create_defs()
del create_defs

View File

@ -617,10 +617,10 @@ class CoverDelegate(QStyledItemDelegate):
height = self.original_height = gprefs['cover_grid_height']
self.original_show_title = show_title = gprefs['cover_grid_show_title']
self.original_flush_bottom = self.flush_bottom = gprefs['cover_grid_text_flush_bottom']
self.original_show_emblems = gprefs['show_emblems']
self.original_emblem_style = gprefs['emblem_style']
self.orginal_emblem_size = gprefs['emblem_size']
self.orginal_emblem_position = gprefs['emblem_position']
self.emblem_size = gprefs['emblem_size'] if self.original_show_emblems else 0
self.emblem_size = gprefs['emblem_size'] if self.original_emblem_style != 'none' else 0
try:
self.gutter_position = getattr(self, self.orginal_emblem_position.upper())
except Exception:
@ -644,7 +644,7 @@ class CoverDelegate(QStyledItemDelegate):
sz = f.pointSize() * self.parent().logicalDpiY() / 72.0
self.title_height = int(max(25, sz + 10))
self.item_size = self.cover_size + QSize(2 * self.MARGIN, (2 * self.MARGIN) + self.title_height)
if self.emblem_size > 0:
if self.emblem_size > 0 and self.original_emblem_style == 'gutter':
extra = self.emblem_size + self.MARGIN
self.item_size += QSize(extra, 0) if self.gutter_position in (self.LEFT, self.RIGHT) else QSize(0, extra)
self.calculate_spacing()
@ -732,10 +732,11 @@ class CoverDelegate(QStyledItemDelegate):
painter.save()
right_adjust = 0
emblem_rect = None
try:
rect = option.rect
rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN)
if self.emblem_size > 0:
if self.emblem_size > 0 and self.original_emblem_style == 'gutter':
self.paint_emblems(painter, rect, emblems)
orect = QRect(rect)
trect = QRect(rect)
@ -749,6 +750,7 @@ class CoverDelegate(QStyledItemDelegate):
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter|Qt.TextFlag.TextWordWrap, f'{title}\n\n{authors}')
if self.title_height != 0:
self.paint_title(painter, trect, db, book_id)
emblem_rect = QRect(rect)
else:
if self.animating is not None and self.animating.row() == index.row():
cover = cover.scaled(cover.size() * self._animated_size)
@ -764,6 +766,10 @@ class CoverDelegate(QStyledItemDelegate):
if self.flush_bottom:
trect.setTop(rect.bottom() + 5)
self.paint_title(painter, trect, db, book_id, align_top=self.flush_bottom)
emblem_rect = QRect(rect)
if self.original_emblem_style == 'emboss' and emblems:
self.paint_emblems_on_cover(painter, emblem_rect, emblems)
return
if self.emblem_size > 0:
# We don't draw embossed emblems as the ondevice/marked emblems are drawn in the gutter
return
@ -831,6 +837,37 @@ class CoverDelegate(QStyledItemDelegate):
finally:
painter.restore()
def paint_emblems_on_cover(self, painter, rect, emblems):
esz = self.emblem_size
if not esz:
return
margin = self.MARGIN
r = gprefs['cover_corner_radius']
if r > 0:
if gprefs['cover_corner_radius_unit'] == '%':
corner_inset = int(r / 100 * min(rect.width(), rect.height()))
else:
corner_inset = int(r)
else:
corner_inset = 0
available_height = rect.height() - corner_inset
max_per_edge = max(1, available_height // (esz + margin))
with painter:
painter.setClipRect(rect)
for i, emblem in enumerate(emblems):
if i < max_per_edge:
x = rect.left() + margin
y = rect.top() + corner_inset + i * (esz + margin)
else:
j = i - max_per_edge
if j >= max_per_edge:
break
x = rect.right() - esz - margin
y = rect.top() + corner_inset + j * (esz + margin)
ew = int(emblem.width() / emblem.devicePixelRatio())
eh = int(emblem.height() / emblem.devicePixelRatio())
painter.drawPixmap(QRect(x, y, ew, eh), emblem)
def paint_embossed_emblem(self, pixmap, painter, orect, right_adjust, left=True):
drect = QRect(orect)
pw = int(pixmap.width() / pixmap.devicePixelRatio())
@ -1030,7 +1067,7 @@ class GridView(MomentumScrollMixin, QListView):
)
if (size_changed or gprefs[
'cover_grid_show_title'] != self.delegate.original_show_title or gprefs[
'show_emblems'] != self.delegate.original_show_emblems or gprefs[
'emblem_style'] != self.delegate.original_emblem_style or gprefs[
'emblem_size'] != self.delegate.orginal_emblem_size or gprefs[
'emblem_position'] != self.delegate.orginal_emblem_position or gprefs[
'cover_grid_text_flush_bottom'] != self.delegate.original_flush_bottom):

View File

@ -38,6 +38,10 @@ class CoverGridTab(QTabWidget, LazyConfigWidgetBase, Ui_cover_grid_tab):
r('emblem_size', gprefs)
r('emblem_position', gprefs, choices=[
(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')])
r('emblem_style', gprefs, choices=[
(_('Do not show emblems'), 'none'),
(_('Show in a gutter next to the cover'), 'gutter'),
(_('Draw on the cover'), 'emboss')])
fm = db.field_metadata
choices = sorted((('{} ({})'.format(fm[k]['name'], k), k) for k in fm.displayable_field_keys() if fm[k]['name']),

View File

@ -288,10 +288,45 @@ A value of zero means calculate automatically.</string>
</item>
</layout>
</widget>
<widget class="EditRulesWidget" name="opt_cover_grid_icon_rules">
<widget class="QWidget" name="tab_emblems">
<attribute name="title">
<string>&amp;Emblems</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_emblems">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_emblems">
<item>
<widget class="QLabel" name="label_emblem_style">
<property name="text">
<string>Emblem &amp;style:</string>
</property>
<property name="buddy">
<cstring>opt_emblem_style</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_emblem_style"/>
</item>
<item>
<spacer name="horizontalSpacer_emblems">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="EditRulesWidget" name="opt_cover_grid_icon_rules"/>
</item>
</layout>
</widget>
<widget class="CoverCacheConfig" name="config_cache">
<attribute name="title">