diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 650911d9ca..d07e9516fd 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -513,6 +513,11 @@ class DocumentView(QWebView): # {{{ def scroll_horizontally(self, 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): link, text = unicode(link), unicode(text) if link: diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index f368dfd270..48927f0e4a 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -493,8 +493,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pending_bookmark = None self.load_path(self.iterator.spine[spine_index]) - def toc_clicked(self, index): - if QApplication.mouseButtons() & Qt.LeftButton: + def toc_clicked(self, index, force=False): + if force or QApplication.mouseButtons() & Qt.LeftButton: item = self.toc_model.itemFromIndex(index) if item.abspath is not None: if not os.path.exists(item.abspath): @@ -625,11 +625,17 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pending_anchor = frag self.load_path(path) else: + oldpos = self.view.document.ypos if frag: self.view.scroll_to(frag) else: # Scroll to top 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: open_url(url) @@ -664,13 +670,41 @@ class EbookViewer(MainWindow, Ui_EbookViewer): return self.current_index def goto_next_section(self): - nindex = (self.current_index + 1)%len(self.iterator.spine) - self.load_path(self.iterator.spine[nindex]) + if hasattr(self, 'current_index'): + 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): - pindex = (self.current_index - 1 + len(self.iterator.spine)) \ - % len(self.iterator.spine) - self.load_path(self.iterator.spine[pindex]) + if hasattr(self, 'current_index'): + entry = self.toc_model.next_entry(self.current_index, backwards=True) + 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): 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() 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): if (hasattr(self, 'current_index') and self.current_index < len(self.iterator.spine) - 1): diff --git a/src/calibre/gui2/viewer/toc.py b/src/calibre/gui2/viewer/toc.py index d05c2048e7..5807404b08 100644 --- a/src/calibre/gui2/viewer/toc.py +++ b/src/calibre/gui2/viewer/toc.py @@ -51,20 +51,27 @@ class TOCItem(QStandardItem): def update_indexing_state(self, spine_index, scroll_pos, anchor_map): is_being_viewed = False + top, bottom = scroll_pos if spine_index >= self.starts_at and spine_index <= self.ends_at: start_pos = anchor_map.get(self.start_anchor, 0) psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors] if self.ends_at == spine_index: 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) if spine_index > self.starts_at and spine_index < self.ends_at: is_being_viewed = True - elif spine_index == self.starts_at and scroll_pos >= start_pos: - if spine_index != self.ends_at or scroll_pos < end_pos: + elif spine_index == self.starts_at and top >= start_pos: + if spine_index != self.ends_at or top < end_pos: is_being_viewed = True - elif spine_index == self.ends_at and scroll_pos < end_pos: - if spine_index != self.starts_at or scroll_pos >= start_pos: + elif spine_index == self.ends_at and top < end_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 changed = is_being_viewed != self.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.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): def __init__(self, spine, toc=None): @@ -97,11 +110,29 @@ class TOC(QStandardItemModel): x.ends_at = min_spine x.possible_end_anchors = possible_enders + self.currently_viewed_entry = None + def update_indexing_state(self, *args): items_being_viewed = [] for t in self.all_items: t.update_indexing_state(*args) if t.is_being_viewed: items_being_viewed.append(t) + self.currently_viewed_entry = t 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 + + +