mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
E-book viewer: Start work on paged display mode
This commit is contained in:
parent
99f410acf8
commit
4ba810a75f
Binary file not shown.
11
resources/viewer/blank.html
Normal file
11
resources/viewer/blank.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>blank</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div> </div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
203
src/calibre/ebooks/oeb/display/paged.coffee
Normal file
203
src/calibre/ebooks/oeb/display/paged.coffee
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env coffee
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
###
|
||||||
|
Copyright 2012, Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
Released under the GPLv3 License
|
||||||
|
###
|
||||||
|
|
||||||
|
log = (args...) -> # {{{
|
||||||
|
if args
|
||||||
|
msg = args.join(' ')
|
||||||
|
if window?.console?.log
|
||||||
|
window.console.log(msg)
|
||||||
|
else if process?.stdout?.write
|
||||||
|
process.stdout.write(msg + '\n')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
absleft = (elem) ->
|
||||||
|
r = elem.getBoundingClientRect()
|
||||||
|
return r.left + window.pageXOffset
|
||||||
|
|
||||||
|
class PagedDisplay
|
||||||
|
###
|
||||||
|
This class is a namespace to expose functions via the
|
||||||
|
window.paged_display object. The most important functions are:
|
||||||
|
|
||||||
|
layout(): causes the currently loaded document to be laid out in columns.
|
||||||
|
###
|
||||||
|
|
||||||
|
constructor: () ->
|
||||||
|
this.set_geometry()
|
||||||
|
this.page_width = 0
|
||||||
|
this.screen_width = 0
|
||||||
|
this.in_paged_mode = false
|
||||||
|
this.current_margin_side = 0
|
||||||
|
|
||||||
|
set_geometry: (cols_per_screen=2, margin_top=20, margin_side=40, margin_bottom=20) ->
|
||||||
|
this.margin_top = margin_top
|
||||||
|
this.margin_side = margin_side
|
||||||
|
this.margin_bottom = margin_bottom
|
||||||
|
this.cols_per_screen = cols_per_screen
|
||||||
|
|
||||||
|
layout: () ->
|
||||||
|
ww = window.innerWidth
|
||||||
|
wh = window.innerHeight
|
||||||
|
body_height = wh - this.margin_bottom = this.margin_top
|
||||||
|
n = this.cols_per_screen
|
||||||
|
|
||||||
|
# Calculate the column width so that cols_per_screen columns fit in the
|
||||||
|
# window in such a way the right margin of the last column is <=
|
||||||
|
# side_margin (it may be less if the window width is not a
|
||||||
|
# multiple of n*(col_width+2*side_margin).
|
||||||
|
|
||||||
|
adjust = ww - Math.floor(ww/n)*n
|
||||||
|
# Ensure that the margins are large enough that the adjustment does not
|
||||||
|
# cause them to become negative semidefinite
|
||||||
|
sm = Math.max(2*adjust, this.margin_side)
|
||||||
|
# Minimum column width, for the cases when the window is too
|
||||||
|
# narrow
|
||||||
|
col_width = Math.max(100, ((ww - adjust)/n) - 2*sm)
|
||||||
|
this.page_width = col_width + 2*sm
|
||||||
|
this.screen_width = this.page_width * this.cols_per_screen
|
||||||
|
|
||||||
|
body_style = window.getComputedStyle(document.body)
|
||||||
|
fgcolor = body_style.getPropertyValue('color')
|
||||||
|
bs = document.body.style
|
||||||
|
|
||||||
|
bs.setProperty('-webkit-column-gap', (2*sm)+'px')
|
||||||
|
bs.setProperty('-webkit-column-width', col_width+'px')
|
||||||
|
bs.setProperty('-webkit-column-rule-color', fgcolor)
|
||||||
|
bs.setProperty('overflow', 'visible')
|
||||||
|
bs.setProperty('height', 'auto')
|
||||||
|
bs.setProperty('width', 'auto')
|
||||||
|
bs.setProperty('margin-top', this.margin_top+'px')
|
||||||
|
bs.setProperty('margin-bottom', this.margin_bottom+'px')
|
||||||
|
bs.setProperty('margin-left', sm+'px')
|
||||||
|
bs.setProperty('margin-right', sm+'px')
|
||||||
|
for edge in ['left', 'right', 'top', 'bottom']
|
||||||
|
bs.setProperty('padding-'+edge, '0px')
|
||||||
|
bs.setProperty('border-'+edge+'-width', '0px')
|
||||||
|
bs.setProperty('min-width', '0')
|
||||||
|
bs.setProperty('max-width', 'none')
|
||||||
|
bs.setProperty('min-height', '0')
|
||||||
|
bs.setProperty('max-height', 'none')
|
||||||
|
|
||||||
|
# Ensure that the top margin is correct, otherwise for some documents,
|
||||||
|
# webkit lays out the body with a lot of space on top
|
||||||
|
brect = document.body.getBoundingClientRect()
|
||||||
|
if brect.top > this.margin_top
|
||||||
|
bs.setProperty('margin-top', (this.margin_top - brect.top)+'px')
|
||||||
|
brect = document.body.getBoundingClientRect()
|
||||||
|
this.in_paged_mode = true
|
||||||
|
this.current_margin_side = sm
|
||||||
|
return sm
|
||||||
|
|
||||||
|
scroll_to_pos: (frac) ->
|
||||||
|
# Scroll to the position represented by frac (number between 0 and 1)
|
||||||
|
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||||
|
this.scroll_to_xpos(xpos)
|
||||||
|
|
||||||
|
scroll_to_xpos: (xpos) ->
|
||||||
|
# Scroll so that the column containing xpos is the left most column in
|
||||||
|
# the viewport
|
||||||
|
pos = 0
|
||||||
|
until (pos <= xpos < pos + this.page_width)
|
||||||
|
pos += this.page_width
|
||||||
|
limit = document.body.scrollWidth - this.screen_width
|
||||||
|
pos = limit if pos > limit
|
||||||
|
window.scrollTo(pos, 0)
|
||||||
|
|
||||||
|
current_pos: (frac) ->
|
||||||
|
# The current scroll position as a fraction between 0 and 1
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if limit <= 0
|
||||||
|
return 0.0
|
||||||
|
return window.pageXOffset / limit
|
||||||
|
|
||||||
|
current_column_location: () ->
|
||||||
|
# The location of the left edge of the left most column currently
|
||||||
|
# visible in the viewport
|
||||||
|
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||||
|
edge = Math.floor(x/this.page_width)
|
||||||
|
while edge < x
|
||||||
|
edge += this.page_width
|
||||||
|
return edge - this.page_width
|
||||||
|
|
||||||
|
next_screen_location: () ->
|
||||||
|
# The position to scroll to for the next screen (which could contain
|
||||||
|
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||||
|
cc = this.current_column_location()
|
||||||
|
ans = cc + this.screen_width
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if ans > limit
|
||||||
|
ans = if window.pageXOffset < limit then limit else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
previous_screen_location: () ->
|
||||||
|
# The position to scroll to for the previous screen (which could contain
|
||||||
|
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||||
|
cc = this.current_column_location()
|
||||||
|
ans = cc - this.screen_width
|
||||||
|
if ans < 0
|
||||||
|
# We ignore small scrolls (less than 15px) when going to previous
|
||||||
|
# screen
|
||||||
|
ans = if window.pageXOffset > 15 then 0 else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
next_col_location: () ->
|
||||||
|
# The position to scroll to for the next column (same as
|
||||||
|
# next_screen_location() if columns per screen == 1). Returns -1 if no
|
||||||
|
# further scrolling is possible.
|
||||||
|
cc = this.current_column_location()
|
||||||
|
ans = cc + this.page_width
|
||||||
|
limit = document.body.scrollWidth - window.innerWidth
|
||||||
|
if ans > limit
|
||||||
|
ans = if window.pageXOffset < limit then limit else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
previous_col_location: () ->
|
||||||
|
# The position to scroll to for the previous column (same as
|
||||||
|
# previous_screen_location() if columns per screen == 1). Returns -1 if
|
||||||
|
# no further scrolling is possible.
|
||||||
|
cc = this.current_column_location()
|
||||||
|
ans = cc - this.page_width
|
||||||
|
if ans < 0
|
||||||
|
ans = if window.pageXOffset > 0 then 0 else -1
|
||||||
|
return ans
|
||||||
|
|
||||||
|
jump_to_anchor: (name) ->
|
||||||
|
elem = document.getElementById(name)
|
||||||
|
if !elem
|
||||||
|
elems = document.getElementsByName(name)
|
||||||
|
if elems
|
||||||
|
elem = elems[0]
|
||||||
|
if !elem
|
||||||
|
return
|
||||||
|
elem.scrollIntoView()
|
||||||
|
if this.in_paged_mode
|
||||||
|
# Ensure we are scrolled to the column containing elem
|
||||||
|
this.scroll_to_xpos(absleft(elem)+10)
|
||||||
|
|
||||||
|
if window?
|
||||||
|
window.paged_display = new PagedDisplay()
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# jump_to_anchor() and handling of internal links in the same flow and
|
||||||
|
# different flows
|
||||||
|
# css pagebreak rules
|
||||||
|
# CFI and bookmarks
|
||||||
|
# Go to reference positions
|
||||||
|
# Indexing
|
||||||
|
# Resizing of images
|
||||||
|
# Special handling for identifiable covers (colspan)?
|
||||||
|
# Full screen mode
|
||||||
|
# Resizing of viewport
|
@ -31,12 +31,15 @@ def self_closing_sub(match):
|
|||||||
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
|
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
|
||||||
|
|
||||||
def load_html(path, view, codec='utf-8', mime_type=None,
|
def load_html(path, view, codec='utf-8', mime_type=None,
|
||||||
pre_load_callback=lambda x:None):
|
pre_load_callback=lambda x:None, path_is_html=False):
|
||||||
from PyQt4.Qt import QUrl, QByteArray
|
from PyQt4.Qt import QUrl, QByteArray
|
||||||
if mime_type is None:
|
if mime_type is None:
|
||||||
mime_type = guess_type(path)[0]
|
mime_type = guess_type(path)[0]
|
||||||
with open(path, 'rb') as f:
|
if path_is_html:
|
||||||
html = f.read().decode(codec, 'replace')
|
html = path
|
||||||
|
else:
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
html = f.read().decode(codec, 'replace')
|
||||||
|
|
||||||
html = EntityDeclarationProcessor(html).processed_html
|
html = EntityDeclarationProcessor(html).processed_html
|
||||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||||
|
@ -22,7 +22,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
|
|||||||
from calibre.gui2.viewer.position import PagePosition
|
from calibre.gui2.viewer.position import PagePosition
|
||||||
from calibre.gui2.viewer.config import config, ConfigDialog
|
from calibre.gui2.viewer.config import config, ConfigDialog
|
||||||
from calibre.ebooks.oeb.display.webview import load_html
|
from calibre.ebooks.oeb.display.webview import load_html
|
||||||
|
from calibre.utils.config import tweaks
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def load_builtin_fonts():
|
def load_builtin_fonts():
|
||||||
@ -59,10 +59,12 @@ class Document(QWebPage): # {{{
|
|||||||
def __init__(self, shortcuts, parent=None, debug_javascript=False):
|
def __init__(self, shortcuts, parent=None, debug_javascript=False):
|
||||||
QWebPage.__init__(self, parent)
|
QWebPage.__init__(self, parent)
|
||||||
self.setObjectName("py_bridge")
|
self.setObjectName("py_bridge")
|
||||||
|
self.in_paged_mode = tweaks.get('viewer_test_paged_mode', False)
|
||||||
# Use this to pass arbitrary JSON encodable objects between python and
|
# Use this to pass arbitrary JSON encodable objects between python and
|
||||||
# javascript. In python get/set the value as: self.bridge_value. In
|
# javascript. In python get/set the value as: self.bridge_value. In
|
||||||
# javascript, get/set the value as: py_bridge.value
|
# javascript, get/set the value as: py_bridge.value
|
||||||
self.bridge_value = None
|
self.bridge_value = None
|
||||||
|
self.first_load = True
|
||||||
|
|
||||||
self.debug_javascript = debug_javascript
|
self.debug_javascript = debug_javascript
|
||||||
self.anchor_positions = {}
|
self.anchor_positions = {}
|
||||||
@ -104,6 +106,13 @@ class Document(QWebPage): # {{{
|
|||||||
self.mainFrame().javaScriptWindowObjectCleared.connect(
|
self.mainFrame().javaScriptWindowObjectCleared.connect(
|
||||||
self.add_window_objects)
|
self.add_window_objects)
|
||||||
|
|
||||||
|
self.turn_off_internal_scrollbars()
|
||||||
|
|
||||||
|
def turn_off_internal_scrollbars(self):
|
||||||
|
mf = self.mainFrame()
|
||||||
|
mf.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
|
||||||
|
mf.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
||||||
|
|
||||||
def set_user_stylesheet(self):
|
def set_user_stylesheet(self):
|
||||||
raw = config().parse().user_css
|
raw = config().parse().user_css
|
||||||
raw = '::selection {background:#ffff00; color:#000;}\nbody {background-color: white;}\n'+raw
|
raw = '::selection {background:#ffff00; color:#000;}\nbody {background-color: white;}\n'+raw
|
||||||
@ -175,9 +184,12 @@ class Document(QWebPage): # {{{
|
|||||||
'document.body.style.marginLeft').toString())
|
'document.body.style.marginLeft').toString())
|
||||||
self.initial_right_margin = unicode(self.javascript(
|
self.initial_right_margin = unicode(self.javascript(
|
||||||
'document.body.style.marginRight').toString())
|
'document.body.style.marginRight').toString())
|
||||||
|
if self.in_paged_mode:
|
||||||
|
self.switch_to_paged_mode()
|
||||||
if self.in_fullscreen_mode:
|
if self.in_fullscreen_mode:
|
||||||
self.switch_to_fullscreen_mode()
|
self.switch_to_fullscreen_mode()
|
||||||
self.read_anchor_positions(use_cache=False)
|
self.read_anchor_positions(use_cache=False)
|
||||||
|
self.first_load = False
|
||||||
|
|
||||||
def read_anchor_positions(self, use_cache=True):
|
def read_anchor_positions(self, use_cache=True):
|
||||||
self.bridge_value = tuple(self.index_anchors)
|
self.bridge_value = tuple(self.index_anchors)
|
||||||
@ -190,6 +202,22 @@ class Document(QWebPage): # {{{
|
|||||||
self.anchor_positions = {}
|
self.anchor_positions = {}
|
||||||
return self.anchor_positions
|
return self.anchor_positions
|
||||||
|
|
||||||
|
def switch_to_paged_mode(self, onresize=False):
|
||||||
|
side_margin = self.javascript('paged_display.layout()', typ=int)
|
||||||
|
# Setup the contents size to ensure that there is a right most margin.
|
||||||
|
# Without this webkit renders the final column with no margin, as the
|
||||||
|
# columns extend beyond the boundaries (and margin) of body
|
||||||
|
mf = self.mainFrame()
|
||||||
|
sz = mf.contentsSize()
|
||||||
|
if sz.width() > self.window_width:
|
||||||
|
sz.setWidth(sz.width()+side_margin)
|
||||||
|
self.setPreferredContentsSize(sz)
|
||||||
|
|
||||||
|
def after_resize(self):
|
||||||
|
if self.in_paged_mode:
|
||||||
|
self.setPreferredContentsSize(QSize())
|
||||||
|
self.switch_to_paged_mode(onresize=True)
|
||||||
|
|
||||||
def switch_to_fullscreen_mode(self):
|
def switch_to_fullscreen_mode(self):
|
||||||
self.in_fullscreen_mode = True
|
self.in_fullscreen_mode = True
|
||||||
self.javascript('''
|
self.javascript('''
|
||||||
@ -233,20 +261,21 @@ class Document(QWebPage): # {{{
|
|||||||
|
|
||||||
def javascript(self, string, typ=None):
|
def javascript(self, string, typ=None):
|
||||||
ans = self.mainFrame().evaluateJavaScript(string)
|
ans = self.mainFrame().evaluateJavaScript(string)
|
||||||
if typ == 'int':
|
if typ in {'int', int}:
|
||||||
ans = ans.toInt()
|
ans = ans.toInt()
|
||||||
if ans[1]:
|
if ans[1]:
|
||||||
return ans[0]
|
return ans[0]
|
||||||
return 0
|
return 0
|
||||||
|
if typ in {'float', float}:
|
||||||
|
ans = ans.toReal()
|
||||||
|
return ans[0] if ans[1] else 0.0
|
||||||
if typ == 'string':
|
if typ == 'string':
|
||||||
return unicode(ans.toString())
|
return unicode(ans.toString())
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def javaScriptConsoleMessage(self, msg, lineno, msgid):
|
def javaScriptConsoleMessage(self, msg, lineno, msgid):
|
||||||
if self.debug_javascript:
|
if self.debug_javascript:
|
||||||
prints( 'JS:', msgid, lineno)
|
|
||||||
prints(msg)
|
prints(msg)
|
||||||
prints(' ')
|
|
||||||
else:
|
else:
|
||||||
return QWebPage.javaScriptConsoleMessage(self, msg, lineno, msgid)
|
return QWebPage.javaScriptConsoleMessage(self, msg, lineno, msgid)
|
||||||
|
|
||||||
@ -263,13 +292,7 @@ class Document(QWebPage): # {{{
|
|||||||
self.mainFrame().setScrollPosition(QPoint(x, y))
|
self.mainFrame().setScrollPosition(QPoint(x, y))
|
||||||
|
|
||||||
def jump_to_anchor(self, anchor):
|
def jump_to_anchor(self, anchor):
|
||||||
self.javascript('document.location.hash = "%s"'%anchor)
|
self.javascript('paged_display.jump_to_anchor("%s")'%anchor)
|
||||||
|
|
||||||
def quantize(self):
|
|
||||||
if self.height > self.window_height:
|
|
||||||
r = self.height%self.window_height
|
|
||||||
if r > 0:
|
|
||||||
self.javascript('document.body.style.paddingBottom = "%dpx"'%r)
|
|
||||||
|
|
||||||
def element_ypos(self, elem):
|
def element_ypos(self, elem):
|
||||||
ans, ok = elem.evaluateJavaScript('$(this).offset().top').toInt()
|
ans, ok = elem.evaluateJavaScript('$(this).offset().top').toInt()
|
||||||
@ -314,15 +337,22 @@ class Document(QWebPage): # {{{
|
|||||||
@dynamic_property
|
@dynamic_property
|
||||||
def scroll_fraction(self):
|
def scroll_fraction(self):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
try:
|
if self.in_paged_mode:
|
||||||
return abs(float(self.ypos)/(self.height-self.window_height))
|
return self.javascript('paged_display.current_pos()',
|
||||||
except ZeroDivisionError:
|
typ='float')
|
||||||
return 0.
|
else:
|
||||||
|
try:
|
||||||
|
return abs(float(self.ypos)/(self.height-self.window_height))
|
||||||
|
except ZeroDivisionError:
|
||||||
|
return 0.
|
||||||
def fset(self, val):
|
def fset(self, val):
|
||||||
npos = val * (self.height - self.window_height)
|
if self.in_paged_mode:
|
||||||
if npos < 0:
|
self.javascript('paged_display.scroll_to_pos(%f)'%val)
|
||||||
npos = 0
|
else:
|
||||||
self.scroll_to(x=self.xpos, y=npos)
|
npos = val * (self.height - self.window_height)
|
||||||
|
if npos < 0:
|
||||||
|
npos = 0
|
||||||
|
self.scroll_to(x=self.xpos, y=npos)
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -363,6 +393,7 @@ class DocumentView(QWebView): # {{{
|
|||||||
DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern)
|
DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern)
|
||||||
|
|
||||||
def initialize_view(self, debug_javascript=False):
|
def initialize_view(self, debug_javascript=False):
|
||||||
|
self.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
|
||||||
self.flipper = SlideFlip(self)
|
self.flipper = SlideFlip(self)
|
||||||
self.is_auto_repeat_event = False
|
self.is_auto_repeat_event = False
|
||||||
self.debug_javascript = debug_javascript
|
self.debug_javascript = debug_javascript
|
||||||
@ -570,7 +601,7 @@ class DocumentView(QWebView): # {{{
|
|||||||
if self.manager is not None:
|
if self.manager is not None:
|
||||||
self.manager.load_started()
|
self.manager.load_started()
|
||||||
|
|
||||||
load_html(path, self, codec=path.encoding, mime_type=getattr(path,
|
load_html(path, self, codec=getattr(path, 'encoding', 'utf-8'), mime_type=getattr(path,
|
||||||
'mime_type', None), pre_load_callback=callback)
|
'mime_type', None), pre_load_callback=callback)
|
||||||
entries = set()
|
entries = set()
|
||||||
for ie in getattr(path, 'index_entries', []):
|
for ie in getattr(path, 'index_entries', []):
|
||||||
@ -579,10 +610,12 @@ class DocumentView(QWebView): # {{{
|
|||||||
if ie.end_anchor:
|
if ie.end_anchor:
|
||||||
entries.add(ie.end_anchor)
|
entries.add(ie.end_anchor)
|
||||||
self.document.index_anchors = entries
|
self.document.index_anchors = entries
|
||||||
self.turn_off_internal_scrollbars()
|
|
||||||
|
|
||||||
def initialize_scrollbar(self):
|
def initialize_scrollbar(self):
|
||||||
if getattr(self, 'scrollbar', None) is not None:
|
if getattr(self, 'scrollbar', None) is not None:
|
||||||
|
if self.document.in_paged_mode:
|
||||||
|
self.scrollbar.setVisible(False)
|
||||||
|
return
|
||||||
delta = self.document.width - self.size().width()
|
delta = self.document.width - self.size().width()
|
||||||
if delta > 0:
|
if delta > 0:
|
||||||
self._ignore_scrollbar_signals = True
|
self._ignore_scrollbar_signals = True
|
||||||
@ -623,7 +656,6 @@ class DocumentView(QWebView): # {{{
|
|||||||
self.manager.scrolled(self.document.scroll_fraction,
|
self.manager.scrolled(self.document.scroll_fraction,
|
||||||
onload=True)
|
onload=True)
|
||||||
|
|
||||||
self.turn_off_internal_scrollbars()
|
|
||||||
if self.flipper.isVisible():
|
if self.flipper.isVisible():
|
||||||
if self.flipper.running:
|
if self.flipper.running:
|
||||||
self.flipper.setVisible(False)
|
self.flipper.setVisible(False)
|
||||||
@ -631,12 +663,6 @@ class DocumentView(QWebView): # {{{
|
|||||||
self.flipper(self.current_page_image(),
|
self.flipper(self.current_page_image(),
|
||||||
duration=self.document.page_flip_duration)
|
duration=self.document.page_flip_duration)
|
||||||
|
|
||||||
|
|
||||||
def turn_off_internal_scrollbars(self):
|
|
||||||
self.document.mainFrame().setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)
|
|
||||||
self.document.mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def test_line(cls, img, y):
|
def test_line(cls, img, y):
|
||||||
'Test if line contains pixels of exactly the same color'
|
'Test if line contains pixels of exactly the same color'
|
||||||
@ -651,6 +677,7 @@ class DocumentView(QWebView): # {{{
|
|||||||
overlap = self.height()
|
overlap = self.height()
|
||||||
img = QImage(self.width(), overlap, QImage.Format_ARGB32_Premultiplied)
|
img = QImage(self.width(), overlap, QImage.Format_ARGB32_Premultiplied)
|
||||||
painter = QPainter(img)
|
painter = QPainter(img)
|
||||||
|
painter.setRenderHints(self.renderHints())
|
||||||
self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap))
|
self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap))
|
||||||
painter.end()
|
painter.end()
|
||||||
return img
|
return img
|
||||||
@ -670,6 +697,28 @@ class DocumentView(QWebView): # {{{
|
|||||||
return
|
return
|
||||||
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
||||||
|
|
||||||
|
if self.document.in_paged_mode:
|
||||||
|
loc = self.document.javascript(
|
||||||
|
'paged_display.previous_screen_location()', typ='int')
|
||||||
|
if loc < 0:
|
||||||
|
if self.manager is not None:
|
||||||
|
if epf:
|
||||||
|
self.flipper.initialize(self.current_page_image(),
|
||||||
|
forwards=False)
|
||||||
|
self.manager.previous_document()
|
||||||
|
else:
|
||||||
|
if epf:
|
||||||
|
self.flipper.initialize(self.current_page_image(),
|
||||||
|
forwards=False)
|
||||||
|
self.document.scroll_to(x=loc, y=0)
|
||||||
|
if epf:
|
||||||
|
self.flipper(self.current_page_image(),
|
||||||
|
duration=self.document.page_flip_duration)
|
||||||
|
if self.manager is not None:
|
||||||
|
self.manager.scrolled(self.scroll_fraction)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
delta_y = self.document.window_height - 25
|
delta_y = self.document.window_height - 25
|
||||||
if self.document.at_top:
|
if self.document.at_top:
|
||||||
if self.manager is not None:
|
if self.manager is not None:
|
||||||
@ -700,6 +749,26 @@ class DocumentView(QWebView): # {{{
|
|||||||
return
|
return
|
||||||
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
epf = self.document.enable_page_flip and not self.is_auto_repeat_event
|
||||||
|
|
||||||
|
if self.document.in_paged_mode:
|
||||||
|
loc = self.document.javascript(
|
||||||
|
'paged_display.next_screen_location()', typ='int')
|
||||||
|
if loc < 0:
|
||||||
|
if self.manager is not None:
|
||||||
|
if epf:
|
||||||
|
self.flipper.initialize(self.current_page_image())
|
||||||
|
self.manager.next_document()
|
||||||
|
else:
|
||||||
|
if epf:
|
||||||
|
self.flipper.initialize(self.current_page_image())
|
||||||
|
self.document.scroll_to(x=loc, y=0)
|
||||||
|
if epf:
|
||||||
|
self.flipper(self.current_page_image(),
|
||||||
|
duration=self.document.page_flip_duration)
|
||||||
|
if self.manager is not None:
|
||||||
|
self.manager.scrolled(self.scroll_fraction)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
window_height = self.document.window_height
|
window_height = self.document.window_height
|
||||||
document_height = self.document.height
|
document_height = self.document.height
|
||||||
ddelta = document_height - window_height
|
ddelta = document_height - window_height
|
||||||
@ -762,25 +831,38 @@ class DocumentView(QWebView): # {{{
|
|||||||
#print 'After all:', self.document.ypos
|
#print 'After all:', self.document.ypos
|
||||||
|
|
||||||
def scroll_by(self, x=0, y=0, notify=True):
|
def scroll_by(self, x=0, y=0, notify=True):
|
||||||
old_pos = self.document.ypos
|
old_pos = (self.document.xpos if self.document.in_paged_mode else
|
||||||
|
self.document.ypos)
|
||||||
self.document.scroll_by(x, y)
|
self.document.scroll_by(x, y)
|
||||||
if notify and self.manager is not None and self.document.ypos != old_pos:
|
new_pos = (self.document.xpos if self.document.in_paged_mode else
|
||||||
|
self.document.ypos)
|
||||||
|
if notify and self.manager is not None and new_pos != old_pos:
|
||||||
self.manager.scrolled(self.scroll_fraction)
|
self.manager.scrolled(self.scroll_fraction)
|
||||||
|
|
||||||
def scroll_to(self, pos, notify=True):
|
def scroll_to(self, pos, notify=True):
|
||||||
if self._ignore_scrollbar_signals:
|
if self._ignore_scrollbar_signals:
|
||||||
return
|
return
|
||||||
old_pos = self.document.ypos
|
old_pos = (self.document.xpos if self.document.in_paged_mode else
|
||||||
if isinstance(pos, basestring):
|
self.document.ypos)
|
||||||
self.document.jump_to_anchor(pos)
|
if self.document.in_paged_mode:
|
||||||
else:
|
if isinstance(pos, basestring):
|
||||||
if pos >= 1:
|
self.document.jump_to_anchor(pos)
|
||||||
self.document.scroll_to(0, self.document.height)
|
|
||||||
else:
|
else:
|
||||||
y = int(math.ceil(
|
self.document.scroll_fraction = pos
|
||||||
pos*(self.document.height-self.document.window_height)))
|
else:
|
||||||
self.document.scroll_to(0, y)
|
if isinstance(pos, basestring):
|
||||||
if notify and self.manager is not None and self.document.ypos != old_pos:
|
self.document.jump_to_anchor(pos)
|
||||||
|
else:
|
||||||
|
if pos >= 1:
|
||||||
|
self.document.scroll_to(0, self.document.height)
|
||||||
|
else:
|
||||||
|
y = int(math.ceil(
|
||||||
|
pos*(self.document.height-self.document.window_height)))
|
||||||
|
self.document.scroll_to(0, y)
|
||||||
|
|
||||||
|
new_pos = (self.document.xpos if self.document.in_paged_mode else
|
||||||
|
self.document.ypos)
|
||||||
|
if notify and self.manager is not None and new_pos != old_pos:
|
||||||
self.manager.scrolled(self.scroll_fraction)
|
self.manager.scrolled(self.scroll_fraction)
|
||||||
|
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
@ -813,9 +895,8 @@ class DocumentView(QWebView): # {{{
|
|||||||
return QWebView.changeEvent(self, event)
|
return QWebView.changeEvent(self, event)
|
||||||
|
|
||||||
def paintEvent(self, event):
|
def paintEvent(self, event):
|
||||||
self.turn_off_internal_scrollbars()
|
|
||||||
|
|
||||||
painter = QPainter(self)
|
painter = QPainter(self)
|
||||||
|
painter.setRenderHints(self.renderHints())
|
||||||
self.document.mainFrame().render(painter, event.region())
|
self.document.mainFrame().render(painter, event.region())
|
||||||
if not self.isEnabled():
|
if not self.isEnabled():
|
||||||
painter.fillRect(event.region().boundingRect(), self.DISABLED_BRUSH)
|
painter.fillRect(event.region().boundingRect(), self.DISABLED_BRUSH)
|
||||||
@ -827,6 +908,18 @@ class DocumentView(QWebView): # {{{
|
|||||||
if self.manager is not None and event.delta() != 0:
|
if self.manager is not None and event.delta() != 0:
|
||||||
(self.manager.font_size_larger if event.delta() > 0 else
|
(self.manager.font_size_larger if event.delta() > 0 else
|
||||||
self.manager.font_size_smaller)()
|
self.manager.font_size_smaller)()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.document.in_paged_mode:
|
||||||
|
if abs(event.delta()) < 15: return
|
||||||
|
typ = 'screen' if self.document.wheel_flips_pages else 'col'
|
||||||
|
direction = 'next' if event.delta() < 0 else 'previous'
|
||||||
|
loc = self.document.javascript('paged_display.%s_%s_location()'%(
|
||||||
|
direction, typ), typ='int')
|
||||||
|
if loc > -1:
|
||||||
|
self.document.scroll_to(x=loc, y=0)
|
||||||
|
return
|
||||||
|
|
||||||
if event.delta() < -14:
|
if event.delta() < -14:
|
||||||
if self.document.wheel_flips_pages:
|
if self.document.wheel_flips_pages:
|
||||||
self.next_page()
|
self.next_page()
|
||||||
@ -866,6 +959,17 @@ class DocumentView(QWebView): # {{{
|
|||||||
if not self.handle_key_press(event):
|
if not self.handle_key_press(event):
|
||||||
return QWebView.keyPressEvent(self, event)
|
return QWebView.keyPressEvent(self, event)
|
||||||
|
|
||||||
|
def paged_col_scroll(self, forward=True):
|
||||||
|
dir = 'next' if forward else 'previous'
|
||||||
|
loc = self.document.javascript(
|
||||||
|
'paged_display.%s_col_location()'%dir, typ='int')
|
||||||
|
if loc > -1:
|
||||||
|
self.document.scroll_to(x=loc, y=0)
|
||||||
|
self.manager.scrolled(self.document.scroll_fraction)
|
||||||
|
else:
|
||||||
|
(self.manager.next_document() if forward else
|
||||||
|
self.manager.previous_document())
|
||||||
|
|
||||||
def handle_key_press(self, event):
|
def handle_key_press(self, event):
|
||||||
handled = True
|
handled = True
|
||||||
key = self.shortcuts.get_match(event)
|
key = self.shortcuts.get_match(event)
|
||||||
@ -877,21 +981,33 @@ class DocumentView(QWebView): # {{{
|
|||||||
finally:
|
finally:
|
||||||
self.is_auto_repeat_event = False
|
self.is_auto_repeat_event = False
|
||||||
elif key == 'Down':
|
elif key == 'Down':
|
||||||
if (not self.document.line_scrolling_stops_on_pagebreaks and
|
if self.document.in_paged_mode:
|
||||||
self.document.at_bottom):
|
self.paged_col_scroll()
|
||||||
self.manager.next_document()
|
|
||||||
else:
|
else:
|
||||||
self.scroll_by(y=15)
|
if (not self.document.line_scrolling_stops_on_pagebreaks and
|
||||||
|
self.document.at_bottom):
|
||||||
|
self.manager.next_document()
|
||||||
|
else:
|
||||||
|
self.scroll_by(y=15)
|
||||||
elif key == 'Up':
|
elif key == 'Up':
|
||||||
if (not self.document.line_scrolling_stops_on_pagebreaks and
|
if self.document.in_paged_mode:
|
||||||
self.document.at_top):
|
self.paged_col_scroll(forward=False)
|
||||||
self.manager.previous_document()
|
|
||||||
else:
|
else:
|
||||||
self.scroll_by(y=-15)
|
if (not self.document.line_scrolling_stops_on_pagebreaks and
|
||||||
|
self.document.at_top):
|
||||||
|
self.manager.previous_document()
|
||||||
|
else:
|
||||||
|
self.scroll_by(y=-15)
|
||||||
elif key == 'Left':
|
elif key == 'Left':
|
||||||
self.scroll_by(x=-15)
|
if self.document.in_paged_mode:
|
||||||
|
self.paged_col_scroll(forward=False)
|
||||||
|
else:
|
||||||
|
self.scroll_by(x=-15)
|
||||||
elif key == 'Right':
|
elif key == 'Right':
|
||||||
self.scroll_by(x=15)
|
if self.document.in_paged_mode:
|
||||||
|
self.paged_col_scroll()
|
||||||
|
else:
|
||||||
|
self.scroll_by(x=15)
|
||||||
else:
|
else:
|
||||||
handled = False
|
handled = False
|
||||||
return handled
|
return handled
|
||||||
|
@ -30,10 +30,11 @@ class JavaScriptLoader(object):
|
|||||||
CS = {
|
CS = {
|
||||||
'cfi':'ebooks.oeb.display.cfi',
|
'cfi':'ebooks.oeb.display.cfi',
|
||||||
'indexing':'ebooks.oeb.display.indexing',
|
'indexing':'ebooks.oeb.display.indexing',
|
||||||
|
'paged':'ebooks.oeb.display.paged',
|
||||||
}
|
}
|
||||||
|
|
||||||
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
|
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
|
||||||
'hyphenation', 'hyphenator', 'cfi', 'indexing',)
|
'hyphenation', 'hyphenator', 'cfi', 'indexing', 'paged')
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, dynamic_coffeescript=False):
|
def __init__(self, dynamic_coffeescript=False):
|
||||||
|
@ -747,6 +747,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
# There hasn't been a resize event for some time
|
# There hasn't been a resize event for some time
|
||||||
# restore the current page position.
|
# restore the current page position.
|
||||||
self.resize_in_progress = False
|
self.resize_in_progress = False
|
||||||
|
self.view.document.after_resize()
|
||||||
if self.window_mode_changed:
|
if self.window_mode_changed:
|
||||||
# This resize is part of a window mode change, special case it
|
# This resize is part of a window mode change, special case it
|
||||||
self.handle_window_mode_toggle()
|
self.handle_window_mode_toggle()
|
||||||
@ -1003,6 +1004,12 @@ def main(args=sys.argv):
|
|||||||
QApplication.setApplicationName(APP_UID)
|
QApplication.setApplicationName(APP_UID)
|
||||||
main = EbookViewer(args[1] if len(args) > 1 else None,
|
main = EbookViewer(args[1] if len(args) > 1 else None,
|
||||||
debug_javascript=opts.debug_javascript, open_at=open_at)
|
debug_javascript=opts.debug_javascript, open_at=open_at)
|
||||||
|
# This is needed for paged mode. Without it, the first document that is
|
||||||
|
# loaded will have extra blank space at the bottom, as
|
||||||
|
# turn_off_internal_scrollbars does not take effect for the first
|
||||||
|
# rendered document
|
||||||
|
main.view.load_path(P('viewer/blank.html', allow_user_override=False))
|
||||||
|
|
||||||
sys.excepthook = main.unhandled_exception
|
sys.excepthook = main.unhandled_exception
|
||||||
main.show()
|
main.show()
|
||||||
if opts.raise_window:
|
if opts.raise_window:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user