Bug #1920733: Slight lag when viewing composite column in quickview.

This problem is caused by Quickview filling in the entite table whenever the book changes. Unfortunately this is necessary because the table is sorted.

I improved performance using two methods:
- Compute the data for a column only when it is needed. This means that in general only the data in the sorted column is computed before displaying the table.
- Delay a bit before redisplaying the table. This lets the main gui remain responsive at the cost of a small delay in seeing the quickview data.
This commit is contained in:
Charles Haley 2021-03-22 14:22:54 +00:00
parent 33eac400fd
commit 3c781d1334

View File

@ -12,7 +12,7 @@ from functools import partial
from qt.core import ( from qt.core import (
Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem, Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem,
QCoreApplication, QEvent, QObject, QApplication, pyqtSignal, QByteArray, QMenu, QCoreApplication, QEvent, QObject, QApplication, pyqtSignal, QByteArray, QMenu,
QShortcut) QShortcut, QTimer)
from calibre.customize.ui import find_plugin from calibre.customize.ui import find_plugin
from calibre.gui2 import gprefs from calibre.gui2 import gprefs
@ -29,13 +29,18 @@ class TableItem(QTableWidgetItem):
A QTableWidgetItem that sorts on a separate string and uses ICU rules A QTableWidgetItem that sorts on a separate string and uses ICU rules
''' '''
def __init__(self, val, sort, idx=0): def __init__(self, getter=None):
self.sort = sort self.val = ''
self.sort_idx = idx self.sort = None
QTableWidgetItem.__init__(self, val) self.sort_idx = 0
self.getter = getter
self.resolved = False
QTableWidgetItem.__init__(self, '')
self.setFlags(Qt.ItemFlag.ItemIsEnabled|Qt.ItemFlag.ItemIsSelectable) self.setFlags(Qt.ItemFlag.ItemIsEnabled|Qt.ItemFlag.ItemIsSelectable)
def __ge__(self, other): def __ge__(self, other):
self.get_data()
other.get_data()
if self.sort is None: if self.sort is None:
if other.sort is None: if other.sort is None:
# None == None therefore >= # None == None therefore >=
@ -59,6 +64,8 @@ class TableItem(QTableWidgetItem):
return 0 return 0
def __lt__(self, other): def __lt__(self, other):
self.get_data()
other.get_data()
if self.sort is None: if self.sort is None:
if other.sort is None: if other.sort is None:
# None == None therefore not < # None == None therefore not <
@ -81,6 +88,17 @@ class TableItem(QTableWidgetItem):
return self.sort_idx < other.sort_idx return self.sort_idx < other.sort_idx
return 0 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_ITEMS = 0
IN_WIDGET_BOOKS = 1 IN_WIDGET_BOOKS = 1
@ -228,7 +246,7 @@ class Quickview(QDialog, Ui_Quickview):
# resizeRowsToContents can word wrap long cell contents, creating # resizeRowsToContents can word wrap long cell contents, creating
# double-high rows # double-high rows
self.books_table.setRowCount(1) self.books_table.setRowCount(1)
self.books_table.setItem(0, 0, TableItem('A', '')) self.books_table.setItem(0, 0, TableItem())
self.books_table.resizeRowsToContents() self.books_table.resizeRowsToContents()
self.books_table_row_height = self.books_table.rowHeight(0) self.books_table_row_height = self.books_table.rowHeight(0)
self.books_table.setRowCount(0) self.books_table.setRowCount(0)
@ -236,10 +254,13 @@ class Quickview(QDialog, Ui_Quickview):
# Add the data # Add the data
self.refresh(row) self.refresh(row)
self.view.clicked.connect(self.slave) self.slave_timers = [QTimer(), QTimer(), QTimer()]
self.view.selectionModel().currentColumnChanged.connect(self.column_slave) 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) QCoreApplication.instance().aboutToQuit.connect(self.save_state)
self.view.model().new_bookdisplay_data.connect(self.book_was_changed) 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.setDefault(False)
self.close_button_tooltip = _('The Quickview shortcut ({0}) shows/hides the Quickview panel') self.close_button_tooltip = _('The Quickview shortcut ({0}) shows/hides the Quickview panel')
@ -285,6 +306,14 @@ class Quickview(QDialog, Ui_Quickview):
self.close_button.setToolTip(_('Alternate shortcut: ') + self.close_button.setToolTip(_('Alternate shortcut: ') +
toggle_shortcut.toString()) toggle_shortcut.toString())
def delayed_slave(self, current, func=None, dex=None):
self.slave_timers[dex].stop()
t = self.slave_timers[dex] = QTimer()
t.timeout.connect(partial(func, current))
t.setSingleShot(True)
t.setInterval(200)
t.start()
def item_doubleclicked(self, item): def item_doubleclicked(self, item):
tb = self.gui.stack.tb_widget tb = self.gui.stack.tb_widget
tb.set_focus_to_find_box() tb.set_focus_to_find_box()
@ -513,7 +542,7 @@ class Quickview(QDialog, Ui_Quickview):
self.items.clear() self.items.clear()
self.books_table.setRowCount(0) self.books_table.setRowCount(0)
mi = self.db.get_metadata(book_id, index_is_id=True, get_user_categories=False) mi = self.db.new_api.get_proxy_metadata(book_id)
vals = mi.get(key, None) vals = mi.get(key, None)
if self.fm[key]['datatype'] == 'composite' and self.fm[key]['is_multiple']: if self.fm[key]['datatype'] == 'composite' and self.fm[key]['is_multiple']:
sep = self.fm[key]['is_multiple'].get('cache_to_list', ',') sep = self.fm[key]['is_multiple'].get('cache_to_list', ',')
@ -605,47 +634,12 @@ class Quickview(QDialog, Ui_Quickview):
'which also changes the selected book.' 'which also changes the selected book.'
) + '</p>') ) + '</p>')
for row, b in enumerate(books): for row, b in enumerate(books):
mi = self.db.new_api.get_proxy_metadata(b)
for col in self.column_order: for col in self.column_order:
try: a = TableItem(partial(self.get_item_data, b, col))
if col == 'title': if col == 'title':
a = TableItem(mi.title, mi.title_sort) if b == self.current_book_id:
if b == self.current_book_id: select_item = a
select_item = a # The data is supplied on demand when the item is displayed
elif col == 'authors':
a = TableItem(' & '.join(mi.authors), mi.author_sort)
elif col == 'series':
series = mi.format_field('series')[1]
if series is None:
a = TableItem('', '', 0)
else:
a = TableItem(series, mi.series, mi.series_index)
elif col == 'size':
v = mi.get('book_size')
if v is not None:
a = TableItem('{:n}'.format(v), v)
a.setTextAlignment(Qt.AlignmentFlag.AlignRight)
else:
a = TableItem(' ', None)
elif self.fm[col]['datatype'] == 'series':
v = mi.format_field(col)[1]
a = TableItem(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
a = TableItem(v, timestampfromdt(d))
elif self.fm[col]['datatype'] in ('float', 'int'):
v = mi.format_field(col)[1]
sort_val = mi.get(col)
a = TableItem(v, sort_val)
else:
v = mi.format_field(col)[1]
a = TableItem(v, v)
except:
traceback.print_exc()
a = TableItem(_('Something went wrong while filling in the table'), '')
a.setData(Qt.ItemDataRole.UserRole, b) a.setData(Qt.ItemDataRole.UserRole, b)
a.setToolTip(tt) a.setToolTip(tt)
self.books_table.setItem(row, self.key_to_table_widget_column(col), a) self.books_table.setItem(row, self.key_to_table_widget_column(col), a)
@ -657,6 +651,45 @@ class Quickview(QDialog, Ui_Quickview):
self.books_table.scrollToItem(select_item, QAbstractItemView.ScrollHint.PositionAtCenter) self.books_table.scrollToItem(select_item, QAbstractItemView.ScrollHint.PositionAtCenter)
self.set_search_text(sv) 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 ('{:n}'.format(v), 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 # Deal with sizing the table columns. Done here because the numbers are not
# correct until the first paint. # correct until the first paint.
def resizeEvent(self, *args): def resizeEvent(self, *args):