%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/preferences/ |
Current File : //lib/calibre/calibre/gui2/preferences/look_feel.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import json from collections import defaultdict from threading import Thread from qt.core import ( QApplication, QFont, QFontInfo, QFontDialog, QColorDialog, QPainter, QDialog, QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal, QCursor, QListWidgetItem, QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout, QItemSelectionModel, QTableWidget, QTableWidgetItem, QLabel, QFormLayout, QLineEdit, QComboBox, QDialogButtonBox ) from calibre import human_readable from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK from calibre.constants import ismacos, iswindows from calibre.ebooks.metadata.sources.prefs import msprefs from calibre.gui2 import default_author_link from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog, error_dialog from calibre.utils.localization import (available_translations, get_language, get_lang) from calibre.utils.config import prefs from calibre.utils.icu import sort_key from calibre.gui2.book_details import get_field_list from calibre.gui2.dialogs.quickview import get_qv_field_list from calibre.gui2.preferences.coloring import EditRules from calibre.gui2.library.alternate_views import auto_height, CM_TO_INCH from calibre.gui2.widgets2 import Dialog from calibre.gui2.actions.show_quickview import get_quickview_action_plugin from calibre.utils.resources import set_data from polyglot.builtins import iteritems class BusyCursor: def __enter__(self): QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor)) def __exit__(self, *args): QApplication.restoreOverrideCursor() 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, t.horizontalHeader().Stretch) self.cb = b = QPushButton(QIcon(I('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(I('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(I('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 = map(lambda c: self.table.item(r, c).text(), 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 DisplayedFields(QAbstractListModel): # {{{ def __init__(self, db, parent=None, pref_name=None): self.pref_name = pref_name or 'book_display_fields' QAbstractListModel.__init__(self, parent) self.fields = [] self.db = db self.changed = False def get_field_list(self, use_defaults=False): return get_field_list(self.db.field_metadata, use_defaults=use_defaults, pref_name=self.pref_name) def initialize(self, use_defaults=False): self.beginResetModel() self.fields = [[x[0], x[1]] for x in self.get_field_list(use_defaults=use_defaults)] self.endResetModel() self.changed = True def rowCount(self, *args): return len(self.fields) def data(self, index, role): try: field, visible = self.fields[index.row()] except: return None if role == Qt.ItemDataRole.DisplayRole: name = field try: name = self.db.field_metadata[field]['name'] except: pass if not name: return field return f'{name} ({field})' if role == Qt.ItemDataRole.CheckStateRole: return Qt.CheckState.Checked if visible else Qt.CheckState.Unchecked if role == Qt.ItemDataRole.DecorationRole and field.startswith('#'): return QIcon(I('column.png')) return None def toggle_all(self, show=True): for i in range(self.rowCount()): idx = self.index(i) if idx.isValid(): self.setData(idx, show, Qt.ItemDataRole.CheckStateRole) def flags(self, index): ans = QAbstractListModel.flags(self, index) return ans | Qt.ItemFlag.ItemIsUserCheckable def setData(self, index, val, role): ret = False if role == Qt.ItemDataRole.CheckStateRole: self.fields[index.row()][1] = bool(val) self.changed = True ret = True self.dataChanged.emit(index, index) return ret def restore_defaults(self): self.initialize(use_defaults=True) def commit(self): if self.changed: self.db.new_api.set_pref(self.pref_name, self.fields) def move(self, idx, delta): row = idx.row() + delta if row >= 0 and row < len(self.fields): t = self.fields[row] self.fields[row] = self.fields[row-delta] self.fields[row-delta] = t self.dataChanged.emit(idx, idx) idx = self.index(row) self.dataChanged.emit(idx, idx) self.changed = True return idx def move_field_up(widget, model): idx = widget.currentIndex() if idx.isValid(): idx = model.move(idx, -1) if idx is not None: sm = widget.selectionModel() sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect) widget.setCurrentIndex(idx) def move_field_down(widget, model): idx = widget.currentIndex() if idx.isValid(): idx = model.move(idx, 1) if idx is not None: sm = widget.selectionModel() sm.select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect) widget.setCurrentIndex(idx) # }}} class EMDisplayedFields(DisplayedFields): # {{{ def __init__(self, db, parent=None): DisplayedFields.__init__(self, db, parent) def initialize(self, use_defaults=False): self.beginResetModel() self.fields = [[x[0], x[1]] for x in em_get_field_list(self.db, use_defaults=use_defaults)] self.endResetModel() self.changed = True def commit(self): if self.changed: self.db.new_api.set_pref('edit_metadata_custom_columns_to_display', self.fields) # }}} class QVDisplayedFields(DisplayedFields): # {{{ def __init__(self, db, parent=None): DisplayedFields.__init__(self, db, parent) def initialize(self, use_defaults=False): self.beginResetModel() self.fields = [[x[0], x[1]] for x in get_qv_field_list(self.db.field_metadata, use_defaults=use_defaults)] self.endResetModel() self.changed = True def commit(self): if self.changed: self.db.new_api.set_pref('qv_display_fields', self.fields) # }}} 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 if not ismacos and not iswindows: self.label_widget_style.setVisible(False) self.opt_ui_style.setVisible(False) db = gui.library_view.model().db r = self.register try: self.icon_theme_title = json.loads(I('icon-theme.json', data=True))['name'] except Exception: self.icon_theme_title = _('Default icons') self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title) self.commit_icon_theme = None self.icon_theme_button.clicked.connect(self.choose_icon_theme) self.default_author_link = DefaultAuthorLink(self.default_author_link_container) self.default_author_link.changed_signal.connect(self.changed_signal) r('gui_layout', config, restart_required=True, choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')]) r('hidpi', gprefs, restart_required=True, choices=[(_('Automatic'), 'auto'), (_('On'), 'on'), (_('Off'), 'off')]) if ismacos: self.opt_hidpi.setVisible(False), self.label_hidpi.setVisible(False) r('ui_style', gprefs, restart_required=True, choices=[(_('System default'), 'system'), (_('calibre style'), 'calibre')]) r('book_list_tooltips', gprefs) r('dnd_merge', gprefs) r('wrap_toolbar_text', gprefs, restart_required=True) r('show_layout_buttons', gprefs, restart_required=True) r('row_numbers_in_book_list', gprefs) r('tag_browser_old_look', gprefs) r('tag_browser_hide_empty_categories', gprefs) r('tag_browser_always_autocollapse', gprefs) r('tag_browser_show_tooltips', gprefs) r('tag_browser_allow_keyboard_focus', gprefs) 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('tag_browser_show_counts', gprefs) r('tag_browser_item_padding', gprefs) r('books_autoscroll_time', gprefs) r('qv_respects_vls', gprefs) r('qv_dclick_changes_column', gprefs) r('qv_retkey_changes_column', gprefs) r('qv_follows_column', gprefs) r('cover_flow_queue_length', config, restart_required=True) r('cover_browser_reflections', gprefs) r('cover_browser_title_template', db.prefs) fm = db.field_metadata r('cover_browser_subtitle_field', db.prefs, choices=[(_('No subtitle'), 'none')] + sorted( (fm[k].get('name'), k) for k in fm.all_field_keys() if fm[k].get('name') )) r('emblem_size', gprefs) r('emblem_position', gprefs, choices=[ (_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')]) r('book_list_extra_row_spacing', gprefs) r('booklist_grid', gprefs) r('book_details_comments_heading_pos', gprefs, choices=[ (_('Never'), 'hide'), (_('Above text'), 'above'), (_('Beside text'), 'side')]) self.cover_browser_title_template_button.clicked.connect(self.edit_cb_title_template) self.id_links_button.clicked.connect(self.edit_id_link_rules) def get_esc_lang(l): if l == 'en': return 'English' return get_language(l) lang = get_lang() if lang is None or lang not in available_translations(): lang = 'en' items = [(l, get_esc_lang(l)) for l in available_translations() if l != lang] if lang != 'en': items.append(('en', get_esc_lang('en'))) items.sort(key=lambda x: x[1].lower()) choices = [(y, x) for x, y in items] # Default language is the autodetected one choices = [(get_language(lang), lang)] + choices r('language', prefs, choices=choices, restart_required=True) r('show_avg_rating', config) r('disable_animations', config) r('systray_icon', config, restart_required=True) r('show_splash_screen', gprefs) r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) r('separate_cover_flow', config, restart_required=True) r('cb_fullscreen', gprefs) r('cb_preserve_aspect_ratio', gprefs) r('cb_double_click_to_activate', gprefs) choices = [(_('Off'), 'off'), (_('Small'), 'small'), (_('Medium'), 'medium'), (_('Large'), 'large')] r('toolbar_icon_size', gprefs, choices=choices) choices = [(_('If there is enough room'), 'auto'), (_('Always'), 'always'), (_('Never'), 'never')] r('toolbar_text', gprefs, choices=choices) choices = [(_('Disabled'), 'disable'), (_('By first letter'), 'first letter'), (_('Partitioned'), 'partition')] r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) r('tags_browser_collapse_fl_at', gprefs) choices = {k for k in db.field_metadata.all_field_keys() if (db.field_metadata[k]['is_category'] and ( db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration' ]) and not db.field_metadata[k]['display'].get('is_names', False)) or ( db.field_metadata[k]['datatype'] in ['composite' ] and db.field_metadata[k]['display'].get('make_category', False))} choices |= {'search'} r('tag_browser_dont_collapse', gprefs, setting=CommaSeparatedList, choices=sorted(choices, key=sort_key)) choices -= {'authors', 'publisher', 'formats', 'news', 'identifiers'} r('categories_using_hierarchy', db.prefs, setting=CommaSeparatedList, choices=sorted(choices, key=sort_key)) 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, choices=[(_('Default'), 'default'), (_('Compact metadata'), 'alt1'), (_('All on 1 tab'), 'alt2')]) r('edit_metadata_ignore_display_order', db.prefs) r('edit_metadata_elision_point', gprefs, choices=[(_('Left'), 'left'), (_('Middle'), 'middle'), (_('Right'), 'right')]) r('edit_metadata_elide_labels', gprefs) r('edit_metadata_single_use_2_cols_for_custom_fields', gprefs) r('edit_metadata_bulk_cc_label_length', gprefs) r('edit_metadata_single_cc_label_length', gprefs) self.current_font = self.initial_font = None self.change_font_button.clicked.connect(self.change_font) 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) connect_lambda(self.df_up_button.clicked, self, lambda self: move_field_up(self.field_display_order, self.display_model)) connect_lambda(self.df_down_button.clicked, self, lambda self: move_field_down(self.field_display_order, self.display_model)) 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) connect_lambda(self.em_up_button.clicked, self, lambda self: move_field_up(self.em_display_order, self.em_display_model)) connect_lambda(self.em_down_button.clicked, self, lambda self: move_field_down(self.em_display_order, self.em_display_model)) self.qv_display_model = QVDisplayedFields(self.gui.current_db, self.qv_display_order) self.qv_display_model.dataChanged.connect(self.changed_signal) self.qv_display_order.setModel(self.qv_display_model) connect_lambda(self.qv_up_button.clicked, self, lambda self: move_field_up(self.qv_display_order, self.qv_display_model)) connect_lambda(self.qv_down_button.clicked, self, lambda self: move_field_down(self.qv_display_order, self.qv_display_model)) self.edit_rules = EditRules(self.tabWidget) self.edit_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.edit_rules, QIcon(I('format-fill-color.png')), _('Column &coloring')) self.icon_rules = EditRules(self.tabWidget) self.icon_rules.changed.connect(self.changed_signal) self.tabWidget.addTab(self.icon_rules, QIcon(I('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.fs_help_msg.setText(self.fs_help_msg.text()%( QKeySequence(QKeySequence.StandardKey.FullScreen).toString(QKeySequence.SequenceFormat.NativeText))) 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')() 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()) self.sections_view.currentRowChanged.connect(self.tabWidget.setCurrentIndex) self.sections_view.setMaximumWidth(self.sections_view.sizeHintForColumn(0) + 16) self.sections_view.setSpacing(4) self.sections_view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.tabWidget.currentWidget().setFocus(Qt.FocusReason.OtherFocusReason) def choose_icon_theme(self): from calibre.gui2.icon_theme import ChooseTheme d = ChooseTheme(self) if d.exec() == QDialog.DialogCode.Accepted: self.commit_icon_theme = d.commit_changes self.icon_theme_title = d.new_theme_title or _('Default icons') self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title) self.changed_signal.emit() def edit_id_link_rules(self): 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 edit_cb_title_template(self): t = TemplateDialog(self, self.opt_cover_browser_title_template.text(), fm=self.gui.current_db.field_metadata) t.setWindowTitle(_('Edit template for caption')) if t.exec(): self.opt_cover_browser_title_template.setText(t.rule[1]) def initialize(self): ConfigWidgetBase.initialize(self) self.default_author_link.value = default_author_link() font = gprefs['font'] if font is not None: font = list(font) font.append(gprefs.get('font_stretch', QFont.Stretch.Unstretched)) self.current_font = self.initial_font = font self.update_font_display() self.display_model.initialize() self.em_display_model.initialize() self.qv_display_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 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) self.tb_focus_label.setVisible(self.opt_tag_browser_allow_keyboard_focus.isChecked()) 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 ofont = self.current_font self.current_font = None if ofont is not None: self.changed_signal.emit() self.update_font_display() self.display_model.restore_defaults() self.em_display_model.restore_defaults() self.qv_display_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 build_font_obj(self): font_info = qt_app.original_font if self.current_font is None else self.current_font font = QFont(*(font_info[:4])) font.setStretch(font_info[4]) return font def update_font_display(self): font = self.build_font_obj() fi = QFontInfo(font) name = str(fi.family()) self.font_display.setFont(font) self.font_display.setText(name + ' [%dpt]'%fi.pointSize()) def change_font(self, *args): fd = QFontDialog(self.build_font_obj(), self) if fd.exec() == QDialog.DialogCode.Accepted: font = fd.selectedFont() fi = QFontInfo(font) self.current_font = [str(fi.family()), fi.pointSize(), fi.weight(), fi.italic(), font.stretch()] self.update_font_display() self.changed_signal.emit() def commit(self, *args): with BusyCursor(): rr = ConfigWidgetBase.commit(self, *args) if self.current_font != self.initial_font: gprefs['font'] = (self.current_font[:4] if self.current_font else None) gprefs['font_stretch'] = (self.current_font[4] if self.current_font is not None else QFont.Stretch.Unstretched) QApplication.setFont(self.font_display.font()) rr = True self.display_model.commit() self.em_display_model.commit() self.qv_display_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 if self.commit_icon_theme is not None: self.commit_icon_theme() rr = True 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 rr def refresh_gui(self, gui): gui.book_details.book_info.refresh_css() m = gui.library_view.model() m.beginResetModel(), m.endResetModel() self.update_font_display() gui.tags_view.set_look_and_feel() gui.tags_view.reread_collapse_parameters() gui.library_view.refresh_book_details(force=True) gui.library_view.refresh_grid() gui.library_view.set_row_header_visibility() gui.cover_flow.setShowReflections(gprefs['cover_browser_reflections']) gui.cover_flow.setPreserveAspectRatio(gprefs['cb_preserve_aspect_ratio']) gui.cover_flow.setActivateOnDoubleClick(gprefs['cb_double_click_to_activate']) gui.update_cover_flow_subtitle_font() gui.cover_flow.template_inited = False for view in 'library memory card_a card_b'.split(): getattr(gui, view + '_view').set_row_header_visibility() gui.library_view.refresh_row_sizing() gui.grid_view.refresh_settings() gui.update_auto_scroll_timeout() qv = get_quickview_action_plugin() if qv: qv.refill_quickview() if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) test_widget('Interface', 'Look & Feel')