mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Paged display: Indexing now (mostly) works
This commit is contained in:
parent
8cab25887d
commit
93a370ef8c
@ -6,20 +6,34 @@
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
body_height = () ->
|
||||
db = document.body
|
||||
dde = document.documentElement
|
||||
if db? and dde?
|
||||
return Math.max(db.scrollHeight, dde.scrollHeight, db.offsetHeight,
|
||||
dde.offsetHeight, db.clientHeight, dde.clientHeight)
|
||||
return 0
|
||||
window_scroll_pos = (win=window) -> # {{{
|
||||
if typeof(win.pageXOffset) == 'number'
|
||||
x = win.pageXOffset
|
||||
y = win.pageYOffset
|
||||
else # IE < 9
|
||||
if document.body and ( document.body.scrollLeft or document.body.scrollTop )
|
||||
x = document.body.scrollLeft
|
||||
y = document.body.scrollTop
|
||||
else if document.documentElement and ( document.documentElement.scrollLeft or document.documentElement.scrollTop)
|
||||
y = document.documentElement.scrollTop
|
||||
x = document.documentElement.scrollLeft
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
abstop = (elem) ->
|
||||
ans = elem.offsetTop
|
||||
while elem.offsetParent
|
||||
elem = elem.offsetParent
|
||||
ans += elem.offsetTop
|
||||
return ans
|
||||
viewport_to_document = (x, y, doc=window?.document) -> # {{{
|
||||
until doc == window.document
|
||||
# We are in a frame
|
||||
frame = doc.defaultView.frameElement
|
||||
rect = frame.getBoundingClientRect()
|
||||
x += rect.left
|
||||
y += rect.top
|
||||
doc = frame.ownerDocument
|
||||
win = doc.defaultView
|
||||
[wx, wy] = window_scroll_pos(win)
|
||||
x += wx
|
||||
y += wy
|
||||
return [x, y]
|
||||
# }}}
|
||||
|
||||
class BookIndexing
|
||||
###
|
||||
@ -33,7 +47,7 @@ class BookIndexing
|
||||
|
||||
constructor: () ->
|
||||
this.cache = {}
|
||||
this.body_height_at_last_check = null
|
||||
this.last_check = [null, null]
|
||||
|
||||
cache_valid: (anchors) ->
|
||||
for a in anchors
|
||||
@ -45,7 +59,9 @@ class BookIndexing
|
||||
return true
|
||||
|
||||
anchor_positions: (anchors, use_cache=false) ->
|
||||
if use_cache and body_height() == this.body_height_at_last_check and this.cache_valid(anchors)
|
||||
body = document.body
|
||||
doc_constant = body.scrollHeight == this.last_check[1] and body.scrollWidth == this.last_check[0]
|
||||
if use_cache and doc_constant and this.cache_valid(anchors)
|
||||
return this.cache
|
||||
|
||||
ans = {}
|
||||
@ -56,19 +72,24 @@ class BookIndexing
|
||||
try
|
||||
result = document.evaluate(
|
||||
".//*[local-name() = 'a' and @name='#{ anchor }']",
|
||||
document.body, null,
|
||||
body, null,
|
||||
XPathResult.FIRST_ORDERED_NODE_TYPE, null)
|
||||
elem = result.singleNodeValue
|
||||
catch error
|
||||
# The anchor had a ' or other invalid char
|
||||
elem = null
|
||||
if elem == null
|
||||
pos = body_height() + 10000
|
||||
pos = [body.scrollWidth+1000, body.scrollHeight+1000]
|
||||
else
|
||||
pos = abstop(elem)
|
||||
br = elem.getBoundingClientRect()
|
||||
pos = viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||
|
||||
if window.paged_display?.in_paged_mode
|
||||
pos[0] = window.paged_display.column_at(pos[0])
|
||||
ans[anchor] = pos
|
||||
|
||||
this.cache = ans
|
||||
this.body_height_at_last_check = body_height()
|
||||
this.last_check = [body.scrollWidth, body.scrollHeight]
|
||||
return ans
|
||||
|
||||
if window?
|
||||
|
@ -170,9 +170,7 @@ class PagedDisplay
|
||||
if this.is_full_screen_layout
|
||||
window.scrollTo(0, 0)
|
||||
return
|
||||
pos = 0
|
||||
until (pos <= xpos < pos + this.page_width)
|
||||
pos += this.page_width
|
||||
pos = Math.floor(xpos/this.page_width) * this.page_width
|
||||
limit = document.body.scrollWidth - this.screen_width
|
||||
pos = limit if pos > limit
|
||||
if animated
|
||||
@ -180,6 +178,16 @@ class PagedDisplay
|
||||
else
|
||||
window.scrollTo(pos, 0)
|
||||
|
||||
column_at: (xpos) ->
|
||||
# Return the number of the column that contains xpos
|
||||
return Math.floor(xpos/this.page_width)
|
||||
|
||||
column_boundaries: () ->
|
||||
# Return the column numbers at the left edge and after the right edge
|
||||
# of the viewport
|
||||
l = this.column_at(window.pageXOffset + 10)
|
||||
return [l, l + this.cols_per_screen]
|
||||
|
||||
animated_scroll: (pos, duration=1000, notify=true) ->
|
||||
# Scroll the window to X-position pos in an animated fashion over
|
||||
# duration milliseconds. If notify is true, py_bridge.animated_scroll_done is
|
||||
@ -217,10 +225,7 @@ class PagedDisplay
|
||||
if this.is_full_screen_layout
|
||||
return 0
|
||||
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||
edge = Math.floor(x/this.page_width) * this.page_width
|
||||
while edge < x
|
||||
edge += this.page_width
|
||||
return edge - this.page_width
|
||||
return Math.floor(x/this.page_width) * this.page_width
|
||||
|
||||
next_screen_location: () ->
|
||||
# The position to scroll to for the next screen (which could contain
|
||||
@ -354,7 +359,6 @@ if window?
|
||||
window.paged_display = new PagedDisplay()
|
||||
|
||||
# TODO:
|
||||
# Indexing
|
||||
# Resizing of images
|
||||
# Full screen mode
|
||||
# Highlight on jump_to_anchor
|
||||
|
@ -202,7 +202,7 @@ class Document(QWebPage): # {{{
|
||||
if not isinstance(self.anchor_positions, dict):
|
||||
# Some weird javascript error happened
|
||||
self.anchor_positions = {}
|
||||
return self.anchor_positions
|
||||
return {k:tuple(v) for k, v in self.anchor_positions.iteritems()}
|
||||
|
||||
def switch_to_paged_mode(self, onresize=False):
|
||||
if onresize and not self.loaded_javascript:
|
||||
@ -217,6 +217,13 @@ class Document(QWebPage): # {{{
|
||||
sz.setWidth(sz.width()+side_margin)
|
||||
self.setPreferredContentsSize(sz)
|
||||
|
||||
@property
|
||||
def column_boundaries(self):
|
||||
if not self.loaded_javascript:
|
||||
return (0, 1)
|
||||
self.javascript(u'py_bridge.value = paged_display.column_boundaries()')
|
||||
return tuple(self.bridge_value)
|
||||
|
||||
def after_resize(self):
|
||||
if self.in_paged_mode:
|
||||
self.setPreferredContentsSize(QSize())
|
||||
@ -558,6 +565,18 @@ class DocumentView(QWebView): # {{{
|
||||
return (self.document.ypos, self.document.ypos +
|
||||
self.document.window_height)
|
||||
|
||||
@property
|
||||
def viewport_rect(self):
|
||||
# (left, top, right, bottom) of the viewport in document co-ordinates
|
||||
# When in paged mode, left and right are the numbers of the columns
|
||||
# at the left edge and *after* the right edge of the viewport
|
||||
d = self.document
|
||||
if d.in_paged_mode:
|
||||
l, r = d.column_boundaries
|
||||
else:
|
||||
l, r = d.xpos, d.xpos + d.window_width
|
||||
return (l, d.ypos, r, d.ypos + d.window_height)
|
||||
|
||||
def link_hovered(self, link, text, context):
|
||||
link, text = unicode(link), unicode(text)
|
||||
if link:
|
||||
|
@ -683,7 +683,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if hasattr(self, 'current_index'):
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
self.view.document.read_anchor_positions(),
|
||||
self.view.scroll_pos)
|
||||
self.view.viewport_rect, self.view.document.in_paged_mode)
|
||||
if entry is not None:
|
||||
self.pending_goto_next_section = (
|
||||
self.toc_model.currently_viewed_entry, entry, False)
|
||||
@ -693,7 +693,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if hasattr(self, 'current_index'):
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
self.view.document.read_anchor_positions(),
|
||||
self.view.scroll_pos, backwards=True)
|
||||
self.view.viewport_rect, self.view.document.in_paged_mode,
|
||||
backwards=True)
|
||||
if entry is not None:
|
||||
self.pending_goto_next_section = (
|
||||
self.toc_model.currently_viewed_entry, entry, True)
|
||||
@ -705,7 +706,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
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)
|
||||
self.view.viewport_rect, anchor_positions,
|
||||
self.view.document.in_paged_mode)
|
||||
if items:
|
||||
self.toc.scrollTo(items[-1].index())
|
||||
if pgns is not None:
|
||||
@ -714,7 +716,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if pgns[0] is self.toc_model.currently_viewed_entry:
|
||||
entry = self.toc_model.next_entry(self.current_index,
|
||||
self.view.document.read_anchor_positions(),
|
||||
self.view.scroll_pos,
|
||||
self.view.viewport_rect,
|
||||
self.view.document.in_paged_mode,
|
||||
backwards=pgns[2], current_entry=pgns[1])
|
||||
if entry is not None:
|
||||
self.pending_goto_next_section = (
|
||||
|
@ -93,9 +93,19 @@ class TOCItem(QStandardItem):
|
||||
def type(cls):
|
||||
return QStandardItem.UserType+10
|
||||
|
||||
def update_indexing_state(self, spine_index, scroll_pos, anchor_map):
|
||||
def update_indexing_state(self, spine_index, viewport_rect, anchor_map,
|
||||
in_paged_mode):
|
||||
if in_paged_mode:
|
||||
self.update_indexing_state_paged(spine_index, viewport_rect,
|
||||
anchor_map)
|
||||
else:
|
||||
self.update_indexing_state_unpaged(spine_index, viewport_rect,
|
||||
anchor_map)
|
||||
|
||||
def update_indexing_state_unpaged(self, spine_index, viewport_rect,
|
||||
anchor_map):
|
||||
is_being_viewed = False
|
||||
top, bottom = scroll_pos
|
||||
top, bottom = viewport_rect[1], viewport_rect[3]
|
||||
# We use bottom-25 in the checks below 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
|
||||
@ -103,6 +113,9 @@ class TOCItem(QStandardItem):
|
||||
# be larger than 25, but that's a decent compromise. Also we dont want
|
||||
# to count a partial line as being visible.
|
||||
|
||||
# We only care about y position
|
||||
anchor_map = {k:v[1] for k, v in anchor_map.iteritems()}
|
||||
|
||||
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
||||
# The position at which this anchor is present in the document
|
||||
start_pos = anchor_map.get(self.start_anchor, 0)
|
||||
@ -115,7 +128,7 @@ class TOCItem(QStandardItem):
|
||||
# ancestors of this entry.
|
||||
psp = [anchor_map.get(x, 0) for x in self.possible_end_anchors]
|
||||
psp = [x for x in psp if x >= start_pos]
|
||||
# The end position. The first anchor whose pos is >= self.start_pos
|
||||
# The end position. The first anchor whose pos is >= start_pos
|
||||
# or if the end is not in this spine item, we set it to the bottom
|
||||
# of the window +1
|
||||
end_pos = min(psp) if psp else (bottom+1 if self.ends_at >=
|
||||
@ -141,6 +154,51 @@ class TOCItem(QStandardItem):
|
||||
if changed:
|
||||
self.setFont(self.bold_font if is_being_viewed else self.normal_font)
|
||||
|
||||
def update_indexing_state_paged(self, spine_index, viewport_rect,
|
||||
anchor_map):
|
||||
is_being_viewed = False
|
||||
|
||||
left, right = viewport_rect[0], viewport_rect[2]
|
||||
left, right = (left, 0), (right, -1)
|
||||
|
||||
if spine_index >= self.starts_at and spine_index <= self.ends_at:
|
||||
# The position at which this anchor is present in the document
|
||||
start_pos = anchor_map.get(self.start_anchor, (0, 0))
|
||||
psp = []
|
||||
if self.ends_at == spine_index:
|
||||
# Anchors that could possibly indicate the start of the next
|
||||
# section and therefore the end of this section.
|
||||
# self.possible_end_anchors is a set of anchors belonging to
|
||||
# toc entries with depth <= self.depth that are also not
|
||||
# ancestors of this entry.
|
||||
psp = [anchor_map.get(x, (0, 0)) for x in self.possible_end_anchors]
|
||||
psp = [x for x in psp if x >= start_pos]
|
||||
# The end position. The first anchor whose pos is >= start_pos
|
||||
# or if the end is not in this spine item, we set it to the column
|
||||
# after the right edge of the viewport
|
||||
end_pos = min(psp) if psp else (right if self.ends_at >=
|
||||
spine_index else (0, 0))
|
||||
if spine_index > self.starts_at and spine_index < self.ends_at:
|
||||
# The entire spine item is contained in this entry
|
||||
is_being_viewed = True
|
||||
elif (spine_index == self.starts_at and right > start_pos and
|
||||
# This spine item contains the start
|
||||
# The start position is before the end of the viewport
|
||||
(spine_index != self.ends_at or left < end_pos)):
|
||||
# The end position is after the start of the viewport
|
||||
is_being_viewed = True
|
||||
elif (spine_index == self.ends_at and left < end_pos and
|
||||
# This spine item contains the end
|
||||
# The end position is after the start of the viewport
|
||||
(spine_index != self.starts_at or right > start_pos)):
|
||||
# The start position is before the end of the viewport
|
||||
is_being_viewed = True
|
||||
|
||||
changed = is_being_viewed != self.is_being_viewed
|
||||
self.is_being_viewed = is_being_viewed
|
||||
if changed:
|
||||
self.setFont(self.bold_font if is_being_viewed else self.normal_font)
|
||||
|
||||
def __repr__(self):
|
||||
return 'TOC Item: %s %s#%s'%(self.title, self.abspath, self.fragment)
|
||||
|
||||
@ -183,20 +241,26 @@ class TOC(QStandardItemModel):
|
||||
self.currently_viewed_entry = t
|
||||
return items_being_viewed
|
||||
|
||||
def next_entry(self, spine_pos, anchor_map, scroll_pos, backwards=False,
|
||||
current_entry=None):
|
||||
def next_entry(self, spine_pos, anchor_map, viewport_rect, in_paged_mode,
|
||||
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
|
||||
top = scroll_pos[0]
|
||||
|
||||
if in_paged_mode:
|
||||
start = viewport_rect[0]
|
||||
anchor_map = {k:v[0] for k, v in anchor_map.iteritems()}
|
||||
else:
|
||||
start = viewport_rect[1]
|
||||
anchor_map = {k:v[1] for k, v in anchor_map.iteritems()}
|
||||
|
||||
for item in items:
|
||||
if found:
|
||||
start_pos = anchor_map.get(item.start_anchor, 0)
|
||||
if backwards and item.is_being_viewed and start_pos >= top:
|
||||
# Going to this item will either not move the scroll
|
||||
# position or cause to to *increase* instead of descresing
|
||||
if backwards and item.is_being_viewed and start_pos >= start:
|
||||
# This item will not cause any scrolling
|
||||
continue
|
||||
if item.starts_at != spine_pos or item.start_anchor:
|
||||
return item
|
||||
|
Loading…
x
Reference in New Issue
Block a user