%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/ |
Current File : //lib/calibre/calibre/gui2/init.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import functools from qt.core import ( QAction, QApplication, QDialog, QEvent, QIcon, QLabel, QMenu, QStylePainter, QSizePolicy, QSplitter, QStackedWidget, QStatusBar, QStyle, QStyleOption, Qt, QTabBar, QTimer, QToolButton, QVBoxLayout, QWidget ) from calibre.constants import get_appname_for_display, get_version, ismacos from calibre.customize.ui import find_plugin from calibre.gui2 import ( config, error_dialog, gprefs, is_widescreen, open_local_file, open_url ) from calibre.gui2.book_details import BookDetails from calibre.gui2.layout_menu import LayoutMenu from calibre.gui2.library.alternate_views import GridView from calibre.gui2.library.views import BooksView, DeviceBooksView from calibre.gui2.notify import get_notifier from calibre.gui2.tag_browser.ui import TagBrowserWidget from calibre.gui2.widgets import LayoutButton, Splitter from calibre.utils.config import prefs from calibre.utils.icu import sort_key from calibre.utils.localization import localize_website_link _keep_refs = [] def partial(*args, **kwargs): ans = functools.partial(*args, **kwargs) _keep_refs.append(ans) return ans class LibraryViewMixin: # {{{ def __init__(self, *args, **kwargs): pass def init_library_view_mixin(self, db): self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.ConnectionType.QueuedConnection) self.library_view.books_dropped.connect(self.iactions['Edit Metadata'].books_dropped, type=Qt.ConnectionType.QueuedConnection) self.library_view.add_column_signal.connect(partial(self.iactions['Preferences'].do_config, initial_plugin=('Interface', 'Custom Columns'), close_after_initial=True), type=Qt.ConnectionType.QueuedConnection) for func, args in [ ('connect_to_search_box', (self.search, self.search_done)), ('connect_to_book_display', (self.book_details.show_data,)), ]: for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view): getattr(view, func)(*args) self.memory_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.memory_view.connect_upload_collections_signal( func=self.upload_collections, oncard=None) self.card_a_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.card_a_view.connect_upload_collections_signal( func=self.upload_collections, oncard='carda') self.card_b_view.connect_dirtied_signal(self.upload_dirtied_booklists) self.card_b_view.connect_upload_collections_signal( func=self.upload_collections, oncard='cardb') self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) self.library_view.set_database(db) self.library_view.model().set_book_on_device_func(self.book_on_device) prefs['library_path'] = self.library_path for view in ('library', 'memory', 'card_a', 'card_b'): view = getattr(self, view+'_view') view.verticalHeader().sectionDoubleClicked.connect(self.iactions['View'].view_specific_book) self.library_view.model().set_highlight_only(config['highlight_search_matches']) def build_context_menus(self): from calibre.gui2.bars import populate_menu lm = QMenu(self) populate_menu(lm, gprefs['action-layout-context-menu'], self.iactions) dm = QMenu(self) populate_menu(dm, gprefs['action-layout-context-menu-device'], self.iactions) ec = self.iactions['Edit Collections'].qaction self.library_view.set_context_menu(lm, ec) sm = QMenu(self) populate_menu(sm, gprefs['action-layout-context-menu-split'], self.iactions) self.library_view.pin_view.set_context_menu(sm) for v in (self.memory_view, self.card_a_view, self.card_b_view): v.set_context_menu(dm, ec) if hasattr(self.cover_flow, 'set_context_menu'): cm = QMenu(self.cover_flow) populate_menu(cm, gprefs['action-layout-context-menu-cover-browser'], self.iactions) self.cover_flow.set_context_menu(cm) def search_done(self, view, ok): if view is self.current_view(): self.search.search_done(ok) self.set_number_of_books_shown() if ok: v = self.current_view() if hasattr(v, 'set_current_row'): v.set_current_row(0) if v is self.library_view and v.row_count() == 0: self.book_details.reset_info() # }}} class QuickviewSplitter(QSplitter): # {{{ def __init__(self, parent=None, orientation=Qt.Orientation.Vertical, qv_widget=None): QSplitter.__init__(self, parent=parent, orientation=orientation) self.splitterMoved.connect(self.splitter_moved) self.setChildrenCollapsible(False) self.qv_widget = qv_widget def splitter_moved(self): gprefs['quickview_dialog_heights'] = self.sizes() def resizeEvent(self, *args): QSplitter.resizeEvent(self, *args) if self.sizes()[1] != 0: gprefs['quickview_dialog_heights'] = self.sizes() def set_sizes(self): sizes = gprefs.get('quickview_dialog_heights', []) if len(sizes) == 2: self.setSizes(sizes) def add_quickview_dialog(self, qv_dialog): self.qv_widget.layout().addWidget(qv_dialog) def show_quickview_widget(self): self.qv_widget.show() def hide_quickview_widget(self): self.qv_widget.hide() # }}} class LibraryWidget(Splitter): # {{{ def __init__(self, parent): orientation = Qt.Orientation.Vertical if config['gui_layout'] == 'narrow': orientation = Qt.Orientation.Horizontal if is_widescreen() else Qt.Orientation.Vertical idx = 0 if orientation == Qt.Orientation.Vertical else 1 size = 300 if orientation == Qt.Orientation.Vertical else 550 Splitter.__init__(self, 'cover_browser_splitter', _('Cover browser'), I('cover_flow.png'), orientation=orientation, parent=parent, connect_button=not config['separate_cover_flow'], side_index=idx, initial_side_size=size, initial_show=False, shortcut='Shift+Alt+B') quickview_widget = QWidget() parent.quickview_splitter = QuickviewSplitter( parent=self, orientation=Qt.Orientation.Vertical, qv_widget=quickview_widget) parent.library_view = BooksView(parent) parent.library_view.setObjectName('library_view') stack = QStackedWidget(self) av = parent.library_view.alternate_views parent.pin_container = av.set_stack(stack) parent.grid_view = GridView(parent) parent.grid_view.setObjectName('grid_view') av.add_view('grid', parent.grid_view) parent.quickview_splitter.addWidget(stack) l = QVBoxLayout() l.setContentsMargins(4, 0, 0, 0) quickview_widget.setLayout(l) parent.quickview_splitter.addWidget(quickview_widget) parent.quickview_splitter.hide_quickview_widget() self.addWidget(parent.quickview_splitter) # }}} class Stack(QStackedWidget): # {{{ def __init__(self, parent): QStackedWidget.__init__(self, parent) parent.cb_splitter = LibraryWidget(parent) self.tb_widget = TagBrowserWidget(parent) parent.tb_splitter = Splitter('tag_browser_splitter', _('Tag browser'), I('tags.png'), parent=parent, side_index=0, initial_side_size=200, shortcut='Shift+Alt+T') parent.tb_splitter.state_changed.connect( self.tb_widget.set_pane_is_visible, Qt.ConnectionType.QueuedConnection) parent.tb_splitter.addWidget(self.tb_widget) parent.tb_splitter.addWidget(parent.cb_splitter) parent.tb_splitter.setCollapsible(parent.tb_splitter.other_index, False) self.addWidget(parent.tb_splitter) for x in ('memory', 'card_a', 'card_b'): name = x+'_view' w = DeviceBooksView(parent) setattr(parent, name, w) self.addWidget(w) w.setObjectName(name) # }}} class UpdateLabel(QLabel): # {{{ def __init__(self, *args, **kwargs): QLabel.__init__(self, *args, **kwargs) self.setCursor(Qt.CursorShape.PointingHandCursor) def contextMenuEvent(self, e): pass # }}} class VersionLabel(QLabel): # {{{ def __init__(self, parent): QLabel.__init__(self, parent) self.mouse_over = False self.setCursor(Qt.CursorShape.PointingHandCursor) self.setToolTip(_('See what\'s new in this calibre release')) def mouseReleaseEvent(self, ev): open_url(localize_website_link('https://calibre-ebook.com/whats-new')) ev.accept() return QLabel.mouseReleaseEvent(self, ev) def event(self, ev): m = None et = ev.type() if et == QEvent.Type.Enter: m = True elif et == QEvent.Type.Leave: m = False if m is not None and m != self.mouse_over: self.mouse_over = m self.update() return QLabel.event(self, ev) def paintEvent(self, ev): if self.mouse_over: p = QStylePainter(self) tool = QStyleOption() tool.initFrom(self) tool.rect = self.rect() tool.state = QStyle.StateFlag.State_Raised | QStyle.StateFlag.State_Active | QStyle.StateFlag.State_MouseOver p.drawPrimitive(QStyle.PrimitiveElement.PE_PanelButtonTool, tool) p.end() return QLabel.paintEvent(self, ev) # }}} class StatusBar(QStatusBar): # {{{ def __init__(self, parent=None): QStatusBar.__init__(self, parent) self.version = get_version() self.base_msg = f'{get_appname_for_display()} {self.version}' self.device_string = '' self.update_label = UpdateLabel('') self.total = self.current = self.selected = self.library_total = 0 self.addPermanentWidget(self.update_label) self.update_label.setVisible(False) self.defmsg = VersionLabel(self) self.addWidget(self.defmsg) self.set_label() def initialize(self, systray=None): self.systray = systray self.notifier = get_notifier(systray) def device_connected(self, devname): self.device_string = _('Connected ') + devname self.set_label() def update_state(self, library_total, total, current, selected): self.library_total = library_total self.total, self.current, self.selected = total, current, selected self.set_label() def set_label(self): try: self._set_label() except: import traceback traceback.print_exc() def _set_label(self): msg = self.base_msg if self.device_string: msg += ' ..::.. ' + self.device_string else: msg += _(' %(created)s %(name)s') % dict(created=_('created by'), name='Kovid Goyal') if self.total != self.current: base = _('%(num)d of %(total)d books') % dict(num=self.current, total=self.total) else: base = ngettext('one book', '{} books', self.total).format(self.total) if self.selected > 0: base = ngettext('%(num)s, %(sel)d selected', '%(num)s, %(sel)d selected', self.selected) % dict(num=base, sel=self.selected) if self.library_total != self.total: base = _('{0}, {1} total').format(base, self.library_total) self.defmsg.setText(f'\xa0{msg}\xa0\xa0\xa0\xa0[{base}] ') self.clearMessage() def device_disconnected(self): self.device_string = '' self.set_label() def show_message(self, msg, timeout=0, show_notification=True): self.showMessage(msg, timeout) if self.notifier is not None and not config['disable_tray_notification'] and show_notification: self.notifier(msg) def clear_message(self): self.clearMessage() # }}} class GridViewButton(LayoutButton): # {{{ def __init__(self, gui): sc = 'Alt+Shift+G' LayoutButton.__init__(self, I('grid.png'), _('Cover grid'), parent=gui, shortcut=sc) self.set_state_to_show() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('grid view toggle' + self.label, str(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.action_toggle.changed.connect(self.update_shortcut) self.toggled.connect(self.update_state) def update_state(self, checked): if checked: self.set_state_to_hide() else: self.set_state_to_show() def save_state(self): gprefs['grid view visible'] = bool(self.isChecked()) def restore_state(self): if gprefs.get('grid view visible', False): self.toggle() # }}} class SearchBarButton(LayoutButton): # {{{ def __init__(self, gui): sc = 'Alt+Shift+F' LayoutButton.__init__(self, I('search.png'), _('Search bar'), parent=gui, shortcut=sc) self.set_state_to_hide() self.action_toggle = QAction(self.icon(), _('Toggle') + ' ' + self.label, self) gui.addAction(self.action_toggle) gui.keyboard.register_shortcut('search bar toggle' + self.label, str(self.action_toggle.text()), default_keys=(sc,), action=self.action_toggle) self.action_toggle.triggered.connect(self.toggle) self.action_toggle.changed.connect(self.update_shortcut) self.toggled.connect(self.update_state) def update_state(self, checked): if checked: self.set_state_to_hide() else: self.set_state_to_show() def save_state(self): gprefs['search bar visible'] = bool(self.isChecked()) def restore_state(self): self.setChecked(bool(gprefs.get('search bar visible', True))) # }}} class VLTabs(QTabBar): # {{{ def __init__(self, parent): QTabBar.__init__(self, parent) self.setDocumentMode(True) self.setDrawBase(False) self.setMovable(True) self.setTabsClosable(gprefs['vl_tabs_closable']) self.gui = parent self.ignore_tab_changed = False self.currentChanged.connect(self.tab_changed) self.tabMoved.connect(self.tab_moved, type=Qt.ConnectionType.QueuedConnection) self.tabCloseRequested.connect(self.tab_close) self.setVisible(gprefs['show_vl_tabs']) self.next_action = a = QAction(self) a.triggered.connect(partial(self.next_tab, delta=1)), self.gui.addAction(a) self.previous_action = a = QAction(self) a.triggered.connect(partial(self.next_tab, delta=-1)), self.gui.addAction(a) self.gui.keyboard.register_shortcut( 'virtual-library-tab-bar-next', _('Next Virtual library'), action=self.next_action, default_keys=('Ctrl+Right',), description=_('Switch to the next Virtual library in the Virtual library tab bar') ) self.gui.keyboard.register_shortcut( 'virtual-library-tab-bar-previous', _('Previous Virtual library'), action=self.previous_action, default_keys=('Ctrl+Left',), description=_('Switch to the previous Virtual library in the Virtual library tab bar') ) def next_tab(self, delta=1): if self.count() > 1 and self.isVisible(): idx = (self.currentIndex() + delta) % self.count() self.setCurrentIndex(idx) def enable_bar(self): gprefs['show_vl_tabs'] = True self.setVisible(True) self.gui.set_number_of_books_shown() def disable_bar(self): gprefs['show_vl_tabs'] = False self.setVisible(False) self.gui.set_number_of_books_shown() def lock_tab(self): gprefs['vl_tabs_closable'] = False self.setTabsClosable(False) def unlock_tab(self): gprefs['vl_tabs_closable'] = True self.setTabsClosable(True) try: self.tabButton(0, QTabBar.ButtonPosition.RightSide).setVisible(False) except AttributeError: try: self.tabButton(0, QTabBar.ButtonPosition.LeftSide).setVisible(False) except AttributeError: # On some OS X machines (using native style) the tab button is # on the left pass def tab_changed(self, idx): if self.ignore_tab_changed: return vl = str(self.tabData(idx) or '').strip() or None self.gui.apply_virtual_library(vl, update_tabs=False) def tab_moved(self, from_, to): self.current_db.new_api.set_pref('virt_libs_order', [str(self.tabData(i) or '') for i in range(self.count())]) def tab_close(self, index): vl = str(self.tabData(index) or '') if vl: # Dont allow closing the All Books tab self.current_db.new_api.set_pref('virt_libs_hidden', list( self.current_db.new_api.pref('virt_libs_hidden', ())) + [vl]) self.removeTab(index) @property def current_db(self): return self.gui.current_db def rebuild(self): self.ignore_tab_changed = True try: self._rebuild() finally: self.ignore_tab_changed = False def _rebuild(self): db = self.current_db vl_map = db.new_api.pref('virtual_libraries', {}) virt_libs = frozenset(vl_map) hidden = set(db.new_api.pref('virt_libs_hidden', ())) if hidden - virt_libs: hidden = hidden.intersection(virt_libs) db.new_api.set_pref('virt_libs_hidden', list(hidden)) order = db.new_api.pref('virt_libs_order', ()) while self.count(): self.removeTab(0) current_lib = db.data.get_base_restriction_name() if current_lib in hidden: hidden.discard(current_lib) db.new_api.set_pref('virt_libs_hidden', list(hidden)) current_idx = all_idx = None virt_libs = (set(virt_libs) - hidden) | {''} order = {x:i for i, x in enumerate(order)} for i, vl in enumerate(sorted(virt_libs, key=lambda x:(order.get(x, 0), sort_key(x)))): self.addTab(vl.replace('&', '&&') or _('All books')) sexp = vl_map.get(vl, None) if sexp is not None: self.setTabToolTip(i, _('Search expression for this Virtual library:') + '\n\n' + sexp) self.setTabData(i, vl) if vl == current_lib: current_idx = i if not vl: all_idx = i self.setCurrentIndex(all_idx if current_idx is None else current_idx) if current_idx is None and current_lib: self.setTabText(all_idx, current_lib) try: self.tabButton(all_idx, QTabBar.ButtonPosition.RightSide).setVisible(False) except AttributeError: try: self.tabButton(all_idx, QTabBar.ButtonPosition.LeftSide).setVisible(False) except AttributeError: # On some OS X machines (using native style) the tab button is # on the left pass def update_current(self): self.rebuild() def contextMenuEvent(self, ev): m = QMenu(self) m.addAction(QIcon.ic('sort.png'), _('Sort tabs alphabetically'), self.sort_alphabetically) hidden = self.current_db.new_api.pref('virt_libs_hidden') if hidden: s = m._s = m.addMenu(_('Restore hidden tabs')) for x in hidden: s.addAction(x, partial(self.restore, x)) m.addAction(_('Hide Virtual library tabs'), self.disable_bar) if gprefs['vl_tabs_closable']: m.addAction(QIcon.ic('drm-locked.png'), _('Lock Virtual library tabs'), self.lock_tab) else: m.addAction(QIcon.ic('drm-unlocked.png'), _('Unlock Virtual library tabs'), self.unlock_tab) i = self.tabAt(ev.pos()) if i > -1: vl = str(self.tabData(i) or '') if vl: vln = vl.replace('&', '&&') m.addSeparator() m.addAction(QIcon.ic('edit_input.png'), _('Edit "%s"') % vln, partial(self.gui.do_create_edit, name=vl)) m.addAction(QIcon.ic('trash.png'), _('Delete "%s"') % vln, partial(self.gui.remove_vl_triggered, name=vl)) m.exec(ev.globalPos()) def sort_alphabetically(self): self.current_db.new_api.set_pref('virt_libs_order', ()) self.rebuild() def restore(self, x): h = self.current_db.new_api.pref('virt_libs_hidden', ()) self.current_db.new_api.set_pref('virt_libs_hidden', list(set(h) - {x})) self.rebuild() # }}} class LayoutMixin: # {{{ def __init__(self, *args, **kwargs): pass def init_layout_mixin(self): self.vl_tabs = VLTabs(self) self.centralwidget.layout().addWidget(self.vl_tabs) if config['gui_layout'] == 'narrow': # narrow {{{ self.book_details = BookDetails(False, self) self.stack = Stack(self) self.bd_splitter = Splitter('book_details_splitter', _('Book details'), I('book.png'), orientation=Qt.Orientation.Vertical, parent=self, side_index=1, shortcut='Shift+Alt+D') self.bd_splitter.addWidget(self.stack) self.bd_splitter.addWidget(self.book_details) self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False) self.centralwidget.layout().addWidget(self.bd_splitter) button_order = ('sb', 'tb', 'bd', 'gv', 'cb', 'qv') # }}} else: # wide {{{ self.bd_splitter = Splitter('book_details_splitter', _('Book details'), I('book.png'), initial_side_size=200, orientation=Qt.Orientation.Horizontal, parent=self, side_index=1, shortcut='Shift+Alt+D') self.stack = Stack(self) self.bd_splitter.addWidget(self.stack) self.book_details = BookDetails(True, self) self.bd_splitter.addWidget(self.book_details) self.bd_splitter.setCollapsible(self.bd_splitter.other_index, False) self.bd_splitter.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)) self.centralwidget.layout().addWidget(self.bd_splitter) button_order = ('sb', 'tb', 'cb', 'gv', 'qv', 'bd') # }}} # This must use the base method to find the plugin because it hasn't # been fully initialized yet self.qv = find_plugin('Quickview') if self.qv and self.qv.actual_plugin_: self.qv = self.qv.actual_plugin_ self.status_bar = StatusBar(self) stylename = str(self.style().objectName()) self.grid_view_button = GridViewButton(self) self.search_bar_button = SearchBarButton(self) self.grid_view_button.toggled.connect(self.toggle_grid_view) self.search_bar_button.toggled.connect(self.toggle_search_bar) self.layout_buttons = [] for x in button_order: if hasattr(self, x + '_splitter'): button = getattr(self, x + '_splitter').button else: if x == 'gv': button = self.grid_view_button elif x == 'qv': if self.qv is None: continue button = self.qv.qv_button else: button = self.search_bar_button self.layout_buttons.append(button) button.setVisible(False) if ismacos and stylename != 'Calibre': button.setStyleSheet(''' QToolButton { background: none; border:none; padding: 0px; } QToolButton:checked { background: rgba(0, 0, 0, 25%); } ''') self.status_bar.addPermanentWidget(button) if gprefs['show_layout_buttons']: for b in self.layout_buttons: b.setVisible(True) self.status_bar.addPermanentWidget(b) else: self.layout_button = b = QToolButton(self) b.setAutoRaise(True), b.setCursor(Qt.CursorShape.PointingHandCursor) b.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) b.setText(_('Layout')), b.setIcon(QIcon(I('config.png'))) b.setMenu(LayoutMenu(self)) b.setToolTip(_( 'Show and hide various parts of the calibre main window')) self.status_bar.addPermanentWidget(b) self.status_bar.addPermanentWidget(self.jobs_button) self.setStatusBar(self.status_bar) self.status_bar.update_label.linkActivated.connect(self.update_link_clicked) def finalize_layout(self): self.status_bar.initialize(self.system_tray_icon) self.book_details.show_book_info.connect(self.iactions['Show Book Details'].show_book_info) self.book_details.files_dropped.connect(self.iactions['Add Books'].files_dropped_on_book) self.book_details.cover_changed.connect(self.bd_cover_changed, type=Qt.ConnectionType.QueuedConnection) self.book_details.open_cover_with.connect(self.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) self.book_details.open_fmt_with.connect(self.bd_open_fmt_with, type=Qt.ConnectionType.QueuedConnection) self.book_details.edit_book.connect(self.bd_edit_book, type=Qt.ConnectionType.QueuedConnection) self.book_details.cover_removed.connect(self.bd_cover_removed, type=Qt.ConnectionType.QueuedConnection) self.book_details.remote_file_dropped.connect( self.iactions['Add Books'].remote_file_dropped_on_book, type=Qt.ConnectionType.QueuedConnection) self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id) self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) self.book_details.search_requested.connect(self.set_search_string_with_append) self.book_details.remove_specific_format.connect( self.iactions['Remove Books'].remove_format_by_id) self.book_details.remove_metadata_item.connect( self.iactions['Edit Metadata'].remove_metadata_item) self.book_details.save_specific_format.connect( self.iactions['Save To Disk'].save_library_format_by_ids) self.book_details.restore_specific_format.connect( self.iactions['Remove Books'].restore_format) self.book_details.set_cover_from_format.connect( self.iactions['Edit Metadata'].set_cover_from_format) self.book_details.copy_link.connect(self.bd_copy_link, type=Qt.ConnectionType.QueuedConnection) self.book_details.view_device_book.connect( self.iactions['View'].view_device_book) self.book_details.manage_category.connect(self.manage_category_triggerred) self.book_details.find_in_tag_browser.connect(self.find_in_tag_browser_triggered) self.book_details.edit_identifiers.connect(self.edit_identifiers_triggerred) self.book_details.compare_specific_format.connect(self.compare_format) m = self.library_view.model() if m.rowCount(None) > 0: QTimer.singleShot(0, self.library_view.set_current_row) m.current_changed(self.library_view.currentIndex(), self.library_view.currentIndex()) self.library_view.setFocus(Qt.FocusReason.OtherFocusReason) def set_search_string_with_append(self, expression, append=''): current = self.search.text().strip() if append: expr = f'{current} {append} {expression}' if current else expression else: expr = expression self.search.set_search_string(expr) def edit_identifiers_triggerred(self): book_id = self.library_view.current_book db = self.current_db.new_api identifiers = db.field_for('identifiers', book_id, default_value={}) from calibre.gui2.metadata.basic_widgets import Identifiers d = Identifiers(identifiers, self) if d.exec() == QDialog.DialogCode.Accepted: identifiers = d.get_identifiers() db.set_field('identifiers', {book_id: identifiers}) self.iactions['Edit Metadata'].refresh_books_after_metadata_edit({book_id}) def manage_category_triggerred(self, field, value): if field and value: if field == 'authors': self.do_author_sort_edit(self, value, select_sort=False, select_link=False, lookup_author=True) elif field: self.do_tags_list_edit(value, field) def find_in_tag_browser_triggered(self, field, value): if field and value: tb = self.stack.tb_widget tb.set_focus_to_find_box() tb.item_search.lineEdit().setText(field + ':=' + value) tb.do_find() def toggle_grid_view(self, show): self.library_view.alternate_views.show_view('grid' if show else None) self.sort_button.setVisible(show) def toggle_search_bar(self, show): self.search_bar.setVisible(show) if show: self.search.setFocus(Qt.FocusReason.OtherFocusReason) def bd_cover_changed(self, id_, cdata): self.library_view.model().db.set_cover(id_, cdata) self.refresh_cover_browser() def bd_open_cover_with(self, book_id, entry): cpath = self.current_db.new_api.format_abspath(book_id, '__COVER_INTERNAL__') if cpath: if entry is None: open_local_file(cpath) return from calibre.gui2.open_with import run_program run_program(entry, cpath, self) def bd_open_fmt_with(self, book_id, fmt, entry): path = self.current_db.new_api.format_abspath(book_id, fmt) if path: from calibre.gui2.open_with import run_program run_program(entry, path, self) else: fmt = fmt.upper() error_dialog(self, _('No %s format') % fmt, _( 'The book {0} does not have the {1} format').format( self.current_db.new_api.field_for('title', book_id, default_value=_('Unknown')), fmt), show=True) def bd_edit_book(self, book_id, fmt): from calibre.gui2.device import BusyCursor with BusyCursor(): self.iactions['Tweak ePub'].ebook_edit_format(book_id, fmt) def open_with_action_triggerred(self, fmt, entry, *args): book_id = self.library_view.current_book if book_id is not None: if fmt == 'cover_image': self.bd_open_cover_with(book_id, entry) else: self.bd_open_fmt_with(book_id, fmt, entry) def bd_cover_removed(self, id_): self.library_view.model().db.remove_cover(id_, commit=True, notify=False) self.refresh_cover_browser() def bd_copy_link(self, url): if url: QApplication.clipboard().setText(url) def compare_format(self, book_id, fmt): db = self.current_db.new_api ofmt = fmt if fmt.startswith('ORIGINAL_'): fmt = fmt.partition('_')[-1] else: ofmt = 'ORIGINAL_' + fmt path1, path2 = db.format_abspath(book_id, ofmt), db.format_abspath(book_id, fmt) from calibre.gui2.tweak_book.diff.main import compare_books compare_books(path1, path2, parent=self, revert_msg=_('Restore %s') % ofmt, revert_callback=partial( self.iactions['Remove Books'].restore_format, book_id, ofmt), names=(ofmt, fmt)) def save_layout_state(self): for x in ('library', 'memory', 'card_a', 'card_b'): getattr(self, x+'_view').save_state() for x in ('cb', 'tb', 'bd'): s = getattr(self, x+'_splitter') s.update_desired_state() s.save_state() self.grid_view_button.save_state() self.search_bar_button.save_state() if self.qv: self.qv.qv_button.save_state() def read_layout_settings(self): # View states are restored automatically when set_database is called for x in ('cb', 'tb', 'bd'): getattr(self, x+'_splitter').restore_state() self.grid_view_button.restore_state() self.search_bar_button.restore_state() # Can't do quickview here because the gui isn't totally set up. Do it in ui def update_status_bar(self, *args): v = self.current_view() selected = len(v.selectionModel().selectedRows()) library_total, total, current = v.model().counts() self.status_bar.update_state(library_total, total, current, selected) # }}}