%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/dialogs/ |
| Current File : //lib/calibre/calibre/gui2/dialogs/quickview.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
import traceback
from functools import partial
from qt.core import (
Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem,
QCoreApplication, QEvent, QObject, QApplication, pyqtSignal, QByteArray, QMenu,
QShortcut, QTimer, QStyle)
from calibre.customize.ui import find_plugin
from calibre.gui2 import gprefs
from calibre.gui2.dialogs.quickview_ui import Ui_Quickview
from calibre.utils.date import timestampfromdt
from calibre.utils.icu import sort_key
from calibre.utils.iso8601 import UNDEFINED_DATE
class TableItem(QTableWidgetItem):
'''
A QTableWidgetItem that sorts on a separate string and uses ICU rules
'''
def __init__(self, getter=None):
self.val = ''
self.sort = None
self.sort_idx = 0
self.getter = getter
self.resolved = False
QTableWidgetItem.__init__(self, '')
self.setFlags(Qt.ItemFlag.ItemIsEnabled|Qt.ItemFlag.ItemIsSelectable)
def __ge__(self, other):
self.get_data()
other.get_data()
if self.sort is None:
if other.sort is None:
# None == None therefore >=
return True
# self is None, other is not None therefore self < other
return False
if other.sort is None:
# self is not None and other is None therefore self >= other
return True
if isinstance(self.sort, (bytes, str)):
l = sort_key(self.sort)
r = sort_key(other.sort)
else:
l = self.sort
r = other.sort
if l > r:
return 1
if l == r:
return self.sort_idx >= other.sort_idx
return 0
def __lt__(self, other):
self.get_data()
other.get_data()
if self.sort is None:
if other.sort is None:
# None == None therefore not <
return False
# self is None, other is not None therefore self < other
return True
if other.sort is None:
# self is not None therefore self > other
return False
if isinstance(self.sort, (bytes, str)):
l = sort_key(self.sort)
r = sort_key(other.sort)
else:
l = self.sort
r = other.sort
if l < r:
return 1
if l == r:
return self.sort_idx < other.sort_idx
return 0
def get_data(self):
if not self.resolved and self.getter:
self.resolved = True
self.val, self.sort, self.sort_idx = self.getter()
def data(self, role):
self.get_data()
if role == Qt.DisplayRole:
return self.val
return QTableWidgetItem.data(self, role)
IN_WIDGET_ITEMS = 0
IN_WIDGET_BOOKS = 1
IN_WIDGET_LOCK = 2
IN_WIDGET_DOCK = 3
IN_WIDGET_SEARCH = 4
IN_WIDGET_CLOSE = 5
class BooksTableFilter(QObject):
return_pressed_signal = pyqtSignal()
def eventFilter(self, obj, event):
if event.type() == QEvent.Type.KeyPress and event.key() == Qt.Key.Key_Return:
self.return_pressed_signal.emit()
return True
return False
class WidgetFocusFilter(QObject):
focus_entered_signal = pyqtSignal(object)
def eventFilter(self, obj, event):
if event.type() == QEvent.Type.FocusIn:
self.focus_entered_signal.emit(obj)
return False
class WidgetTabFilter(QObject):
def __init__(self, attach_to_Class, which_widget, tab_signal):
QObject.__init__(self, attach_to_Class)
self.tab_signal = tab_signal
self.which_widget = which_widget
def eventFilter(self, obj, event):
if event.type() == QEvent.Type.KeyPress:
if event.key() == Qt.Key.Key_Tab:
self.tab_signal.emit(self.which_widget, True)
return True
if event.key() == Qt.Key.Key_Backtab:
self.tab_signal.emit(self.which_widget, False)
return True
return False
class Quickview(QDialog, Ui_Quickview):
reopen_after_dock_change = pyqtSignal()
tab_pressed_signal = pyqtSignal(object, object)
quickview_closed = pyqtSignal()
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())
def delayed_slave(self, current, func=None, dex=None):
self.slave_timers[dex].stop()
t = self.slave_timers[dex] = QTimer(self)
t.timeout.connect(partial(func, current))
t.setSingleShot(True)
t.setInterval(200)
t.start()
def item_doubleclicked(self, item):
tb = self.gui.stack.tb_widget
tb.set_focus_to_find_box()
tb.item_search.lineEdit().setText(self.current_key + ':=' + item.text())
tb.do_find()
def show_item_context_menu(self, point):
item = self.items.currentItem()
self.context_menu = QMenu(self)
self.context_menu.addAction(self.search_icon, _('Find item in the Tag browser'),
partial(self.item_doubleclicked, item))
self.context_menu.addAction(self.search_icon, _('Find item in the library'),
partial(self.do_search, follow_library_view=False))
self.context_menu.popup(self.items.mapToGlobal(point))
self.context_menu = QMenu(self)
def show_context_menu(self, point):
index = self.books_table.indexAt(point)
row = index.row()
column = index.column()
item = self.books_table.item(index.row(), 0)
if item is None:
return False
book_id = int(item.data(Qt.ItemDataRole.UserRole))
book_displayed = self.book_displayed_in_library_view(book_id)
m = self.context_menu = QMenu(self)
a = m.addAction(self.select_book_icon, _('Select this book in the library'),
partial(self.select_book, book_id))
a.setEnabled(book_displayed)
m.addAction(self.search_icon, _('Find item in the library'),
partial(self.do_search, follow_library_view=False))
a = m.addAction(self.edit_metadata_icon, _('Edit metadata'),
partial(self.edit_metadata, book_id, follow_library_view=False))
a.setEnabled(book_displayed)
a = m.addAction(self.quickview_icon, _('Quickview this cell'),
partial(self.quickview_item, row, column))
a.setEnabled(self.is_category(self.column_order[column]) and
book_displayed and not self.lock_qv.isChecked())
m.addSeparator()
m.addAction(self.view_icon, _('Open book in the E-book viewer'),
partial(self.view_plugin._view_calibre_books, [book_id]))
self.context_menu.popup(self.books_table.mapToGlobal(point))
return True
def lock_qv_changed(self, state):
self.refresh_button.setEnabled(state)
def add_columns_to_widget(self):
'''
Get the list of columns from the preferences. Clear the current table
and add the current column set
'''
self.column_order = [x[0] for x in get_qv_field_list(self.fm) if x[1]]
self.books_table.clear()
self.books_table.setRowCount(0)
self.books_table.setColumnCount(len(self.column_order))
for idx,col in enumerate(self.column_order):
t = QTableWidgetItem(self.fm[col]['name'])
self.books_table.setHorizontalHeaderItem(idx, t)
def refill(self):
'''
Refill the table in case the columns displayed changes
'''
self.add_columns_to_widget()
self.refresh(self.view.currentIndex(), ignore_lock=True)
def set_search_text(self, txt):
self.last_search = txt
def focus_entered(self, obj):
if obj == self.books_table:
self.books_table_set_search_string(self.books_table.currentRow(),
self.books_table.currentColumn())
elif obj.currentItem():
self.item_selected(obj.currentItem().text())
def books_table_cell_changed(self, cur_row, cur_col, prev_row, prev_col):
self.books_table_set_search_string(cur_row, cur_col)
def books_table_set_search_string(self, current_row, current_col):
'''
Given the contents of a cell, compute a search string that will find
that book and any others with identical contents in the cell.
'''
current = self.books_table.item(current_row, current_col)
if current is None:
return
book_id = current.data(Qt.ItemDataRole.UserRole)
if current is None:
return
col = self.column_order[current.column()]
if col == 'title':
self.set_search_text('title:="' + current.text().replace('"', '\\"') + '"')
elif col == 'authors':
authors = []
for aut in [t.strip() for t in current.text().split('&')]:
authors.append('authors:="' + aut.replace('"', '\\"') + '"')
self.set_search_text(' and '.join(authors))
elif self.fm[col]['datatype'] == 'series':
mi = self.db.get_metadata(book_id, index_is_id=True, get_user_categories=False)
t = mi.get(col)
if t:
self.set_search_text(col+ ':="' + t + '"')
else:
self.set_search_text(None)
else:
if self.fm[col]['is_multiple']:
items = [(col + ':"=' + v.strip() + '"') for v in
current.text().split(self.fm[col]['is_multiple']['ui_to_list'])]
self.set_search_text(' and '.join(items))
else:
self.set_search_text(col + ':"=' + current.text() + '"')
def tab_pressed(self, in_widget, isForward):
if isForward:
in_widget += 1
if in_widget >= len(self.tab_order_widgets):
in_widget = 0
else:
in_widget -= 1
if in_widget < 0:
in_widget = len(self.tab_order_widgets) - 1
self.tab_order_widgets[in_widget].setFocus(Qt.FocusReason.TabFocusReason)
def show(self):
QDialog.show(self)
if self.is_pane:
self.gui.quickview_splitter.show_quickview_widget()
def show_as_pane_changed(self):
gprefs['quickview_is_pane'] = not gprefs.get('quickview_is_pane', False)
self.reopen_after_dock_change.emit()
# search button
def do_search(self, follow_library_view=True):
if self.no_valid_items:
return
if self.last_search is not None:
try:
self.follow_library_view = follow_library_view
self.gui.search.set_search_string(self.last_search)
finally:
self.follow_library_view = True
def book_was_changed(self, mi):
'''
Called when book information is changed in the library view. Make that
book info current. This means that prev and next in edit metadata will move
the current book and change quickview
'''
if self.is_closed or self.current_column is None or not self.follow_library_view:
return
# There is an ordering problem when libraries are changed. The library
# view is changed, triggering a book_was_changed signal. Unfortunately
# this happens before the library_changed actions are run, meaning we
# still have the old database. To avoid the problem we just ignore the
# operation if we get an exception. The "close" will come
# eventually.
try:
self.refresh(self.view.model().index(self.db.row(mi.id), self.current_column))
except:
pass
# clicks on the items listWidget
def item_selected(self, txt):
if self.no_valid_items:
return
self.fill_in_books_box(str(txt))
self.set_search_text(self.current_key + ':"=' + txt.replace('"', '\\"') + '"')
def vl_box_changed(self):
gprefs['qv_respects_vls'] = self.apply_vls.isChecked()
self._refresh(self.current_book_id, self.current_key)
def refresh(self, idx, ignore_lock=False):
'''
Given a cell in the library view, display the information. This method
converts the index into the lookup key
'''
if (not ignore_lock and self.lock_qv.isChecked()):
return
if not idx.isValid():
from calibre.constants import DEBUG
if DEBUG:
from calibre import prints
prints('QuickView: current index is not valid')
return
try:
self.current_column = (
self.view.column_map.index('authors') if (
self.current_column is None and self.view.column_map[idx.column()] == 'title'
) else idx.column())
key = self.view.column_map[self.current_column]
book_id = self.view.model().id(idx.row())
if self.current_book_id == book_id and self.current_key == key:
return
self._refresh(book_id, key)
except:
traceback.print_exc()
self.indicate_no_items()
def is_category(self, key):
return key is not None and (self.fm[key]['is_category'] or
(self.fm[key]['datatype'] == 'composite' and
self.fm[key]['display'].get('make_category', False)))
def _refresh(self, book_id, key):
'''
Actually fill in the left-hand panel from the information in the
selected column of the selected book
'''
# Only show items for categories
if not self.is_category(key):
if self.current_key is None:
self.indicate_no_items()
return
key = self.current_key
label_text = _('&Item: {0} ({1})')
if self.is_pane:
label_text = label_text.replace('&', '')
self.items.blockSignals(True)
self.items.clear()
self.books_table.setRowCount(0)
mi = self.db.new_api.get_proxy_metadata(book_id)
vals = mi.get(key, None)
if self.fm[key]['datatype'] == 'composite' and self.fm[key]['is_multiple']:
sep = self.fm[key]['is_multiple'].get('cache_to_list', ',')
vals = [v.strip() for v in vals.split(sep) if v.strip()]
try:
# Check if we are in the GridView and there are no values for the
# selected column. In this case switch the column to 'authors'
# because there isn't an easy way to switch columns in GridView
# when the QV box is empty.
if not vals:
is_grid_view = (self.gui.current_view().alternate_views.current_view !=
self.gui.current_view().alternate_views.main_view)
if is_grid_view:
key = 'authors'
vals = mi.get(key, None)
except:
traceback.print_exc()
self.current_book_id = book_id
self.current_key = key
self.items_label.setText(label_text.format(self.fm[key]['name'], key))
if vals:
self.no_valid_items = False
if self.fm[key]['datatype'] == 'rating':
if self.fm[key]['display'].get('allow_half_stars', False):
vals = str(vals/2.0)
else:
vals = str(vals//2)
if not isinstance(vals, list):
vals = [vals]
vals.sort(key=sort_key)
for v in vals:
a = QListWidgetItem(v)
a.setToolTip(
'<p>' + _(
'Click to show only books with this item. '
'Double click to search for this item in the Tag browser') + '</p>')
self.items.addItem(a)
self.items.setCurrentRow(0)
self.fill_in_books_box(vals[0])
else:
self.indicate_no_items()
self.items.blockSignals(False)
def check_for_no_items(self):
if not self.is_closed and self.view.model().count() == 0:
self.indicate_no_items()
def indicate_no_items(self):
self.no_valid_items = True
self.items.clear()
self.add_columns_to_widget()
self.items.addItem(QListWidgetItem(_('**No items found**')))
self.books_label.setText(_('Click in a column in the library view '
'to see the information for that book'))
def fill_in_books_box(self, selected_item):
'''
Given the selected row in the left-hand box, fill in the grid with
the books that contain that data.
'''
# Do a bit of fix-up on the items so that the search works.
if selected_item.startswith('.'):
sv = '.' + selected_item
else:
sv = selected_item
sv = self.current_key + ':"=' + sv.replace('"', r'\"') + '"'
if self.apply_vls.isChecked():
books = self.db.search(sv, return_matches=True, sort_results=False)
else:
books = self.db.new_api.search(sv)
self.books_table.setRowCount(len(books))
label_text = _('&Books with selected item "{0}": {1}')
if self.is_pane:
label_text = label_text.replace('&', '')
self.books_label.setText(label_text.format(selected_item, len(books)))
select_item = None
self.books_table.setSortingEnabled(False)
self.books_table.blockSignals(True)
tt = ('<p>' + _(
'Double click on a book to change the selection in the library view or '
'change the column shown in the left-hand panel. '
'Shift- or Ctrl- double click to edit the metadata of a book, '
'which also changes the selected book.'
) + '</p>')
for row, b in enumerate(books):
for col in self.column_order:
a = TableItem(partial(self.get_item_data, b, col))
if col == 'title':
if b == self.current_book_id:
select_item = a
# The data is supplied on demand when the item is displayed
a.setData(Qt.ItemDataRole.UserRole, b)
a.setToolTip(tt)
self.books_table.setItem(row, self.key_to_table_widget_column(col), a)
self.books_table.setRowHeight(row, self.books_table_row_height)
self.books_table.blockSignals(False)
self.books_table.setSortingEnabled(True)
if select_item is not None:
self.books_table.setCurrentItem(select_item)
self.books_table.scrollToItem(select_item, QAbstractItemView.ScrollHint.PositionAtCenter)
self.set_search_text(sv)
def get_item_data(self, book_id, col):
mi = self.db.new_api.get_proxy_metadata(book_id)
try:
if col == 'title':
return (mi.title, mi.title_sort, 0)
elif col == 'authors':
return (' & '.join(mi.authors), mi.author_sort, 0)
elif col == 'series':
series = mi.format_field('series')[1]
if series is None:
return ('', None, 0)
else:
return (series, mi.series, mi.series_index)
elif col == 'size':
v = mi.get('book_size')
if v is not None:
return (f'{v:n}', v, 0)
else:
return ('', None, 0)
elif self.fm[col]['datatype'] == 'series':
v = mi.format_field(col)[1]
return (v, mi.get(col), mi.get(col+'_index'))
elif self.fm[col]['datatype'] == 'datetime':
v = mi.format_field(col)[1]
d = mi.get(col)
if d is None:
d = UNDEFINED_DATE
return (v, timestampfromdt(d), 0)
elif self.fm[col]['datatype'] in ('float', 'int'):
v = mi.format_field(col)[1]
sort_val = mi.get(col)
return (v, sort_val, 0)
else:
v = mi.format_field(col)[1]
return (v, v, 0)
except:
traceback.print_exc()
return (_('Something went wrong while filling in the table'), '', 0)
# Deal with sizing the table columns. Done here because the numbers are not
# correct until the first paint.
def resizeEvent(self, *args):
QDialog.resizeEvent(self, *args)
# Do this if we are resizing for the first time to reset state.
if self.is_pane and self.height() == 0:
self.gui.quickview_splitter.set_sizes()
if self.books_table_column_widths is not None:
for c,w in enumerate(self.books_table_column_widths):
self.books_table.setColumnWidth(c, w)
else:
# the vertical scroll bar might not be rendered, so might not yet
# have a width. Assume 25. Not a problem because user-changed column
# widths will be remembered
w = self.books_table.width() - 25 - self.books_table.verticalHeader().width()
w //= self.books_table.columnCount()
for c in range(0, self.books_table.columnCount()):
self.books_table.setColumnWidth(c, w)
self.save_state()
def key_to_table_widget_column(self, key):
return self.column_order.index(key)
def return_pressed(self):
row = self.books_table.currentRow()
if gprefs['qv_retkey_changes_column']:
self.select_book_and_qv(row, self.books_table.currentColumn())
else:
self.select_book_and_qv(row, self.key_to_table_widget_column(self.current_key))
def book_not_in_view_error(self):
from calibre.gui2 import error_dialog
error_dialog(self, _('Quickview: Book not in library view'),
_('The book you selected is not currently displayed in '
'the library view, perhaps because of a search or a '
'Virtual library, so Quickview cannot select it.'),
show=True,
show_copy_button=False)
def book_displayed_in_library_view(self, book_id):
try:
self.db.data.index(book_id)
return True
except:
return False
def quickview_item(self, row, column):
self.select_book_and_qv(row, column)
def book_doubleclicked(self, row, column):
if self.no_valid_items:
return
try:
if gprefs['qv_dclick_changes_column']:
self.quickview_item(row, column)
else:
self.quickview_item(row, self.key_to_table_widget_column(self.current_key))
except:
self.book_not_in_view_error()
def edit_metadata(self, book_id, follow_library_view=True):
try:
self.follow_library_view = follow_library_view
self.view.select_rows([book_id])
em = find_plugin('Edit Metadata')
if em and em.actual_plugin_:
em.actual_plugin_.edit_metadata(None)
finally:
self.follow_library_view = True
def select_book(self, book_id):
'''
Select a book in the library view without changing the QV lists
'''
try:
self.follow_library_view = False
self.view.select_cell(self.db.data.id_to_index(book_id),
self.current_column)
finally:
self.follow_library_view = True
def select_book_and_qv(self, row, column):
'''
row and column both refer the qv table. In particular, column is not
the logical column in the book list.
'''
item = self.books_table.item(row, column)
if item is None:
return
book_id = int(self.books_table.item(row, column).data(Qt.ItemDataRole.UserRole))
if not self.book_displayed_in_library_view(book_id):
self.book_not_in_view_error()
return
key = self.column_order[column]
if QApplication.keyboardModifiers() in (Qt.KeyboardModifier.ControlModifier, Qt.KeyboardModifier.ShiftModifier):
self.edit_metadata(book_id)
else:
self.view.select_cell(self.db.data.id_to_index(book_id),
self.view.column_map.index(key))
def set_focus(self):
self.activateWindow()
self.books_table.setFocus()
def column_slave(self, current):
'''
called when the column is changed on the booklist
'''
if self.follow_library_view and gprefs['qv_follows_column']:
self.slave(current)
def slave(self, current):
'''
called when a book is clicked on the library view
'''
if self.is_closed or not self.follow_library_view:
return
self.refresh(current)
self.view.activateWindow()
def section_resized(self, logicalIndex, oldSize, newSize):
self.save_state()
def save_state(self):
if self.is_closed:
return
self.books_table_column_widths = []
for c in range(0, self.books_table.columnCount()):
self.books_table_column_widths.append(self.books_table.columnWidth(c))
gprefs['quickview_dialog_books_table_widths'] = self.books_table_column_widths
if not self.is_pane:
gprefs['quickview_dialog_geometry'] = bytearray(self.saveGeometry())
def _close(self):
self.save_state()
# clean up to prevent memory leaks
self.db = self.view = self.gui = None
self.is_closed = True
def close_button_clicked(self):
self.closed_by_button = True
self.quickview_closed.emit()
def reject(self):
if not self.closed_by_button:
self.close_button_clicked()
else:
self._reject()
def _reject(self):
if self.is_pane:
self.gui.quickview_splitter.hide_quickview_widget()
self.gui.library_view.setFocus(Qt.FocusReason.ActiveWindowFocusReason)
self._close()
QDialog.reject(self)
def get_qv_field_list(fm, use_defaults=False):
from calibre.gui2.ui import get_gui
db = get_gui().current_db
if use_defaults:
src = db.prefs.defaults
else:
src = db.prefs
fieldlist = list(src['qv_display_fields'])
names = frozenset(x[0] for x in fieldlist)
for field in fm.displayable_field_keys():
if (field != 'comments' and fm[field]['datatype'] != 'comments' and field not in names):
fieldlist.append((field, False))
available = frozenset(fm.displayable_field_keys())
return [(f, d) for f, d in fieldlist if f in available]