def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) self.marked = None self.gui = parent self.splitter = QSplitter(self) self._l = l = QVBoxLayout(self) self.setLayout(l) l.addWidget(self.splitter) self.cover = Cover(self, show_size=gprefs['bd_overlay_cover_size']) self.cover.resizeEvent = self.cover_view_resized self.cover.cover_changed.connect(self.cover_changed) self.cover.open_with_requested.connect(self.open_with) self.cover.choose_open_with_requested.connect(self.choose_open_with) self.cover_pixmap = None self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) self.details = Details(parent.book_details.book_info, self) self.details.anchor_clicked.connect(self.on_link_clicked) self.link_delegate = link_delegate self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False) palette = self.details.palette() self.details.setAcceptDrops(False) palette.setBrush(QPalette.ColorRole.Base, Qt.GlobalColor.transparent) self.details.setPalette(palette) self.c = QWidget(self) self.c.l = l2 = QGridLayout(self.c) l2.setContentsMargins(0, 0, 0, 0) self.c.setLayout(l2) l2.addWidget(self.details, 0, 0, 1, -1) self.splitter.addWidget(self.c) self.fit_cover = QCheckBox(_('Fit &cover within view'), self) self.fit_cover.setChecked( gprefs.get('book_info_dialog_fit_cover', True)) self.hl = hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() self.clabel = QLabel( '<div style="text-align: right"><a href="calibre:conf" title="{}" style="text-decoration: none">{}</a>' .format(_('Configure this view'), _('Configure'))) self.clabel.linkActivated.connect(self.configure) hl.addWidget(self.clabel) self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self) self.next_button.clicked.connect(self.next) l2.addWidget(self.next_button, l2.rowCount() - 1, 1) self.view = view self.path_to_book = None self.current_row = None self.refresh(row) self.view.model().new_bookdisplay_data.connect(self.slave) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.ns = QShortcut(QKeySequence('Alt+Right'), self) self.ns.activated.connect(self.next) self.ps = QShortcut(QKeySequence('Alt+Left'), self) self.ps.activated.connect(self.previous) self.next_button.setToolTip( _('Next [%s]') % unicode_type(self.ns.key().toString( QKeySequence.SequenceFormat.NativeText))) self.previous_button.setToolTip( _('Previous [%s]') % unicode_type(self.ps.key().toString( QKeySequence.SequenceFormat.NativeText))) geom = QCoreApplication.instance().desktop().availableGeometry(self) screen_height = geom.height() - 100 screen_width = geom.width() - 100 self.resize(max(int(screen_width / 2), 700), screen_height) saved_layout = gprefs.get('book_info_dialog_layout', None) if saved_layout is not None: try: QApplication.instance().safe_restore_geometry( self, saved_layout[0]) self.splitter.restoreState(saved_layout[1]) except Exception: pass from calibre.gui2.ui import get_gui ema = get_gui().iactions['Edit Metadata'].menuless_qaction a = self.ema = QAction('edit metadata', self) a.setShortcut(ema.shortcut()) self.addAction(a) a.triggered.connect(self.edit_metadata)
def __init__(self, gui, row, toggle_shortcut): self.is_pane = gprefs.get('quickview_is_pane', False) if not self.is_pane: QDialog.__init__(self, gui, flags=Qt.WindowType.Widget) else: QDialog.__init__(self, gui) Ui_Quickview.__init__(self) self.setupUi(self) self.isClosed = False self.current_book = None self.closed_by_button = False if self.is_pane: self.main_grid_layout.setContentsMargins(0, 0, 0, 0) else: self.setWindowIcon(self.windowIcon()) self.books_table_column_widths = None try: self.books_table_column_widths = \ gprefs.get('quickview_dialog_books_table_widths', None) if not self.is_pane: geom = gprefs.get('quickview_dialog_geometry', None) if geom: QApplication.instance().safe_restore_geometry( self, QByteArray(geom)) except: pass self.view = gui.library_view self.db = self.view.model().db self.gui = gui self.is_closed = False self.current_book_id = None # the db id of the book used to fill the lh pane self.current_column = None # current logical column in books list self.current_key = None # current lookup key in books list self.last_search = None self.no_valid_items = False self.follow_library_view = True self.apply_vls.setCheckState( Qt.CheckState.Checked if gprefs['qv_respects_vls'] else Qt. CheckState.Unchecked) self.apply_vls.stateChanged.connect(self.vl_box_changed) self.fm = self.db.field_metadata self.items.setSelectionMode( QAbstractItemView.SelectionMode.SingleSelection) self.items.currentTextChanged.connect(self.item_selected) self.items.setProperty('highlight_current_item', 150) self.items.itemDoubleClicked.connect(self.item_doubleclicked) self.items.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.items.customContextMenuRequested.connect( self.show_item_context_menu) focus_filter = WidgetFocusFilter(self.items) focus_filter.focus_entered_signal.connect(self.focus_entered) self.items.installEventFilter(focus_filter) self.tab_pressed_signal.connect(self.tab_pressed) return_filter = BooksTableFilter(self.books_table) return_filter.return_pressed_signal.connect(self.return_pressed) self.books_table.installEventFilter(return_filter) focus_filter = WidgetFocusFilter(self.books_table) focus_filter.focus_entered_signal.connect(self.focus_entered) self.books_table.installEventFilter(focus_filter) self.close_button.clicked.connect(self.close_button_clicked) self.refresh_button.clicked.connect(self.refill) self.tab_order_widgets = [ self.items, self.books_table, self.lock_qv, self.dock_button, self.refresh_button, self.close_button ] for idx, widget in enumerate(self.tab_order_widgets): widget.installEventFilter( WidgetTabFilter(widget, idx, self.tab_pressed_signal)) self.books_table.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) self.books_table.setSelectionMode( QAbstractItemView.SelectionMode.SingleSelection) self.books_table.setProperty('highlight_current_item', 150) # Set up the books table columns self.add_columns_to_widget() self.books_table_header_height = self.books_table.height() self.books_table.cellDoubleClicked.connect(self.book_doubleclicked) self.books_table.currentCellChanged.connect( self.books_table_cell_changed) self.books_table.cellClicked.connect( self.books_table_set_search_string) self.books_table.cellActivated.connect( self.books_table_set_search_string) self.books_table.sortByColumn(0, Qt.SortOrder.AscendingOrder) # get the standard table row height. Do this here because calling # resizeRowsToContents can word wrap long cell contents, creating # double-high rows self.books_table.setRowCount(1) self.books_table.setItem(0, 0, TableItem()) self.books_table.resizeRowsToContents() self.books_table_row_height = self.books_table.rowHeight(0) self.books_table.setRowCount(0) # Add the data self.refresh(row) self.slave_timers = [QTimer(self), QTimer(self), QTimer(self)] self.view.clicked.connect( partial(self.delayed_slave, func=self.slave, dex=0)) self.view.selectionModel().currentColumnChanged.connect( partial(self.delayed_slave, func=self.column_slave, dex=1)) QCoreApplication.instance().aboutToQuit.connect(self.save_state) self.view.model().new_bookdisplay_data.connect( partial(self.delayed_slave, func=self.book_was_changed, dex=2)) self.close_button.setDefault(False) self.close_button_tooltip = _( 'The Quickview shortcut ({0}) shows/hides the Quickview panel') self.refresh_button.setIcon(QIcon.ic('view-refresh.png')) self.close_button.setIcon(self.style().standardIcon( QStyle.StandardPixmap.SP_DialogCloseButton)) if self.is_pane: self.dock_button.setText(_('Undock')) self.dock_button.setToolTip( _('Show the Quickview panel in its own floating window')) self.dock_button.setIcon(QIcon(I('arrow-up.png'))) # Remove the ampersands from the buttons because shortcuts exist. self.lock_qv.setText(_('Lock Quickview contents')) self.refresh_button.setText(_('Refresh')) self.gui.quickview_splitter.add_quickview_dialog(self) self.close_button.setVisible(False) else: self.dock_button.setToolTip( _('Embed the Quickview panel into the main calibre window')) self.dock_button.setIcon(QIcon(I('arrow-down.png'))) self.set_focus() self.books_table.horizontalHeader().sectionResized.connect( self.section_resized) self.dock_button.clicked.connect(self.show_as_pane_changed) self.view.model().search_done.connect(self.check_for_no_items) # Enable the refresh button only when QV is locked self.refresh_button.setEnabled(False) self.lock_qv.stateChanged.connect(self.lock_qv_changed) self.view_icon = QIcon(I('view.png')) self.view_plugin = self.gui.iactions['View'] self.edit_metadata_icon = QIcon(I('edit_input.png')) self.quickview_icon = QIcon(I('quickview.png')) self.select_book_icon = QIcon(I('library.png')) self.search_icon = QIcon(I('search.png')) self.books_table.setContextMenuPolicy( Qt.ContextMenuPolicy.CustomContextMenu) self.books_table.customContextMenuRequested.connect( self.show_context_menu) # Add the quickview toggle as a shortcut for the close button # Don't add it if it identical to the current &X shortcut because that # breaks &X if (not self.is_pane and toggle_shortcut and self.close_button.shortcut() != toggle_shortcut): toggle_sc = QShortcut(toggle_shortcut, self.close_button) toggle_sc.activated.connect(lambda: self.close_button_clicked()) toggle_sc.setEnabled(True) self.close_button.setToolTip( _('Alternate shortcut: ') + toggle_shortcut.toString())
class BookInfo(QDialog): closed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) def __init__(self, parent, view, row, link_delegate): QDialog.__init__(self, parent) self.marked = None self.gui = parent self.splitter = QSplitter(self) self._l = l = QVBoxLayout(self) self.setLayout(l) l.addWidget(self.splitter) self.cover = Cover(self, show_size=gprefs['bd_overlay_cover_size']) self.cover.resizeEvent = self.cover_view_resized self.cover.cover_changed.connect(self.cover_changed) self.cover.open_with_requested.connect(self.open_with) self.cover.choose_open_with_requested.connect(self.choose_open_with) self.cover_pixmap = None self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) self.details = Details(parent.book_details.book_info, self) self.details.anchor_clicked.connect(self.on_link_clicked) self.link_delegate = link_delegate self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False) palette = self.details.palette() self.details.setAcceptDrops(False) palette.setBrush(QPalette.ColorRole.Base, Qt.GlobalColor.transparent) self.details.setPalette(palette) self.c = QWidget(self) self.c.l = l2 = QGridLayout(self.c) l2.setContentsMargins(0, 0, 0, 0) self.c.setLayout(l2) l2.addWidget(self.details, 0, 0, 1, -1) self.splitter.addWidget(self.c) self.fit_cover = QCheckBox(_('Fit &cover within view'), self) self.fit_cover.setChecked( gprefs.get('book_info_dialog_fit_cover', True)) self.hl = hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() self.clabel = QLabel( '<div style="text-align: right"><a href="calibre:conf" title="{}" style="text-decoration: none">{}</a>' .format(_('Configure this view'), _('Configure'))) self.clabel.linkActivated.connect(self.configure) hl.addWidget(self.clabel) self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self) self.next_button.clicked.connect(self.next) l2.addWidget(self.next_button, l2.rowCount() - 1, 1) self.view = view self.path_to_book = None self.current_row = None self.refresh(row) self.view.model().new_bookdisplay_data.connect(self.slave) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.ns = QShortcut(QKeySequence('Alt+Right'), self) self.ns.activated.connect(self.next) self.ps = QShortcut(QKeySequence('Alt+Left'), self) self.ps.activated.connect(self.previous) self.next_button.setToolTip( _('Next [%s]') % unicode_type(self.ns.key().toString( QKeySequence.SequenceFormat.NativeText))) self.previous_button.setToolTip( _('Previous [%s]') % unicode_type(self.ps.key().toString( QKeySequence.SequenceFormat.NativeText))) geom = QCoreApplication.instance().desktop().availableGeometry(self) screen_height = geom.height() - 100 screen_width = geom.width() - 100 self.resize(max(int(screen_width / 2), 700), screen_height) saved_layout = gprefs.get('book_info_dialog_layout', None) if saved_layout is not None: try: QApplication.instance().safe_restore_geometry( self, saved_layout[0]) self.splitter.restoreState(saved_layout[1]) except Exception: pass from calibre.gui2.ui import get_gui ema = get_gui().iactions['Edit Metadata'].menuless_qaction a = self.ema = QAction('edit metadata', self) a.setShortcut(ema.shortcut()) self.addAction(a) a.triggered.connect(self.edit_metadata) def edit_metadata(self): if self.current_row is not None: book_id = self.view.model().id(self.current_row) get_gui().iactions['Edit Metadata'].edit_metadata_for( [self.current_row], [book_id], bulk=False) def configure(self): d = Configure(get_gui().current_db, self) if d.exec_() == QDialog.DialogCode.Accepted: if self.current_row is not None: mi = self.view.model().get_book_display_info(self.current_row) if mi is not None: self.refresh(self.current_row, mi=mi) def on_link_clicked(self, qurl): link = unicode_type(qurl.toString(NO_URL_FORMATTING)) self.link_delegate(link) def done(self, r): saved_layout = (bytearray(self.saveGeometry()), bytearray(self.splitter.saveState())) gprefs.set('book_info_dialog_layout', saved_layout) ret = QDialog.done(self, r) self.view.model().new_bookdisplay_data.disconnect(self.slave) self.view = self.link_delegate = self.gui = None self.closed.emit(self) return ret def cover_changed(self, data): if self.current_row is not None: id_ = self.view.model().id(self.current_row) self.view.model().db.set_cover(id_, data) self.gui.refresh_cover_browser() ci = self.view.currentIndex() if ci.isValid(): self.view.model().current_changed(ci, ci) def details_size_hint(self): return QSize(350, 550) def toggle_cover_fit(self, state): gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked()) self.resize_cover() def cover_view_resized(self, event): QTimer.singleShot(1, self.resize_cover) def slave(self, mi): self.refresh(mi.row_number, mi) def move(self, delta=1): idx = self.view.currentIndex() if idx.isValid(): m = self.view.model() ni = m.index(idx.row() + delta, idx.column()) if ni.isValid(): if self.view.isVisible(): self.view.scrollTo(ni) self.view.setCurrentIndex(ni) def next(self): self.move() def previous(self): self.move(-1) def resize_cover(self): if self.cover_pixmap is None: self.cover.set_marked(self.marked) return pixmap = self.cover_pixmap if self.fit_cover.isChecked(): scaled, new_width, new_height = fit_image( pixmap.width(), pixmap.height(), self.cover.size().width() - 10, self.cover.size().height() - 10) if scaled: try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() pixmap = pixmap.scaled( int(dpr * new_width), int(dpr * new_height), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation) pixmap.setDevicePixelRatio(dpr) self.cover.set_pixmap(pixmap) self.cover.set_marked(self.marked) self.update_cover_tooltip() def update_cover_tooltip(self): tt = '' if self.marked: tt += _('This book is marked') if self.marked in { True, 'true' } else _('This book is marked as: %s') % self.marked tt += '\n\n' if self.path_to_book is not None: tt += textwrap.fill(_('Path: {}').format(self.path_to_book)) tt += '\n\n' if self.cover_pixmap is not None: sz = self.cover_pixmap.size() tt += _('Cover size: %(width)d x %(height)d pixels') % dict( width=sz.width(), height=sz.height()) self.cover.setToolTip(tt) self.cover.pixmap_size = sz.width(), sz.height() def refresh(self, row, mi=None): if isinstance(row, QModelIndex): row = row.row() if row == self.current_row and mi is None: return mi = self.view.model().get_book_display_info(row) if mi is None else mi if mi is None: # Indicates books was deleted from library, or row numbers have # changed return self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex()) - 1 else True) self.current_row = row self.setWindowTitle(mi.title) self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1]) self.path_to_book = getattr(mi, 'path', None) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() self.cover_pixmap.setDevicePixelRatio(dpr) self.marked = mi.marked self.resize_cover() html = render_html(mi, True, self, pref_name='popup_book_display_fields') set_html(mi, html, self.details) self.update_cover_tooltip() def open_with(self, entry): id_ = self.view.model().id(self.current_row) self.open_cover_with.emit(id_, entry) def choose_open_with(self): from calibre.gui2.open_with import choose_program entry = choose_program('cover_image', self) if entry is not None: self.open_with(entry)