mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
E-book viewer: Allow use of Ctrl+PgDn/PgUp to jump between sections as defined by the TOC
This commit is contained in:
parent
983d9280ec
commit
185c6d0f26
@ -513,6 +513,11 @@ class DocumentView(QWebView): # {{{
|
|||||||
def scroll_horizontally(self, amount):
|
def scroll_horizontally(self, amount):
|
||||||
self.document.scroll_to(y=self.document.ypos, x=amount)
|
self.document.scroll_to(y=self.document.ypos, x=amount)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scroll_pos(self):
|
||||||
|
return (self.document.ypos, self.document.ypos +
|
||||||
|
self.document.window_height)
|
||||||
|
|
||||||
def link_hovered(self, link, text, context):
|
def link_hovered(self, link, text, context):
|
||||||
link, text = unicode(link), unicode(text)
|
link, text = unicode(link), unicode(text)
|
||||||
if link:
|
if link:
|
||||||
|
@ -493,8 +493,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.pending_bookmark = None
|
self.pending_bookmark = None
|
||||||
self.load_path(self.iterator.spine[spine_index])
|
self.load_path(self.iterator.spine[spine_index])
|
||||||
|
|
||||||
def toc_clicked(self, index):
|
def toc_clicked(self, index, force=False):
|
||||||
if QApplication.mouseButtons() & Qt.LeftButton:
|
if force or QApplication.mouseButtons() & Qt.LeftButton:
|
||||||
item = self.toc_model.itemFromIndex(index)
|
item = self.toc_model.itemFromIndex(index)
|
||||||
if item.abspath is not None:
|
if item.abspath is not None:
|
||||||
if not os.path.exists(item.abspath):
|
if not os.path.exists(item.abspath):
|
||||||
@ -625,11 +625,17 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
self.pending_anchor = frag
|
self.pending_anchor = frag
|
||||||
self.load_path(path)
|
self.load_path(path)
|
||||||
else:
|
else:
|
||||||
|
oldpos = self.view.document.ypos
|
||||||
if frag:
|
if frag:
|
||||||
self.view.scroll_to(frag)
|
self.view.scroll_to(frag)
|
||||||
else:
|
else:
|
||||||
# Scroll to top
|
# Scroll to top
|
||||||
self.view.scroll_to('#')
|
self.view.scroll_to('#')
|
||||||
|
if self.view.document.ypos == oldpos:
|
||||||
|
# If we are coming from goto_next_section() call this will
|
||||||
|
# cause another goto next section call with the next toc
|
||||||
|
# entry, since this one did not cause any scrolling at all.
|
||||||
|
QTimer.singleShot(10, self.update_indexing_state)
|
||||||
else:
|
else:
|
||||||
open_url(url)
|
open_url(url)
|
||||||
|
|
||||||
@ -664,13 +670,41 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
return self.current_index
|
return self.current_index
|
||||||
|
|
||||||
def goto_next_section(self):
|
def goto_next_section(self):
|
||||||
nindex = (self.current_index + 1)%len(self.iterator.spine)
|
if hasattr(self, 'current_index'):
|
||||||
self.load_path(self.iterator.spine[nindex])
|
entry = self.toc_model.next_entry(self.current_index)
|
||||||
|
if entry is not None:
|
||||||
|
self.pending_goto_next_section = (
|
||||||
|
self.toc_model.currently_viewed_entry, entry, False)
|
||||||
|
self.toc_clicked(entry.index(), force=True)
|
||||||
|
|
||||||
def goto_previous_section(self):
|
def goto_previous_section(self):
|
||||||
pindex = (self.current_index - 1 + len(self.iterator.spine)) \
|
if hasattr(self, 'current_index'):
|
||||||
% len(self.iterator.spine)
|
entry = self.toc_model.next_entry(self.current_index, backwards=True)
|
||||||
self.load_path(self.iterator.spine[pindex])
|
if entry is not None:
|
||||||
|
self.pending_goto_next_section = (
|
||||||
|
self.toc_model.currently_viewed_entry, entry, True)
|
||||||
|
self.toc_clicked(entry.index(), force=True)
|
||||||
|
|
||||||
|
def update_indexing_state(self, anchor_positions=None):
|
||||||
|
pgns = getattr(self, 'pending_goto_next_section', None)
|
||||||
|
if hasattr(self, 'current_index'):
|
||||||
|
if anchor_positions is None:
|
||||||
|
anchor_positions = self.view.document.read_anchor_positions()
|
||||||
|
items = self.toc_model.update_indexing_state(self.current_index,
|
||||||
|
self.view.scroll_pos, anchor_positions)
|
||||||
|
if items:
|
||||||
|
self.toc.scrollTo(items[-1].index())
|
||||||
|
if pgns is not None:
|
||||||
|
self.pending_goto_next_section = None
|
||||||
|
# Check that we actually progressed
|
||||||
|
if pgns[0] is self.toc_model.currently_viewed_entry:
|
||||||
|
entry = self.toc_model.next_entry(self.current_index,
|
||||||
|
backwards=pgns[2], current_entry=pgns[1])
|
||||||
|
if entry is not None:
|
||||||
|
self.pending_goto_next_section = (
|
||||||
|
self.toc_model.currently_viewed_entry, entry,
|
||||||
|
pgns[2])
|
||||||
|
self.toc_clicked(entry.index(), force=True)
|
||||||
|
|
||||||
def load_path(self, path, pos=0.0):
|
def load_path(self, path, pos=0.0):
|
||||||
self.open_progress_indicator(_('Laying out %s')%self.current_title)
|
self.open_progress_indicator(_('Laying out %s')%self.current_title)
|
||||||
@ -868,15 +902,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
ap = self.view.document.read_anchor_positions()
|
ap = self.view.document.read_anchor_positions()
|
||||||
self.update_indexing_state(ap)
|
self.update_indexing_state(ap)
|
||||||
|
|
||||||
def update_indexing_state(self, anchor_positions=None):
|
|
||||||
if hasattr(self, 'current_index'):
|
|
||||||
if anchor_positions is None:
|
|
||||||
anchor_positions = self.view.document.read_anchor_positions()
|
|
||||||
items = self.toc_model.update_indexing_state(self.current_index,
|
|
||||||
self.view.document.ypos, anchor_positions)
|
|
||||||
if items:
|
|
||||||
self.toc.scrollTo(items[-1].index())
|
|
||||||
|
|
||||||
def next_document(self):
|
def next_document(self):
|
||||||
if (hasattr(self, 'current_index') and self.current_index <
|
if (hasattr(self, 'current_index') and self.current_index <
|
||||||
len(self.iterator.spine) - 1):
|
len(self.iterator.spine) - 1):
|
||||||
|
@ -51,20 +51,27 @@ class TOCItem(QStandardItem):
|
|||||||
|
|
||||||
def update_indexing_state(self, spine_index, scroll_pos, anchor_map):
|
def update_indexing_state(self, spine_index, scroll_pos, anchor_map):
|
||||||
is_being_viewed = False
|
is_being_viewed = False
|
||||||
|
top, bottom = scroll_pos
|
||||||
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
||||||
start_pos = anchor_map.get(self.start_anchor, 0)
|
start_pos = anchor_map.get(self.start_anchor, 0)
|
||||||
psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors]
|
psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors]
|
||||||
if self.ends_at == spine_index:
|
if self.ends_at == spine_index:
|
||||||
psp = [x for x in psp if x >= start_pos]
|
psp = [x for x in psp if x >= start_pos]
|
||||||
end_pos = min(psp) if psp else (scroll_pos+1 if self.ends_at ==
|
end_pos = min(psp) if psp else (bottom+1 if self.ends_at ==
|
||||||
spine_index else 0)
|
spine_index else 0)
|
||||||
if spine_index > self.starts_at and spine_index < self.ends_at:
|
if spine_index > self.starts_at and spine_index < self.ends_at:
|
||||||
is_being_viewed = True
|
is_being_viewed = True
|
||||||
elif spine_index == self.starts_at and scroll_pos >= start_pos:
|
elif spine_index == self.starts_at and top >= start_pos:
|
||||||
if spine_index != self.ends_at or scroll_pos < end_pos:
|
if spine_index != self.ends_at or top < end_pos:
|
||||||
is_being_viewed = True
|
is_being_viewed = True
|
||||||
elif spine_index == self.ends_at and scroll_pos < end_pos:
|
elif spine_index == self.ends_at and top < end_pos:
|
||||||
if spine_index != self.starts_at or scroll_pos >= start_pos:
|
if spine_index != self.starts_at or bottom-25 >= start_pos:
|
||||||
|
# We use -25 to account for the case where the next entry
|
||||||
|
# has some invisible margin that just overlaps with the
|
||||||
|
# bottom of the screen. In this case it will appear to the
|
||||||
|
# user that the entry is not visible on the screen. Of
|
||||||
|
# course, the margin could be larger than 25, but that's a
|
||||||
|
# decent compromise.
|
||||||
is_being_viewed = True
|
is_being_viewed = True
|
||||||
changed = is_being_viewed != self.is_being_viewed
|
changed = is_being_viewed != self.is_being_viewed
|
||||||
self.is_being_viewed = is_being_viewed
|
self.is_being_viewed = is_being_viewed
|
||||||
@ -73,6 +80,12 @@ class TOCItem(QStandardItem):
|
|||||||
self.setBackground(self.alternate_base if is_being_viewed else
|
self.setBackground(self.alternate_base if is_being_viewed else
|
||||||
self.base)
|
self.base)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'TOC Item: %s %s#%s'%(self.title, self.abspath, self.fragment)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
class TOC(QStandardItemModel):
|
class TOC(QStandardItemModel):
|
||||||
|
|
||||||
def __init__(self, spine, toc=None):
|
def __init__(self, spine, toc=None):
|
||||||
@ -97,11 +110,29 @@ class TOC(QStandardItemModel):
|
|||||||
x.ends_at = min_spine
|
x.ends_at = min_spine
|
||||||
x.possible_end_anchors = possible_enders
|
x.possible_end_anchors = possible_enders
|
||||||
|
|
||||||
|
self.currently_viewed_entry = None
|
||||||
|
|
||||||
def update_indexing_state(self, *args):
|
def update_indexing_state(self, *args):
|
||||||
items_being_viewed = []
|
items_being_viewed = []
|
||||||
for t in self.all_items:
|
for t in self.all_items:
|
||||||
t.update_indexing_state(*args)
|
t.update_indexing_state(*args)
|
||||||
if t.is_being_viewed:
|
if t.is_being_viewed:
|
||||||
items_being_viewed.append(t)
|
items_being_viewed.append(t)
|
||||||
|
self.currently_viewed_entry = t
|
||||||
return items_being_viewed
|
return items_being_viewed
|
||||||
|
|
||||||
|
def next_entry(self, spine_pos, backwards=False, current_entry=None):
|
||||||
|
current_entry = (self.currently_viewed_entry if current_entry is None
|
||||||
|
else current_entry)
|
||||||
|
if current_entry is None: return
|
||||||
|
items = reversed(self.all_items) if backwards else self.all_items
|
||||||
|
found = False
|
||||||
|
for item in items:
|
||||||
|
if found:
|
||||||
|
if item.starts_at != spine_pos or item.start_anchor:
|
||||||
|
return item
|
||||||
|
if item is current_entry:
|
||||||
|
found = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user