mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Another try at debouncing book details.
1) The debouncing is done where the display is called: views.py and book_details.py. 2) The 250ms timeout defined in book_info.py is arbitrary. I like it but some might want it smaller. 3) This commit implements delayed evaluation of composites when using get_metadata().
This commit is contained in:
parent
57da1f5320
commit
642d44abbf
@ -394,19 +394,16 @@ class Cache:
|
||||
default_value={}))
|
||||
mi.application_id = book_id
|
||||
mi.id = book_id
|
||||
composites = []
|
||||
for key, meta in self.field_metadata.custom_iteritems():
|
||||
mi.set_user_metadata(key, meta)
|
||||
if meta['datatype'] == 'composite':
|
||||
composites.append(key)
|
||||
else:
|
||||
if meta['datatype'] != 'composite':
|
||||
# composites are evaluated on demand in metadata.book.base
|
||||
# because their value is None
|
||||
val = self._field_for(key, book_id)
|
||||
if isinstance(val, tuple):
|
||||
val = list(val)
|
||||
extra = self._field_for(key+'_index', book_id)
|
||||
mi.set(key, val=val, extra=extra)
|
||||
for key in composites:
|
||||
mi.set(key, val=self._composite_for(key, book_id, mi))
|
||||
|
||||
mi.link_maps = self._get_all_link_maps_for_book(book_id)
|
||||
|
||||
|
@ -150,9 +150,7 @@ class Metadata:
|
||||
if field in _data['user_metadata']:
|
||||
d = _data['user_metadata'][field]
|
||||
val = d['#value#']
|
||||
if d['datatype'] != 'composite':
|
||||
return val
|
||||
if val is None:
|
||||
if val is None and d['datatype'] == 'composite':
|
||||
d['#value#'] = 'RECURSIVE_COMPOSITE FIELD (Metadata) ' + field
|
||||
val = d['#value#'] = self.formatter.safe_format(
|
||||
d['display']['composite_template'],
|
||||
@ -191,8 +189,9 @@ class Metadata:
|
||||
langs = [val]
|
||||
_data['languages'] = langs
|
||||
elif field in _data['user_metadata']:
|
||||
_data['user_metadata'][field]['#value#'] = val
|
||||
_data['user_metadata'][field]['#extra#'] = extra
|
||||
d = _data['user_metadata'][field]
|
||||
d['#value#'] = val
|
||||
d['#extra#'] = extra
|
||||
else:
|
||||
# You are allowed to stick arbitrary attributes onto this object as
|
||||
# long as they don't conflict with global or user metadata names
|
||||
@ -205,11 +204,24 @@ class Metadata:
|
||||
def has_key(self, key):
|
||||
return key in object.__getattribute__(self, '_data')
|
||||
|
||||
def _evaluate_all_composites(self):
|
||||
custom_fields = object.__getattribute__(self, '_data')['user_metadata']
|
||||
for field in custom_fields:
|
||||
self._evaluate_composite(field)
|
||||
|
||||
def _evaluate_composite(self, field):
|
||||
f = object.__getattribute__(self, '_data')['user_metadata'].get(field, None)
|
||||
if f is not None:
|
||||
if f['datatype'] == 'composite' and f['#value#'] is None:
|
||||
self.get(field)
|
||||
|
||||
def deepcopy(self, class_generator=lambda : Metadata(None)):
|
||||
''' Do not use this method unless you know what you are doing, if you
|
||||
want to create a simple clone of this object, use :meth:`deepcopy_metadata`
|
||||
instead. Class_generator must be a function that returns an instance
|
||||
of Metadata or a subclass of it.'''
|
||||
# We don't need to evaluate all the composites here because we
|
||||
# are returning a "real" Metadata instance that has __get_attribute__.
|
||||
m = class_generator()
|
||||
if not isinstance(m, Metadata):
|
||||
return None
|
||||
@ -217,6 +229,8 @@ class Metadata:
|
||||
return m
|
||||
|
||||
def deepcopy_metadata(self):
|
||||
# We don't need to evaluate all the composites here because we
|
||||
# are returning a "real" Metadata instance that has __get_attribute__.
|
||||
m = Metadata(None)
|
||||
object.__setattr__(m, '_data', copy.deepcopy(object.__getattribute__(self, '_data')))
|
||||
return m
|
||||
@ -228,6 +242,8 @@ class Metadata:
|
||||
return default
|
||||
|
||||
def get_extra(self, field, default=None):
|
||||
# Don't need to evaluate all composites because a composite can't have
|
||||
# an extra value
|
||||
_data = object.__getattribute__(self, '_data')
|
||||
if field in _data['user_metadata']:
|
||||
try:
|
||||
@ -247,8 +263,7 @@ class Metadata:
|
||||
needed is large. Also, we don't want any manipulations of the returned
|
||||
dict to show up in the book.
|
||||
'''
|
||||
ans = object.__getattribute__(self,
|
||||
'_data')['identifiers']
|
||||
ans = object.__getattribute__(self, '_data')['identifiers']
|
||||
if not ans:
|
||||
ans = {}
|
||||
return copy.deepcopy(ans)
|
||||
@ -273,16 +288,14 @@ class Metadata:
|
||||
typ, val = self._clean_identifier(typ, val)
|
||||
if not typ:
|
||||
return
|
||||
identifiers = object.__getattribute__(self,
|
||||
'_data')['identifiers']
|
||||
identifiers = object.__getattribute__(self, '_data')['identifiers']
|
||||
|
||||
identifiers.pop(typ, None)
|
||||
if val:
|
||||
identifiers[typ] = val
|
||||
|
||||
def has_identifier(self, typ):
|
||||
identifiers = object.__getattribute__(self,
|
||||
'_data')['identifiers']
|
||||
identifiers = object.__getattribute__(self, '_data')['identifiers']
|
||||
return typ in identifiers
|
||||
|
||||
# field-oriented interface. Intended to be the same as in LibraryDatabase
|
||||
@ -373,6 +386,9 @@ class Metadata:
|
||||
return a dict containing all the custom field metadata associated with
|
||||
the book.
|
||||
'''
|
||||
# Must evaluate all composites because we are returning a dict, not a
|
||||
# Metadata instance
|
||||
self._evaluate_all_composites()
|
||||
_data = object.__getattribute__(self, '_data')
|
||||
user_metadata = _data['user_metadata']
|
||||
if not make_copy:
|
||||
@ -388,9 +404,12 @@ class Metadata:
|
||||
None. field is the key name, not the label. Return a copy if requested,
|
||||
just in case the user wants to change values in the dict.
|
||||
'''
|
||||
_data = object.__getattribute__(self, '_data')
|
||||
_data = _data['user_metadata']
|
||||
_data = object.__getattribute__(self, '_data')['user_metadata']
|
||||
if field in _data:
|
||||
# Must evaluate the field because it might be a composite. It won't
|
||||
# be evaluated on demand because we are returning its dict, not a
|
||||
# Metadata instance
|
||||
self._evaluate_composite(field)
|
||||
if make_copy:
|
||||
return copy.deepcopy(_data[field])
|
||||
return _data[field]
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
import textwrap
|
||||
from enum import IntEnum
|
||||
from functools import partial
|
||||
|
||||
from qt.core import (
|
||||
QAction, QApplication, QBrush, QCheckBox, QDialog, QDialogButtonBox, QGridLayout,
|
||||
QHBoxLayout, QIcon, QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap,
|
||||
@ -22,6 +24,8 @@ from calibre.gui2.widgets import CoverView
|
||||
from calibre.gui2.widgets2 import Dialog, HTMLDisplay
|
||||
from calibre.startup import connect_lambda
|
||||
|
||||
BOOK_DETAILS_DISPLAY_DELAY = 250 # 250ms is arbitrary
|
||||
|
||||
|
||||
class Cover(CoverView):
|
||||
|
||||
@ -221,6 +225,7 @@ class BookInfo(QDialog):
|
||||
self.path_to_book = None
|
||||
self.current_row = None
|
||||
self.slave_connected = False
|
||||
self.slave_debounce_timer = QTimer()
|
||||
if library_path is not None:
|
||||
self.view = None
|
||||
db = get_gui().library_broker.get_library(library_path)
|
||||
@ -319,6 +324,7 @@ class BookInfo(QDialog):
|
||||
ret = QDialog.done(self, r)
|
||||
if self.slave_connected:
|
||||
self.view.model().new_bookdisplay_data.disconnect(self.slave)
|
||||
self.slave_debounce_timer.stop() # OK if it isn't running
|
||||
self.view = self.link_delegate = self.gui = None
|
||||
self.closed.emit(self)
|
||||
return ret
|
||||
@ -343,6 +349,13 @@ class BookInfo(QDialog):
|
||||
QTimer.singleShot(1, self.resize_cover)
|
||||
|
||||
def slave(self, mi):
|
||||
self.slave_debounce_timer.stop()
|
||||
t = self.book_display_info_timer = QTimer()
|
||||
t.setSingleShot(True)
|
||||
t.timeout.connect(partial(self._timed_slave, mi))
|
||||
t.start(BOOK_DETAILS_DISPLAY_DELAY)
|
||||
|
||||
def _timed_slave(self, mi):
|
||||
self.refresh(mi.row_number, mi)
|
||||
|
||||
def move(self, delta=1):
|
||||
|
@ -13,7 +13,7 @@ from qt.core import (
|
||||
QAbstractItemView, QDialog, QDialogButtonBox, QDrag, QEvent, QFont, QFontMetrics,
|
||||
QGridLayout, QHeaderView, QIcon, QItemSelection, QItemSelectionModel, QLabel, QMenu,
|
||||
QMimeData, QModelIndex, QPoint, QPushButton, QSize, QSpinBox, QStyle,
|
||||
QStyleOptionHeader, Qt, QTableView, QUrl, pyqtSignal,
|
||||
QStyleOptionHeader, Qt, QTableView, QTimer, QUrl, pyqtSignal,
|
||||
)
|
||||
|
||||
from calibre import force_unicode
|
||||
@ -1615,7 +1615,16 @@ class BooksView(QTableView): # {{{
|
||||
self._model.search_done.connect(self.alternate_views.restore_current_book_state)
|
||||
|
||||
def connect_to_book_display(self, bd):
|
||||
self._model.new_bookdisplay_data.connect(bd)
|
||||
self.connect_to_book_display_timer = QTimer()
|
||||
self._model.new_bookdisplay_data.connect(partial(self._timed_connect_to_book_display, bd))
|
||||
|
||||
def _timed_connect_to_book_display(self, bd, data):
|
||||
self.connect_to_book_display_timer.stop()
|
||||
t = self.connect_to_book_display_timer = QTimer()
|
||||
t.setSingleShot(True)
|
||||
t.timeout.connect(partial(bd, data))
|
||||
from calibre.gui2.dialogs.book_info import BOOK_DETAILS_DISPLAY_DELAY
|
||||
t.start(BOOK_DETAILS_DISPLAY_DELAY)
|
||||
|
||||
def search_done(self, ok):
|
||||
self._search_done(self, ok)
|
||||
|
Loading…
x
Reference in New Issue
Block a user