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 (
Qt, QDialog, QAbstractItemView, QTableWidgetItem, QIcon, QListWidgetItem,
QCoreApplication, QEvent, QObject, QApplication, pyqtSignal, QByteArray, QMenu,
QShortcut)
QShortcut, QTimer)
from calibre.customize.ui import find_plugin
from calibre.gui2 import gprefs
@ -29,13 +29,18 @@ class TableItem(QTableWidgetItem):
A QTableWidgetItem that sorts on a separate string and uses ICU rules
'''
def __init__(self, val, sort, idx=0):
self.sort = sort
self.sort_idx = idx
QTableWidgetItem.__init__(self, val)
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 >=
@ -59,6 +64,8 @@ class TableItem(QTableWidgetItem):
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 <
@ -81,6 +88,17 @@ class TableItem(QTableWidgetItem):
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
@ -228,7 +246,7 @@ class Quickview(QDialog, Ui_Quickview):
# resizeRowsToContents can word wrap long cell contents, creating
# double-high rows
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_row_height = self.books_table.rowHeight(0)
self.books_table.setRowCount(0)
@ -236,10 +254,13 @@ class Quickview(QDialog, Ui_Quickview):
# Add the data
self.refresh(row)
self.view.clicked.connect(self.slave)
self.view.selectionModel().currentColumnChanged.connect(self.column_slave)
self.slave_timers = [QTimer(), QTimer(), QTimer()]
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(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_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: ') +
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):
tb = self.gui.stack.tb_widget
tb.set_focus_to_find_box()
@ -513,7 +542,7 @@ class Quickview(QDialog, Ui_Quickview):
self.items.clear()
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)
if self.fm[key]['datatype'] == 'composite' and self.fm[key]['is_multiple']:
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.'
) + '</p>')
for row, b in enumerate(books):
mi = self.db.new_api.get_proxy_metadata(b)
for col in self.column_order:
try:
if col == 'title':
a = TableItem(mi.title, mi.title_sort)
if b == self.current_book_id:
select_item = a
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 = 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)
@ -657,6 +651,45 @@ class Quickview(QDialog, Ui_Quickview):
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 ('{: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
# correct until the first paint.
def resizeEvent(self, *args):