From b32e1beaf4832228cde67297733bf88cb578019e Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:38:24 +0100 Subject: [PATCH] move "Look & Feel/Book details" to its own widget --- src/calibre/gui2/preferences/look_feel.py | 295 +----------------- src/calibre/gui2/preferences/look_feel.ui | 244 +-------------- .../preferences/look_feel_tabs/__init__.py | 65 +++- .../look_feel_tabs/book_details.py | 256 +++++++++++++++ .../look_feel_tabs/book_details.ui | 258 +++++++++++++++ 5 files changed, 586 insertions(+), 532 deletions(-) create mode 100644 src/calibre/gui2/preferences/look_feel_tabs/book_details.py create mode 100644 src/calibre/gui2/preferences/look_feel_tabs/book_details.ui diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 7e8f4b912b..488a569f06 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -5,33 +5,11 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from collections import defaultdict from functools import partial -from qt.core import ( - QComboBox, - QDialog, - QDialogButtonBox, - QFormLayout, - QHeaderView, - QIcon, - QKeySequence, - QLabel, - QLineEdit, - QListWidgetItem, - QPushButton, - QSize, - Qt, - QTableWidget, - QTableWidgetItem, - QVBoxLayout, - QWidget, - pyqtSignal, -) +from qt.core import QIcon, QKeySequence, QListWidgetItem, Qt -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 +from calibre.gui2 import gprefs from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.coloring import EditRules @@ -46,187 +24,6 @@ from calibre.gui2.preferences.look_feel_tabs import ( ) from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2.widgets import BusyCursor -from calibre.gui2.widgets2 import Dialog -from calibre.startup import connect_lambda -from calibre.utils.icu import sort_key -from calibre.utils.resources import get_path as P -from calibre.utils.resources import set_data -from polyglot.builtins import iteritems - - -class DefaultAuthorLink(QWidget): # {{{ - - changed_signal = pyqtSignal() - - def __init__(self, parent): - QWidget.__init__(self, parent) - l = QVBoxLayout(parent) - l.addWidget(self) - l.setContentsMargins(0, 0, 0, 0) - l = QFormLayout(self) - l.setContentsMargins(0, 0, 0, 0) - l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) - self.choices = c = QComboBox() - c.setMinimumContentsLength(30) - for text, data in [ - (_('Search for the author on Goodreads'), 'search-goodreads'), - (_('Search for the author on Amazon'), 'search-amzn'), - (_('Search for the author in your calibre library'), 'search-calibre'), - (_('Search for the author on Wikipedia'), 'search-wikipedia'), - (_('Search for the author on Google Books'), 'search-google'), - (_('Search for the book on Goodreads'), 'search-goodreads-book'), - (_('Search for the book on Amazon'), 'search-amzn-book'), - (_('Search for the book on Google Books'), 'search-google-book'), - (_('Use a custom search URL'), 'url'), - ]: - c.addItem(text, data) - l.addRow(_('Clicking on &author names should:'), c) - self.custom_url = u = QLineEdit(self) - u.setToolTip(_( - 'Enter the URL to search. It should contain the string {0}' - '\nwhich will be replaced by the author name. For example,' - '\n{1}').format('{author}', 'https://en.wikipedia.org/w/index.php?search={author}')) - u.textChanged.connect(self.changed_signal) - u.setPlaceholderText(_('Enter the URL')) - c.currentIndexChanged.connect(self.current_changed) - l.addRow(u) - self.current_changed() - c.currentIndexChanged.connect(self.changed_signal) - - @property - def value(self): - k = self.choices.currentData() - if k == 'url': - return self.custom_url.text() - return k if k != DEFAULT_AUTHOR_LINK else None - - @value.setter - def value(self, val): - i = self.choices.findData(val) - if i < 0: - i = self.choices.findData('url') - self.custom_url.setText(val) - self.choices.setCurrentIndex(i) - - def current_changed(self): - k = self.choices.currentData() - self.custom_url.setVisible(k == 'url') -# }}} - - -# IdLinksEditor {{{ -class IdLinksRuleEdit(Dialog): - - def __init__(self, key='', name='', template='', parent=None): - title = _('Edit rule') if key else _('Create a new rule') - Dialog.__init__(self, title=title, name='id-links-rule-editor', parent=parent) - self.key.setText(key), self.nw.setText(name), self.template.setText(template or 'https://example.com/{id}') - if self.size().height() < self.sizeHint().height(): - self.resize(self.sizeHint()) - - @property - def rule(self): - return self.key.text().lower(), self.nw.text(), self.template.text() - - def setup_ui(self): - self.l = l = QFormLayout(self) - l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) - l.addRow(QLabel(_( - 'The key of the identifier, for example, in isbn:XXX, the key is "isbn"'))) - self.key = k = QLineEdit(self) - l.addRow(_('&Key:'), k) - l.addRow(QLabel(_( - 'The name that will appear in the Book details panel'))) - self.nw = n = QLineEdit(self) - l.addRow(_('&Name:'), n) - la = QLabel(_( - 'The template used to create the link.' - ' The placeholder {0} in the template will be replaced' - ' with the actual identifier value. Use {1} to avoid the value' - ' being quoted.').format('{id}', '{id_unquoted}')) - la.setWordWrap(True) - l.addRow(la) - self.template = t = QLineEdit(self) - l.addRow(_('&Template:'), t) - t.selectAll() - t.setFocus(Qt.FocusReason.OtherFocusReason) - l.addWidget(self.bb) - - def accept(self): - r = self.rule - for i, which in enumerate([_('Key'), _('Name'), _('Template')]): - if not r[i]: - return error_dialog(self, _('Value needed'), _( - 'The %s field cannot be empty') % which, show=True) - Dialog.accept(self) - - -class IdLinksEditor(Dialog): - - def __init__(self, parent=None): - Dialog.__init__(self, title=_('Create rules for identifiers'), name='id-links-rules-editor', parent=parent) - - def setup_ui(self): - self.l = l = QVBoxLayout(self) - self.la = la = QLabel(_( - 'Create rules to convert identifiers into links.')) - la.setWordWrap(True) - l.addWidget(la) - items = [] - for k, lx in iteritems(msprefs['id_link_rules']): - for n, t in lx: - items.append((k, n, t)) - items.sort(key=lambda x: sort_key(x[1])) - self.table = t = QTableWidget(len(items), 3, self) - t.setHorizontalHeaderLabels([_('Key'), _('Name'), _('Template')]) - for r, (key, val, template) in enumerate(items): - t.setItem(r, 0, QTableWidgetItem(key)) - t.setItem(r, 1, QTableWidgetItem(val)) - t.setItem(r, 2, QTableWidgetItem(template)) - l.addWidget(t) - t.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) - self.cb = b = QPushButton(QIcon.ic('plus.png'), _('&Add rule'), self) - connect_lambda(b.clicked, self, lambda self: self.edit_rule()) - self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) - self.rb = b = QPushButton(QIcon.ic('minus.png'), _('&Remove rule'), self) - connect_lambda(b.clicked, self, lambda self: self.remove_rule()) - self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) - self.eb = b = QPushButton(QIcon.ic('modified.png'), _('&Edit rule'), self) - connect_lambda(b.clicked, self, lambda self: self.edit_rule(self.table.currentRow())) - self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) - l.addWidget(self.bb) - - def sizeHint(self): - return QSize(700, 550) - - def accept(self): - rules = defaultdict(list) - for r in range(self.table.rowCount()): - def item(c): - return self.table.item(r, c).text() - rules[item(0)].append([item(1), item(2)]) - msprefs['id_link_rules'] = dict(rules) - Dialog.accept(self) - - def edit_rule(self, r=-1): - key = name = template = '' - if r > -1: - key, name, template = (self.table.item(r, c).text() for c in range(3)) - d = IdLinksRuleEdit(key, name, template, self) - if d.exec() == QDialog.DialogCode.Accepted: - if r < 0: - self.table.setRowCount(self.table.rowCount() + 1) - r = self.table.rowCount() - 1 - rule = d.rule - for c in range(3): - self.table.setItem(r, c, QTableWidgetItem(rule[c])) - self.table.scrollToItem(self.table.item(r, 0)) - - def remove_rule(self): - r = self.table.currentRow() - if r > -1: - self.table.removeRow(r) -# }}} class EMDisplayedFields(DisplayedFields): # {{{ @@ -246,40 +43,6 @@ class EMDisplayedFields(DisplayedFields): # {{{ # }}} -class BDVerticalCats(DisplayedFields): # {{{ - - def __init__(self, db, parent=None, category_icons=None): - DisplayedFields.__init__(self, db, parent, category_icons=category_icons) - from calibre.gui2.ui import get_gui - self.gui = get_gui() - - def initialize(self, use_defaults=False, pref_data_override=None): - fm = self.db.field_metadata - cats = [k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and not k.startswith('#')] - cats.append('path') - cats.extend([k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and k.startswith('#')]) - ans = [] - if use_defaults: - ans = [[k, False] for k in cats] - self.changed = True - elif pref_data_override: - ph = dict(pref_data_override) - ans = [[k, ph.get(k, False)] for k in cats] - self.changed = True - else: - vertical_cats = self.db.prefs.get('book_details_vertical_categories') or () - for key in cats: - ans.append([key, key in vertical_cats]) - self.beginResetModel() - self.fields = ans - self.endResetModel() - - def commit(self): - if self.changed: - self.db.prefs.set('book_details_vertical_categories', [k for k,v in self.fields if v]) -# }}} - - class ConfigWidget(ConfigWidgetBase, Ui_Form): def genesis(self, gui): @@ -289,17 +52,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r = self.register - self.default_author_link = DefaultAuthorLink(self.default_author_link_container) - self.default_author_link.changed_signal.connect(self.changed_signal) - r('bd_show_cover', gprefs) - r('bd_overlay_cover_size', gprefs) - r('book_details_comments_heading_pos', gprefs, choices=[ - (_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')]) - r('book_details_note_link_icon_width', gprefs) - self.id_links_button.clicked.connect(self.edit_id_link_rules) - - r('use_roman_numerals_for_series_number', config) - choices = [(_('Default'), 'default'), (_('Compact metadata'), 'alt1'), (_('All on 1 tab'), 'alt2')] r('edit_metadata_single_layout', gprefs, @@ -315,15 +67,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('edit_metadata_single_cc_label_length', gprefs) r('edit_metadata_templates_only_F2_on_booklist', gprefs) - self.display_model = DisplayedFields(self.gui.current_db, self.field_display_order) - self.display_model.dataChanged.connect(self.changed_signal) - self.field_display_order.setModel(self.display_model) - mu = partial(move_field_up, self.field_display_order, self.display_model) - md = partial(move_field_down, self.field_display_order, self.display_model) - self.df_up_button.clicked.connect(mu) - self.df_down_button.clicked.connect(md) - self.field_display_order.set_movement_functions(mu, md) - self.em_display_model = EMDisplayedFields(self.gui.current_db, self.em_display_order) self.em_display_model.dataChanged.connect(self.changed_signal) self.em_display_order.setModel(self.em_display_model) @@ -336,10 +79,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.em_import_layout_button.clicked.connect(partial(import_layout, self, model=self.em_display_model)) self.em_reset_layout_button.clicked.connect(partial(reset_layout, model=self.em_display_model)) - self.bd_vertical_cats_model = BDVerticalCats(self.gui.current_db, self.tb_hierarchy_tab.tb_hierarchical_cats) - self.bd_vertical_cats_model.dataChanged.connect(self.changed_signal) - self.bd_vertical_cats.setModel(self.bd_vertical_cats_model) - self.edit_rules = EditRules(self.tabWidget) self.edit_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.edit_rules, QIcon.ic('format-fill-color.png'), _('Column &coloring')) @@ -354,11 +93,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): 'Ctrl+Shift+F', QKeySequence.SequenceFormat.PortableText)] keys = [str(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in keys] - 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')() - self.css_highlighter.apply_theme(get_theme(None)) - self.css_highlighter.set_document(self.opt_book_details_css.document()) for i in range(self.tabWidget.count()): self.sections_view.addItem(QListWidgetItem(self.tabWidget.tabIcon(i), self.tabWidget.tabText(i).replace('&', ''))) self.sections_view.setCurrentRow(self.tabWidget.currentIndex()) @@ -371,58 +105,33 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def initial_tab_changed(self): self.sections_view.setCurrentRow(self.tabWidget.currentIndex()) - def edit_id_link_rules(self): - if IdLinksEditor(self).exec() == QDialog.DialogCode.Accepted: - self.changed_signal.emit() - def initialize(self): ConfigWidgetBase.initialize(self) - self.default_author_link.value = default_author_link() - self.display_model.initialize() self.em_display_model.initialize() - self.bd_vertical_cats_model.initialize() db = self.gui.current_db 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.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 restore_defaults(self): ConfigWidgetBase.restore_defaults(self) - self.default_author_link.value = DEFAULT_AUTHOR_LINK - self.display_model.restore_defaults() self.em_display_model.restore_defaults() - self.bd_vertical_cats_model.restore_defaults() self.edit_rules.clear() self.icon_rules.clear() self.changed_signal.emit() - self.opt_book_details_css.setPlainText(P('templates/book_details.css', allow_user_override=False, data=True).decode('utf-8')) def commit(self, *args): with BusyCursor(): - self.display_model.commit() self.em_display_model.commit() - self.bd_vertical_cats_model.commit() self.edit_rules.commit(self.gui.current_db.prefs) self.icon_rules.commit(self.gui.current_db.prefs) - 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) - if defcss == bcss: - bcss = None - set_data('templates/book_details.css', bcss) def refresh_gui(self, gui): - gui.book_details.book_info.refresh_css() m = gui.library_view.model() m.update_db_prefs_cache() m.beginResetModel(), m.endResetModel() gui.tags_view.model().reset_tag_browser() - gui.library_view.refresh_book_details(force=True) - gui.library_view.refresh_composite_edit() if __name__ == '__main__': diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index c554566888..c2a8de4f12 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -37,7 +37,7 @@ Cover &grid - + :/images/book.png:/images/book.png @@ -45,243 +45,6 @@ &Book details - - - - - Text styling - - - - - - - - - - - - Create rules to convert &identifiers into links - - - - - - - - 0 - 0 - - - - - - - - <p>Check the box if you want the category's values displayed on separate lines instead of separated by commas</p> - - - Categories on separate lines - - - - - - true - - - - - - - - - - Select displayed metadata - - - - - - Move down. Keyboard shortcut: Ctrl-Down arrow - - - - :/images/arrow-down.png:/images/arrow-down.png - - - - - - - Move up. Keyboard shortcut: Ctrl-Up arrow - - - - :/images/arrow-up.png:/images/arrow-up.png - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - - - - - - - <p>Note: <b>comments</b>-like columns will always - be displayed at the end unless their "Heading position" is - "Show heading to the side".</p> - - - true - - - - - - - - - - - - Show &cover - - - - - - - Show the size of the book's cover in pixels - - - Show cover &size - - - - - - - Use &Roman numerals for series - - - true - - - - - - - Qt::Horizontal - - - - 20 - 0 - - - - - - - - - - - - Show comments &heading: - - - opt_book_details_comments_heading_pos - - - - - - - - 0 - 0 - - - - - - - - Qt::Horizontal - - - - 10 - 0 - - - - - - - - Link and note icon si&ze: - - - opt_book_details_note_link_icon_width - - - - - - - Increase or decrease the size of the links and notes icons by this number. Larger -than one increases the icon size while smaller than one decreases it. - - - 1 - - - 0.5 - - - 4.0 - - - 0.1 - - - - - - - Qt::Horizontal - - - - 30 - 0 - - - - - - - @@ -631,6 +394,11 @@ columns". Editing with mouse clicks and the Tab key will be disabled.</p ConfigWidgetBase
calibre/gui2/preferences/look_feel_tabs/cover_grid.h
+ + BookDetailsTab + ConfigWidgetBase +
calibre/gui2/preferences/look_feel_tabs/book_details.h
+
TbDisplayTab ConfigWidgetBase diff --git a/src/calibre/gui2/preferences/look_feel_tabs/__init__.py b/src/calibre/gui2/preferences/look_feel_tabs/__init__.py index d183bbd909..4b2ad7211e 100644 --- a/src/calibre/gui2/preferences/look_feel_tabs/__init__.py +++ b/src/calibre/gui2/preferences/look_feel_tabs/__init__.py @@ -7,13 +7,76 @@ __docformat__ = 'restructuredtext en' import json -from qt.core import QAbstractListModel, QIcon, QItemSelectionModel, Qt +from qt.core import QAbstractListModel, QComboBox, QFormLayout, QIcon, QItemSelectionModel, QLineEdit, Qt, QVBoxLayout, QWidget, pyqtSignal +from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK 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 DefaultAuthorLink(QWidget): + + changed_signal = pyqtSignal() + + def __init__(self, parent): + QWidget.__init__(self, parent) + l = QVBoxLayout() + l.addWidget(self) + l.setContentsMargins(0, 0, 0, 0) + l = QFormLayout(self) + l.setContentsMargins(0, 0, 0, 0) + l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) + self.choices = c = QComboBox() + c.setMinimumContentsLength(30) + for text, data in [ + (_('Search for the author on Goodreads'), 'search-goodreads'), + (_('Search for the author on Amazon'), 'search-amzn'), + (_('Search for the author in your calibre library'), 'search-calibre'), + (_('Search for the author on Wikipedia'), 'search-wikipedia'), + (_('Search for the author on Google Books'), 'search-google'), + (_('Search for the book on Goodreads'), 'search-goodreads-book'), + (_('Search for the book on Amazon'), 'search-amzn-book'), + (_('Search for the book on Google Books'), 'search-google-book'), + (_('Use a custom search URL'), 'url'), + ]: + c.addItem(text, data) + l.addRow(_('Clicking on &author names should:'), c) + self.custom_url = u = QLineEdit(self) + u.setToolTip(_( + 'Enter the URL to search. It should contain the string {0}' + '\nwhich will be replaced by the author name. For example,' + '\n{1}').format('{author}', 'https://en.wikipedia.org/w/index.php?search={author}')) + u.textChanged.connect(self.changed_signal) + u.setPlaceholderText(_('Enter the URL')) + c.currentIndexChanged.connect(self.current_changed) + l.addRow(u) + self.current_changed() + c.currentIndexChanged.connect(self.changed_signal) + + @property + def value(self): + k = self.choices.currentData() + if k == 'url': + return self.custom_url.text() + return k if k != DEFAULT_AUTHOR_LINK else None + + @value.setter + def value(self, val): + i = self.choices.findData(val) + if i < 0: + i = self.choices.findData('url') + self.custom_url.setText(val) + self.choices.setCurrentIndex(i) + + def current_changed(self): + k = self.choices.currentData() + self.custom_url.setVisible(k == 'url') + + def restore_defaults(self): + self.value = DEFAULT_AUTHOR_LINK + + class DisplayedFields(QAbstractListModel): def __init__(self, db, parent=None, pref_name=None, category_icons=None): diff --git a/src/calibre/gui2/preferences/look_feel_tabs/book_details.py b/src/calibre/gui2/preferences/look_feel_tabs/book_details.py new file mode 100644 index 0000000000..1f295b1225 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/book_details.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2025, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from collections import defaultdict +from functools import partial + +from qt.core import ( + QDialog, + QDialogButtonBox, + QFormLayout, + QHeaderView, + QIcon, + QLabel, + QLineEdit, + QPushButton, + QSize, + Qt, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, +) + +from calibre.ebooks.metadata.sources.prefs import msprefs +from calibre.gui2 import config, default_author_link, error_dialog, gprefs +from calibre.gui2.preferences import LazyConfigWidgetBase +from calibre.gui2.preferences.look_feel_tabs import DisplayedFields, move_field_down, move_field_up +from calibre.gui2.preferences.look_feel_tabs.book_details_ui import Ui_Form +from calibre.gui2.widgets import BusyCursor +from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda +from calibre.utils.icu import sort_key +from calibre.utils.resources import set_data +from polyglot.builtins import iteritems + + +class IdLinksRuleEdit(Dialog): + + def __init__(self, key='', name='', template='', parent=None): + title = _('Edit rule') if key else _('Create a new rule') + Dialog.__init__(self, title=title, name='id-links-rule-editor', parent=parent) + self.key.setText(key), self.nw.setText(name), self.template.setText(template or 'https://example.com/{id}') + if self.size().height() < self.sizeHint().height(): + self.resize(self.sizeHint()) + + @property + def rule(self): + return self.key.text().lower(), self.nw.text(), self.template.text() + + def setup_ui(self): + self.l = l = QFormLayout(self) + l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) + l.addRow(QLabel(_( + 'The key of the identifier, for example, in isbn:XXX, the key is "isbn"'))) + self.key = k = QLineEdit(self) + l.addRow(_('&Key:'), k) + l.addRow(QLabel(_( + 'The name that will appear in the Book details panel'))) + self.nw = n = QLineEdit(self) + l.addRow(_('&Name:'), n) + la = QLabel(_( + 'The template used to create the link.' + ' The placeholder {0} in the template will be replaced' + ' with the actual identifier value. Use {1} to avoid the value' + ' being quoted.').format('{id}', '{id_unquoted}')) + la.setWordWrap(True) + l.addRow(la) + self.template = t = QLineEdit(self) + l.addRow(_('&Template:'), t) + t.selectAll() + t.setFocus(Qt.FocusReason.OtherFocusReason) + l.addWidget(self.bb) + + def accept(self): + r = self.rule + for i, which in enumerate([_('Key'), _('Name'), _('Template')]): + if not r[i]: + return error_dialog(self, _('Value needed'), _( + 'The %s field cannot be empty') % which, show=True) + Dialog.accept(self) + + +class IdLinksEditor(Dialog): + + def __init__(self, parent=None): + Dialog.__init__(self, title=_('Create rules for identifiers'), name='id-links-rules-editor', parent=parent) + + def setup_ui(self): + self.l = l = QVBoxLayout(self) + self.la = la = QLabel(_( + 'Create rules to convert identifiers into links.')) + la.setWordWrap(True) + l.addWidget(la) + items = [] + for k, lx in iteritems(msprefs['id_link_rules']): + for n, t in lx: + items.append((k, n, t)) + items.sort(key=lambda x: sort_key(x[1])) + self.table = t = QTableWidget(len(items), 3, self) + t.setHorizontalHeaderLabels([_('Key'), _('Name'), _('Template')]) + for r, (key, val, template) in enumerate(items): + t.setItem(r, 0, QTableWidgetItem(key)) + t.setItem(r, 1, QTableWidgetItem(val)) + t.setItem(r, 2, QTableWidgetItem(template)) + l.addWidget(t) + t.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) + self.cb = b = QPushButton(QIcon.ic('plus.png'), _('&Add rule'), self) + connect_lambda(b.clicked, self, lambda self: self.edit_rule()) + self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) + self.rb = b = QPushButton(QIcon.ic('minus.png'), _('&Remove rule'), self) + connect_lambda(b.clicked, self, lambda self: self.remove_rule()) + self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) + self.eb = b = QPushButton(QIcon.ic('modified.png'), _('&Edit rule'), self) + connect_lambda(b.clicked, self, lambda self: self.edit_rule(self.table.currentRow())) + self.bb.addButton(b, QDialogButtonBox.ButtonRole.ActionRole) + l.addWidget(self.bb) + + def sizeHint(self): + return QSize(700, 550) + + def accept(self): + rules = defaultdict(list) + for r in range(self.table.rowCount()): + def item(c): + return self.table.item(r, c).text() + rules[item(0)].append([item(1), item(2)]) + msprefs['id_link_rules'] = dict(rules) + Dialog.accept(self) + + def edit_rule(self, r=-1): + key = name = template = '' + if r > -1: + key, name, template = (self.table.item(r, c).text() for c in range(3)) + d = IdLinksRuleEdit(key, name, template, self) + if d.exec() == QDialog.DialogCode.Accepted: + if r < 0: + self.table.setRowCount(self.table.rowCount() + 1) + r = self.table.rowCount() - 1 + rule = d.rule + for c in range(3): + self.table.setItem(r, c, QTableWidgetItem(rule[c])) + self.table.scrollToItem(self.table.item(r, 0)) + + def remove_rule(self): + r = self.table.currentRow() + if r > -1: + self.table.removeRow(r) + + +class BDVerticalCats(DisplayedFields): + + def __init__(self, db, parent=None, category_icons=None): + DisplayedFields.__init__(self, db, parent, category_icons=category_icons) + from calibre.gui2.ui import get_gui + self.gui = get_gui() + + def initialize(self, use_defaults=False, pref_data_override=None): + fm = self.db.field_metadata + cats = [k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and not k.startswith('#')] + cats.append('path') + cats.extend([k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and k.startswith('#')]) + ans = [] + if use_defaults: + ans = [[k, False] for k in cats] + self.changed = True + elif pref_data_override: + ph = dict(pref_data_override) + ans = [[k, ph.get(k, False)] for k in cats] + self.changed = True + else: + vertical_cats = self.db.prefs.get('book_details_vertical_categories') or () + for key in cats: + ans.append([key, key in vertical_cats]) + self.beginResetModel() + self.fields = ans + self.endResetModel() + + def commit(self): + if self.changed: + self.db.prefs.set('book_details_vertical_categories', [k for k,v in self.fields if v]) + + +class BookDetailsTab(LazyConfigWidgetBase, Ui_Form): + + def genesis(self, gui): + self.gui = gui + r = self.register + + self.default_author_link.changed_signal.connect(self.changed_signal) + r('bd_show_cover', gprefs) + r('bd_overlay_cover_size', gprefs) + r('book_details_comments_heading_pos', gprefs, choices=[ + (_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')]) + r('book_details_note_link_icon_width', gprefs) + self.id_links_button.clicked.connect(self.edit_id_link_rules) + + r('use_roman_numerals_for_series_number', config) + + self.bd_vertical_cats_model = BDVerticalCats(self.gui.current_db, parent=self) + self.bd_vertical_cats_model.dataChanged.connect(self.changed_signal) + self.bd_vertical_cats.setModel(self.bd_vertical_cats_model) + + self.display_model = DisplayedFields(self.gui.current_db, self.field_display_order) + self.display_model.dataChanged.connect(self.changed_signal) + self.field_display_order.setModel(self.display_model) + mu = partial(move_field_up, self.field_display_order, self.display_model) + md = partial(move_field_down, self.field_display_order, self.display_model) + self.df_up_button.clicked.connect(mu) + self.df_down_button.clicked.connect(md) + self.field_display_order.set_movement_functions(mu, md) + + 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')() + self.css_highlighter.apply_theme(get_theme(None)) + self.css_highlighter.set_document(self.opt_book_details_css.document()) + + def lazy_initialize(self): + self.default_author_link.value = default_author_link() + self.display_model.initialize() + self.blockSignals(True) + self.bd_vertical_cats_model.initialize() + self.opt_book_details_css.setPlainText(P('templates/book_details.css', data=True).decode('utf-8')) + self.blockSignals(False) + + def edit_id_link_rules(self): + if IdLinksEditor(self).exec() == QDialog.DialogCode.Accepted: + self.changed_signal.emit() + + def commit(self): + with BusyCursor(): + self.display_model.commit() + self.bd_vertical_cats_model.commit() + 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) + if defcss == bcss: + bcss = None + set_data('templates/book_details.css', bcss) + return LazyConfigWidgetBase.commit(self) + + def restore_defaults(self): + LazyConfigWidgetBase.restore_defaults(self) + self.default_author_link.restore_defaults() + self.display_model.restore_defaults() + self.bd_vertical_cats_model.restore_defaults() + self.opt_book_details_css.setPlainText(P('templates/book_details.css', allow_user_override=False, data=True).decode('utf-8')) + self.changed_signal.emit() + + def refresh_gui(self, gui): + gui.book_details.book_info.refresh_css() + gui.library_view.refresh_book_details(force=True) + gui.library_view.refresh_composite_edit() diff --git a/src/calibre/gui2/preferences/look_feel_tabs/book_details.ui b/src/calibre/gui2/preferences/look_feel_tabs/book_details.ui new file mode 100644 index 0000000000..6b974614c5 --- /dev/null +++ b/src/calibre/gui2/preferences/look_feel_tabs/book_details.ui @@ -0,0 +1,258 @@ + + + Form + + + + + + Text styling + + + + + + + + + + + + Create rules to convert &identifiers into links + + + + + + + + 0 + 0 + + + + + + + + <p>Check the box if you want the category's values displayed on separate lines instead of separated by commas</p> + + + Categories on separate lines + + + + + + true + + + + + + + + + + Select displayed metadata + + + + + + Move down. Keyboard shortcut: Ctrl-Down arrow + + + + :/images/arrow-down.png:/images/arrow-down.png + + + + + + + Move up. Keyboard shortcut: Ctrl-Up arrow + + + + :/images/arrow-up.png:/images/arrow-up.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + + <p>Note: <b>comments</b>-like columns will always + be displayed at the end unless their "Heading position" is + "Show heading to the side".</p> + + + true + + + + + + + + + + + + Show &cover + + + + + + + Show the size of the book's cover in pixels + + + Show cover &size + + + + + + + Use &Roman numerals for series + + + true + + + + + + + Qt::Horizontal + + + + 20 + 0 + + + + + + + + + + + + Show comments &heading: + + + opt_book_details_comments_heading_pos + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 10 + 0 + + + + + + + + Link and note icon si&ze: + + + opt_book_details_note_link_icon_width + + + + + + + Increase or decrease the size of the links and notes icons by this number. Larger +than one increases the icon size while smaller than one decreases it. + + + 1 + + + 0.5 + + + 4.0 + + + 0.1 + + + + + + + Qt::Horizontal + + + + 30 + 0 + + + + + + + + + + + ListViewWithMoveByKeyPress + QListView +
calibre/gui2/preferences.h
+
+ + DefaultAuthorLink + QWidget +
calibre/gui2/preferences/look_feel_tabs.h
+
+
+ + + +