move "Look & Feel/Cover grid" to its own widget

This commit is contained in:
un-pogaz 2025-01-31 20:29:00 +01:00
parent 210415fd4a
commit 7622074887
5 changed files with 696 additions and 628 deletions

View File

@ -7,12 +7,8 @@ __docformat__ = 'restructuredtext en'
from collections import defaultdict
from functools import partial
from threading import Thread
from qt.core import (
QBrush,
QColor,
QColorDialog,
QComboBox,
QDialog,
QDialogButtonBox,
@ -23,11 +19,8 @@ from qt.core import (
QLabel,
QLineEdit,
QListWidgetItem,
QPainter,
QPixmap,
QPushButton,
QSize,
QSizePolicy,
Qt,
QTableWidget,
QTableWidgetItem,
@ -36,15 +29,21 @@ from qt.core import (
pyqtSignal,
)
from calibre import human_readable
from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK
from calibre.ebooks.metadata.sources.prefs import msprefs
from calibre.gui2 import config, default_author_link, error_dialog, gprefs, open_local_file, question_dialog
from calibre.gui2 import config, default_author_link, error_dialog, gprefs
from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list
from calibre.gui2.library.alternate_views import CM_TO_INCH, auto_height
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.coloring import EditRules
from calibre.gui2.preferences.look_feel_tabs import DisplayedFields, export_layout, import_layout, move_field_down, move_field_up, reset_layout
from calibre.gui2.preferences.look_feel_tabs import (
DisplayedFields,
export_layout,
import_layout,
move_field_down,
move_field_up,
reset_layout,
selected_rows_metadatas,
)
from calibre.gui2.preferences.look_feel_ui import Ui_Form
from calibre.gui2.widgets import BusyCursor
from calibre.gui2.widgets2 import Dialog
@ -281,44 +280,8 @@ class BDVerticalCats(DisplayedFields): # {{{
# }}}
class Background(QWidget): # {{{
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.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
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)
if path:
p = QPixmap(path)
try:
dpr = self.devicePixelRatioF()
except AttributeError:
dpr = self.devicePixelRatio()
p.setDevicePixelRatio(dpr)
self.brush.setTexture(p)
self.update()
def sizeHint(self):
return QSize(200, 120)
def paintEvent(self, ev):
painter = QPainter(self)
painter.fillRect(ev.rect(), self.brush)
painter.end()
# }}}
class ConfigWidget(ConfigWidgetBase, Ui_Form):
size_calculated = pyqtSignal(object)
def genesis(self, gui):
self.gui = gui
@ -330,16 +293,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.default_author_link.changed_signal.connect(self.changed_signal)
r('bd_show_cover', gprefs)
r('bd_overlay_cover_size', gprefs)
r('cover_grid_width', gprefs)
r('cover_grid_height', gprefs)
r('cover_grid_cache_size_multiple', gprefs)
r('cover_grid_disk_cache_size', gprefs)
r('cover_grid_spacing', gprefs)
r('cover_grid_show_title', gprefs)
r('emblem_size', gprefs)
r('emblem_position', gprefs, choices=[
(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')])
r('book_details_comments_heading_pos', gprefs, choices=[
(_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')])
r('book_details_note_link_icon_width', gprefs)
@ -347,11 +300,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('use_roman_numerals_for_series_number', config)
fm = db.field_metadata
choices = sorted(((fm[k]['name'], k) for k in fm.displayable_field_keys() if fm[k]['name']),
key=lambda x:sort_key(x[0]))
r('field_under_covers_in_grid', db.prefs, choices=choices)
choices = [(_('Default'), 'default'), (_('Compact metadata'), 'alt1'),
(_('All on 1 tab'), 'alt2')]
r('edit_metadata_single_layout', gprefs,
@ -400,43 +348,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.icon_rules.changed.connect(self.changed_signal)
self.tabWidget.addTab(self.icon_rules, QIcon.ic('icon_choose.png'), _('Column &icons'))
self.grid_rules = EditRules(self.emblems_tab)
self.grid_rules.changed.connect(self.changed_signal)
self.emblems_tab.setLayout(QVBoxLayout())
self.emblems_tab.layout().addWidget(self.grid_rules)
self.tabWidget.setCurrentIndex(0)
self.tabWidget.tabBar().setVisible(False)
keys = [QKeySequence('F11', QKeySequence.SequenceFormat.PortableText), QKeySequence(
'Ctrl+Shift+F', QKeySequence.SequenceFormat.PortableText)]
keys = [str(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in keys]
self.size_calculated.connect(self.update_cg_cache_size, type=Qt.ConnectionType.QueuedConnection)
self.tabWidget.currentChanged.connect(self.tab_changed)
l = self.cg_background_box.layout()
self.cg_bg_widget = w = Background(self)
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)
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)
connect_lambda(self.cover_grid_smaller_cover.clicked, self, lambda self: self.resize_cover(True))
connect_lambda(self.cover_grid_larger_cover.clicked, self, lambda self: self.resize_cover(False))
self.cover_grid_reset_size.clicked.connect(self.cg_reset_size)
self.opt_cover_grid_disk_cache_size.setMinimum(self.gui.grid_view.thumbnail_cache.min_disk_cache)
self.opt_cover_grid_disk_cache_size.setMaximum(self.gui.grid_view.thumbnail_cache.min_disk_cache * 100)
self.opt_cover_grid_width.valueChanged.connect(self.update_aspect_ratio)
self.opt_cover_grid_height.valueChanged.connect(self.update_aspect_ratio)
self.opt_book_details_css.textChanged.connect(self.changed_signal)
from calibre.gui2.tweak_book.editor.text import get_highlighter, get_theme
self.css_highlighter = get_highlighter('css')()
@ -458,35 +375,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if IdLinksEditor(self).exec() == QDialog.DialogCode.Accepted:
self.changed_signal.emit()
@property
def current_cover_size(self):
cval = self.opt_cover_grid_height.value()
wval = self.opt_cover_grid_width.value()
if cval < 0.1:
dpi = self.opt_cover_grid_height.logicalDpiY()
cval = auto_height(self.opt_cover_grid_height) / dpi / CM_TO_INCH
if wval < 0.1:
wval = 0.75 * cval
return wval, cval
def update_aspect_ratio(self, *args):
width, height = self.current_cover_size
ar = width / height
self.cover_grid_aspect_ratio.setText(_('Current aspect ratio (width/height): %.2g') % ar)
def resize_cover(self, smaller):
wval, cval = self.current_cover_size
ar = wval / cval
delta = 0.2 * (-1 if smaller else 1)
cval += delta
cval = max(0, cval)
self.opt_cover_grid_height.setValue(cval)
self.opt_cover_grid_width.setValue(cval * ar)
def cg_reset_size(self):
self.opt_cover_grid_width.setValue(0)
self.opt_cover_grid_height.setValue(0)
def initialize(self):
ConfigWidgetBase.initialize(self)
@ -495,55 +383,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.em_display_model.initialize()
self.bd_vertical_cats_model.initialize()
db = self.gui.current_db
mi = []
try:
rows = self.gui.current_view().selectionModel().selectedRows()
for row in rows:
if row.isValid():
mi.append(db.new_api.get_proxy_metadata(db.data.index_to_id(row.row())))
except:
pass
mi = selected_rows_metadatas()
self.edit_rules.initialize(db.field_metadata, db.prefs, mi, 'column_color_rules')
self.icon_rules.initialize(db.field_metadata, db.prefs, mi, 'column_icon_rules')
self.grid_rules.initialize(db.field_metadata, db.prefs, mi, 'cover_grid_icon_rules')
self.set_cg_color(gprefs['cover_grid_color'])
self.set_cg_texture(gprefs['cover_grid_texture'])
self.update_aspect_ratio()
self.opt_book_details_css.blockSignals(True)
self.opt_book_details_css.setPlainText(P('templates/book_details.css', data=True).decode('utf-8'))
self.opt_book_details_css.blockSignals(False)
def open_cg_cache(self):
open_local_file(self.gui.grid_view.thumbnail_cache.location)
def update_cg_cache_size(self, size):
self.cover_grid_current_disk_cache.setText(
_('Current space used: %s') % human_readable(size))
def tab_changed(self, index):
if self.tabWidget.currentWidget() is self.cover_grid_tab:
self.show_current_cache_usage()
def show_current_cache_usage(self):
t = Thread(target=self.calc_cache_size)
t.daemon = True
t.start()
def calc_cache_size(self):
self.size_calculated.emit(self.gui.grid_view.thumbnail_cache.current_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 empty_cache(self):
self.gui.grid_view.thumbnail_cache.empty()
self.calc_cache_size()
def restore_defaults(self):
ConfigWidgetBase.restore_defaults(self)
self.default_author_link.value = DEFAULT_AUTHOR_LINK
@ -552,38 +398,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.bd_vertical_cats_model.restore_defaults()
self.edit_rules.clear()
self.icon_rules.clear()
self.grid_rules.clear()
self.changed_signal.emit()
self.set_cg_color(gprefs.defaults['cover_grid_color'])
self.set_cg_texture(gprefs.defaults['cover_grid_texture'])
self.opt_book_details_css.setPlainText(P('templates/book_details.css', allow_user_override=False, data=True).decode('utf-8'))
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.changed_signal.emit()
def commit(self, *args):
with BusyCursor():
self.display_model.commit()
@ -591,9 +408,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.bd_vertical_cats_model.commit()
self.edit_rules.commit(self.gui.current_db.prefs)
self.icon_rules.commit(self.gui.current_db.prefs)
self.grid_rules.commit(self.gui.current_db.prefs)
gprefs['cover_grid_color'] = tuple(self.cg_bg_widget.bcol.getRgb())[:3]
gprefs['cover_grid_texture'] = self.cg_bg_widget.btex
gprefs['default_author_link'] = self.default_author_link.value
bcss = self.opt_book_details_css.toPlainText().encode('utf-8')
defcss = P('templates/book_details.css', data=True, allow_user_override=False)
@ -608,10 +422,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
m.beginResetModel(), m.endResetModel()
gui.tags_view.model().reset_tag_browser()
gui.library_view.refresh_book_details(force=True)
gui.library_view.refresh_grid()
gui.library_view.refresh_composite_edit()
gui.grid_view.refresh_settings()
gui.update_auto_scroll_timeout()
if __name__ == '__main__':

View File

@ -28,10 +28,7 @@
<string>&amp;Main interface</string>
</attribute>
</widget>
<widget class="QTabWidget" name="cover_grid_tab">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="CoverGridTab" name="cover_grid_tab">
<attribute name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/grid.png</normaloff>:/images/grid.png</iconset>
@ -39,412 +36,6 @@
<attribute name="title">
<string>Cover &amp;grid</string>
</attribute>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>&amp;Layout</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Control the Cover grid view. You can enable this view by clicking the &quot;Layout&quot; button in the bottom right corner of the main calibre window.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Field to show &amp;under the covers:</string>
</property>
<property name="buddy">
<cstring>opt_field_under_covers_in_grid</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="opt_field_under_covers_in_grid"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Spac&amp;ing between covers:</string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_spacing</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_spacing">
<property name="toolTip">
<string>The spacing between covers. A value of zero means calculate automatically based on cover size.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="cg_background_box">
<property name="title">
<string>Background for the Cover grid</string>
</property>
<layout class="QGridLayout" name="gridLayout_5"/>
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Cover size</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="2">
<widget class="QPushButton" name="cover_grid_larger_cover">
<property name="toolTip">
<string>Make the covers larger, maintaining current aspect ratio.</string>
</property>
<property name="text">
<string>&amp;Larger covers</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="cover_grid_smaller_cover">
<property name="toolTip">
<string>Make the covers smaller, maintaining current aspect ratio.</string>
</property>
<property name="text">
<string>&amp;Smaller covers</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Cover &amp;height: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_height</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Cover &amp;width: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_width</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_height">
<property name="toolTip">
<string>The height of displayed covers.
A value of zero means calculate automatically.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="3" rowspan="2">
<widget class="QPushButton" name="cover_grid_reset_size">
<property name="toolTip">
<string>Reset size to automatic</string>
</property>
<property name="text">
<string>&amp;Reset size</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QLabel" name="cover_grid_aspect_ratio">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="4">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_width">
<property name="toolTip">
<string>The width of displayed covers.
A value of zero means calculate automatically.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="cover_size_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>By default, calibre chooses a cover size based on your computer's screen size. You can change the cover size here:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="opt_emblem_size">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Size of the emblems (if any) shown &amp;next to the covers: </string>
</property>
<property name="buddy">
<cstring>opt_emblem_size</cstring>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cover_grid_show_title">
<property name="text">
<string>Show a &amp;field (such as title) under the covers</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>&amp;Location of the emblems shown next to the covers:</string>
</property>
<property name="buddy">
<cstring>opt_emblem_position</cstring>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QComboBox" name="opt_emblem_position"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="emblems_tab">
<attribute name="title">
<string>&amp;Emblems</string>
</attribute>
</widget>
<widget class="QWidget" name="tab_6">
<attribute name="title">
<string>&amp;Performance</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Caching of covers for improved performance</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="label_13">
<property name="text">
<string>There are two kinds of caches that calibre uses to improve performance when rendering covers in the grid view. A disk cache that is kept on your hard disk and stores the cover thumbnails and an in memory cache used to ensure flicker free rendering of covers. For best results, keep the memory cache small and the disk cache large, unless you have a lot of extra RAM in your computer and don't mind it being used by the memory cache.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="opt_cover_grid_disk_cache_size">
<property name="specialValueText">
<string>Disable</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="cover_grid_empty_cache">
<property name="text">
<string>&amp;Empty disk cache</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="cover_grid_open_cache">
<property name="text">
<string>&amp;Open cache folder</string>
</property>
</widget>
</item>
<item row="4" column="2" colspan="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>310</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Number of screenfulls of covers to cache in &amp;memory (keep this small):</string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_cache_size_multiple</cstring>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Maximum amount of &amp;disk space to use for caching thumbnails: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_disk_cache_size</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="opt_cover_grid_cache_size_multiple">
<property name="toolTip">
<string>The maximum number of screenfulls of thumbnails to keep in memory. Increasing this will make rendering faster, at the cost of more memory usage. Note that regardless of this setting, a minimum of one hundred thumbnails are always kept in memory, to ensure flicker free rendering.</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="cover_grid_current_disk_cache">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="book_details_tab">
<attribute name="icon">
@ -1035,6 +626,11 @@ columns&quot;. Editing with mouse clicks and the Tab key will be disabled.&lt;/p
<extends>ConfigWidgetBase</extends>
<header>calibre/gui2/preferences/look_feel_tabs/main_interface.h</header>
</customwidget>
<customwidget>
<class>CoverGridTab</class>
<extends>ConfigWidgetBase</extends>
<header>calibre/gui2/preferences/look_feel_tabs/cover_grid.h</header>
</customwidget>
<customwidget>
<class>TbDisplayTab</class>
<extends>ConfigWidgetBase</extends>
@ -1069,22 +665,4 @@ columns&quot;. Editing with mouse clicks and the Tab key will be disabled.&lt;/p
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections>
<connection>
<sender>opt_cover_grid_show_title</sender>
<signal>toggled(bool)</signal>
<receiver>opt_field_under_covers_in_grid</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>86</x>
<y>49</y>
</hint>
<hint type="destinationlabel">
<x>101</x>
<y>51</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -11,6 +11,7 @@ from qt.core import QAbstractListModel, QIcon, QItemSelectionModel, Qt
from calibre.gui2 import choose_files, choose_save_file, error_dialog
from calibre.gui2.book_details import get_field_list
from calibre.gui2.ui import get_gui
class DisplayedFields(QAbstractListModel):
@ -154,3 +155,16 @@ def move_field_down(widget, model):
sm = widget.selectionModel()
sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect)
widget.setCurrentIndex(idx)
def selected_rows_metadatas():
rslt = []
try:
db = get_gui().current_db
rows = get_gui().current_view().selectionModel().selectedRows()
for row in rows:
if row.isValid():
rslt.append(db.new_api.get_proxy_metadata(db.data.index_to_id(row.row())))
except:
pass
return rslt

View File

@ -0,0 +1,223 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2025, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from threading import Thread
from qt.core import QBrush, QColor, QColorDialog, QDialog, QPainter, QPixmap, QPushButton, QSize, QSizePolicy, Qt, QVBoxLayout, QWidget, pyqtSignal
from calibre import human_readable
from calibre.gui2 import gprefs, open_local_file, question_dialog
from calibre.gui2.library.alternate_views import CM_TO_INCH, auto_height
from calibre.gui2.preferences import LazyConfigWidgetBase
from calibre.gui2.preferences.coloring import EditRules
from calibre.gui2.preferences.look_feel_tabs import selected_rows_metadatas
from calibre.gui2.preferences.look_feel_tabs.cover_grid_ui import Ui_Form
from calibre.gui2.widgets import BusyCursor
from calibre.startup import connect_lambda
from calibre.utils.icu import sort_key
class Background(QWidget):
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.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
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)
if path:
p = QPixmap(path)
try:
dpr = self.devicePixelRatioF()
except AttributeError:
dpr = self.devicePixelRatio()
p.setDevicePixelRatio(dpr)
self.brush.setTexture(p)
self.update()
def sizeHint(self):
return QSize(200, 120)
def paintEvent(self, ev):
painter = QPainter(self)
painter.fillRect(ev.rect(), self.brush)
painter.end()
class CoverGridTab(LazyConfigWidgetBase, Ui_Form):
size_calculated = pyqtSignal(object)
def genesis(self, gui):
self.gui = gui
db = self.gui.library_view.model().db
r = self.register
r('cover_grid_width', gprefs)
r('cover_grid_height', gprefs)
r('cover_grid_cache_size_multiple', gprefs)
r('cover_grid_disk_cache_size', gprefs)
r('cover_grid_spacing', gprefs)
r('cover_grid_show_title', gprefs)
r('emblem_size', gprefs)
r('emblem_position', gprefs, choices=[
(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')])
fm = db.field_metadata
choices = sorted(((fm[k]['name'], k) for k in fm.displayable_field_keys() if fm[k]['name']),
key=lambda x:sort_key(x[0]))
r('field_under_covers_in_grid', db.prefs, choices=choices)
self.grid_rules = EditRules(self.emblems_tab)
self.grid_rules.changed.connect(self.changed_signal)
self.emblems_tab.setLayout(QVBoxLayout())
self.emblems_tab.layout().addWidget(self.grid_rules)
self.size_calculated.connect(self.update_cg_cache_size, type=Qt.ConnectionType.QueuedConnection)
l = self.cg_background_box.layout()
self.cg_bg_widget = w = Background(self)
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)
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)
connect_lambda(self.cover_grid_smaller_cover.clicked, self, lambda self: self.resize_cover(True))
connect_lambda(self.cover_grid_larger_cover.clicked, self, lambda self: self.resize_cover(False))
self.cover_grid_reset_size.clicked.connect(self.cg_reset_size)
self.opt_cover_grid_disk_cache_size.setMinimum(self.gui.grid_view.thumbnail_cache.min_disk_cache)
self.opt_cover_grid_disk_cache_size.setMaximum(self.gui.grid_view.thumbnail_cache.min_disk_cache * 100)
self.opt_cover_grid_width.valueChanged.connect(self.update_aspect_ratio)
self.opt_cover_grid_height.valueChanged.connect(self.update_aspect_ratio)
def lazy_initialize(self):
self.show_current_cache_usage()
db = self.gui.current_db
self.blockSignals(True)
self.grid_rules.initialize(db.field_metadata, db.prefs, selected_rows_metadatas(), 'cover_grid_icon_rules')
self.blockSignals(False)
self.set_cg_color(gprefs['cover_grid_color'])
self.set_cg_texture(gprefs['cover_grid_texture'])
self.update_aspect_ratio()
def show_current_cache_usage(self):
t = Thread(target=self.calc_cache_size)
t.daemon = True
t.start()
def calc_cache_size(self):
self.size_calculated.emit(self.gui.grid_view.thumbnail_cache.current_size)
@property
def current_cover_size(self):
cval = self.opt_cover_grid_height.value()
wval = self.opt_cover_grid_width.value()
if cval < 0.1:
dpi = self.opt_cover_grid_height.logicalDpiY()
cval = auto_height(self.opt_cover_grid_height) / dpi / CM_TO_INCH
if wval < 0.1:
wval = 0.75 * cval
return wval, cval
def update_aspect_ratio(self):
width, height = self.current_cover_size
ar = width / height
self.cover_grid_aspect_ratio.setText(_('Current aspect ratio (width/height): %.2g') % ar)
def resize_cover(self, smaller):
wval, cval = self.current_cover_size
ar = wval / cval
delta = 0.2 * (-1 if smaller else 1)
cval += delta
cval = max(0, cval)
self.opt_cover_grid_height.setValue(cval)
self.opt_cover_grid_width.setValue(cval * ar)
def cg_reset_size(self):
self.opt_cover_grid_width.setValue(0)
self.opt_cover_grid_height.setValue(0)
def open_cg_cache(self):
open_local_file(self.gui.grid_view.thumbnail_cache.location)
def update_cg_cache_size(self, size):
self.cover_grid_current_disk_cache.setText(
_('Current space used: %s') % human_readable(size))
def empty_cache(self):
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.changed_signal.emit()
def commit(self):
with BusyCursor():
self.grid_rules.commit(self.gui.current_db.prefs)
gprefs['cover_grid_color'] = tuple(self.cg_bg_widget.bcol.getRgb())[:3]
gprefs['cover_grid_texture'] = self.cg_bg_widget.btex
return LazyConfigWidgetBase.commit(self)
def restore_defaults(self):
LazyConfigWidgetBase.restore_defaults(self)
self.grid_rules.clear()
self.set_cg_color(gprefs.defaults['cover_grid_color'])
self.set_cg_texture(gprefs.defaults['cover_grid_texture'])
self.changed_signal.emit()
def refresh_gui(self, gui):
gui.library_view.refresh_grid()
gui.grid_view.refresh_settings()
gui.update_auto_scroll_timeout()

View File

@ -0,0 +1,442 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<layout class="QVBoxLayout" name="vBoxLayout_cover_grid">
<item>
<widget class="QTabWidget" name="cover_grid_tab">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>&amp;Layout</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Control the Cover grid view. You can enable this view by clicking the &quot;Layout&quot; button in the bottom right corner of the main calibre window.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Field to show &amp;under the covers:</string>
</property>
<property name="buddy">
<cstring>opt_field_under_covers_in_grid</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="opt_field_under_covers_in_grid"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Spac&amp;ing between covers:</string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_spacing</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_spacing">
<property name="toolTip">
<string>The spacing between covers. A value of zero means calculate automatically based on cover size.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="cg_background_box">
<property name="title">
<string>Background for the Cover grid</string>
</property>
<layout class="QGridLayout" name="gridLayout_5"/>
</widget>
</item>
<item row="10" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Cover size</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="2">
<widget class="QPushButton" name="cover_grid_larger_cover">
<property name="toolTip">
<string>Make the covers larger, maintaining current aspect ratio.</string>
</property>
<property name="text">
<string>&amp;Larger covers</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="cover_grid_smaller_cover">
<property name="toolTip">
<string>Make the covers smaller, maintaining current aspect ratio.</string>
</property>
<property name="text">
<string>&amp;Smaller covers</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Cover &amp;height: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_height</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Cover &amp;width: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_width</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_height">
<property name="toolTip">
<string>The height of displayed covers.
Aue of zero means calculate automatically.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="3" rowspan="2">
<widget class="QPushButton" name="cover_grid_reset_size">
<property name="toolTip">
<string>Reset size to automatic</string>
</property>
<property name="text">
<string>&amp;Reset size</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QLabel" name="cover_grid_aspect_ratio">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="4">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="opt_cover_grid_width">
<property name="toolTip">
<string>The width of displayed covers.
Aue of zero means calculate automatically.</string>
</property>
<property name="specialValueText">
<string>Automatic</string>
</property>
<property name="suffix">
<string> cm</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="cover_size_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>By default, calibre chooses a cover size based on your computer's screen size. You can change the cover size here:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="opt_emblem_size">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>16</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Size of the emblems (if any) shown &amp;next to the covers: </string>
</property>
<property name="buddy">
<cstring>opt_emblem_size</cstring>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_cover_grid_show_title">
<property name="text">
<string>Show a &amp;field (such as title) under the covers</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>&amp;Location of the emblems shown next to the covers:</string>
</property>
<property name="buddy">
<cstring>opt_emblem_position</cstring>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QComboBox" name="opt_emblem_position"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="emblems_tab">
<attribute name="title">
<string>&amp;Emblems</string>
</attribute>
</widget>
<widget class="QWidget" name="tab_6">
<attribute name="title">
<string>&amp;Performance</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Caching of covers for improved performance</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="5">
<widget class="QLabel" name="label_13">
<property name="text">
<string>There are two kinds of caches that calibre uses to improve performance when rendering covers in the grid view. A disk cache that is kept on your hard disk and stores the cover thumbnails and an in memory cache used to ensure flicker free rendering of covers. For best results, keep the memory cache small and the disk cache large, unless you have a lot of extra RAM in your computer and don't mind it being used by the memory cache.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="opt_cover_grid_disk_cache_size">
<property name="specialValueText">
<string>Disable</string>
</property>
<property name="suffix">
<string> MB</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="cover_grid_empty_cache">
<property name="text">
<string>&amp;Empty disk cache</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="cover_grid_open_cache">
<property name="text">
<string>&amp;Open cache folder</string>
</property>
</widget>
</item>
<item row="4" column="2" colspan="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>310</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="3">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Number of screenfulls of covers to cache in &amp;memory (keep this small):</string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_cache_size_multiple</cstring>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Maximum amount of &amp;disk space to use for caching thumbnails: </string>
</property>
<property name="buddy">
<cstring>opt_cover_grid_disk_cache_size</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="opt_cover_grid_cache_size_multiple">
<property name="toolTip">
<string>The maximum number of screenfulls of thumbnails to keep in memory. Increasing this will make rendering faster, at the cost of more memory usage. Note that regardless of this setting, a minimum of one hundred thumbnails are always kept in memory, to ensure flicker free rendering.</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="cover_grid_current_disk_cache">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections>
<connection>
<sender>opt_cover_grid_show_title</sender>
<signal>toggled(bool)</signal>
<receiver>opt_field_under_covers_in_grid</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>86</x>
<y>49</y>
</hint>
<hint type="destinationlabel">
<x>101</x>
<y>51</y>
</hint>
</hints>
</connection>
</connections>
</ui>