From 4ba810a75fb268e3a8206f79ea43acc44eacec81 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Jun 2012 12:37:36 +0530 Subject: [PATCH] E-book viewer: Start work on paged display mode --- resources/compiled_coffeescript.zip | Bin 29763 -> 35081 bytes resources/viewer/blank.html | 11 + src/calibre/ebooks/oeb/display/paged.coffee | 203 ++++++++++++++++++ src/calibre/ebooks/oeb/display/webview.py | 9 +- src/calibre/gui2/viewer/documentview.py | 220 +++++++++++++++----- src/calibre/gui2/viewer/javascript.py | 3 +- src/calibre/gui2/viewer/main.py | 7 + 7 files changed, 397 insertions(+), 56 deletions(-) create mode 100644 resources/viewer/blank.html create mode 100644 src/calibre/ebooks/oeb/display/paged.coffee diff --git a/resources/compiled_coffeescript.zip b/resources/compiled_coffeescript.zip index b1c550c8f0600a96bbd07d8085ad8987e620952f..c6744d5df742606e2996ba9792904415ff3592ff 100644 GIT binary patch delta 4907 zcmcgw+iw(A7>CkUzyzdFN-35DA~Q>Omn{)d+C`$G#tW^HAllS%cK7V=l(}qXX1m(} z*^v044?e*056~wce39_#gNcTJz&9f?zWJt!#y2nDcg~raGt=D`BXN^%cjkQG@4H`S zzWI6UN4JNj7GK*oyl)GBmf!#G{BP6uv~Q=jY{9=Nc0HKB_3Q^v+q53$NyV$J8BJ<6 zns8xzjr>jmAHxW2t4c|kEG-xL&$*p&0Oz;vALyBWBghjcXyl3E)4KSco#UK%25hrr zzD(eE+o5h46m9A@!X|kx0xnif+t%arJSpYKS&$_#tUAH7AV;ooOoFli6z>&RyzQ2& zYedV|Cvd0_w((gt;1*=`+mov-M&Edft%XYVau;iAN6V2_FdwG zR`rm~kdEcnyiT!YHt3awdOe^ayEF7V`(j(QmuL_+t)K`;MuU0|4gEDeC!h)8N62k- ztQuHZCZ*I-Q1vNw2OL_ifvh#d@oMPrx@iYAVn4*8t8L#0FB%Tm59EQx2}|s+kz)(0 zM#$ZEtD)t&deyT7qeXo~2#071=Fn@!zY!?DsCZ%MIqbsll}8g&!rpvr?@8slOisnN z-H@@tXsN0p&nfjrnLM=-laS0sjY&3n3#G9mh4EfWgXAJ|6ZcYDqH@)bDi$E=#C1d| z6GS*49LatfIkt6tS4H2DnYSchmyYZ~ zfBJU-e%q#~iRS}~bbw-7TDZ4LeMqPdkZP+|U{!3I*0iVEDaH=cn(fd_6RLEP4V#dO zS}s8T1EiXxa3?alH^}FbSyWVpUzXV>ev=3|RDd6aoahxky^>BrjS)%Qn~8*YEB-Z?rsYV)1%W_swcz`l{)Z zE>J|M_#3E|@pAj{zJ<5>a&&@l-iTBkwb0Bx+crYaKmZ-TWTP3RSC3PwigBiRZA8Uv z%ducOE9wdD;+3S+v}{WBBf5?Uu(opbj~^HR=aCcQlNl|!9GZU`D+@oIMvmHKenmz` z;pD22GW&aa{AJ;dg1}YBSj2>hhp^{_1&OzXw6eI(etc%4Cx@8u-z5*(aHAGT{d`w! z`CjOSYb{!gG6Y3!7+|h^q@1)wOf@!gc=v_K9(9@F_aYXwN3em5Vcowbrz0UU2TH{f zT60+nv{Q(UU!_%<6s)-vhTc!tlqS19DmAYVYA89km)-wyM%b-GvhoJ1a zO1gJJ<7~o!&@((&WWI5(dD2xq!SBti%OiBl0zZ@al1;HqT;kGhC?5n;{;*9Kch)LA z_+gPmZmCwmFDa`tzDyo{003x#K^92AM3%XNS#Ld1mLO=KtQWHS#A{xt4^Nju)LBxP zMVGPbV|z}jtxQS#mQPnLuO0MrOHaDxzxZR5!bV%>(^Zg|cPRZZ3ci><4aSw=2{|!L zA7B`dH7EU~gcqqH3Lz8ZTvkzZ+z>_F^!)#`I9(%IKX`;kNJ&)jN z!UY>7QUh%K1O(4F7vLVg$9aD3BFNBFAcP?s( zI43E;q$EEV%AL&19>IlBI(Z|z2p@v0V5?v_c_OOPGbAhVrQA9xM z46L~nz@QdNmt!H|(;K0;^)u394#6wYqYN41dFwl_v3ROD!Bd6%(-5ipW7qZDt z{=}icR-U1wP+mScgHw&ID7B=tC~tBvr`+VWHvY}&(tC3FP~&5=VMipBIs4?E4lgDx b;mJH2(qaMLtiY&ZU| + + + blank + + + +
 
+ + + diff --git a/src/calibre/ebooks/oeb/display/paged.coffee b/src/calibre/ebooks/oeb/display/paged.coffee new file mode 100644 index 0000000000..ef365541ff --- /dev/null +++ b/src/calibre/ebooks/oeb/display/paged.coffee @@ -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 + 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 diff --git a/src/calibre/ebooks/oeb/display/webview.py b/src/calibre/ebooks/oeb/display/webview.py index efcfe0346c..8b89f54176 100644 --- a/src/calibre/ebooks/oeb/display/webview.py +++ b/src/calibre/ebooks/oeb/display/webview.py @@ -31,12 +31,15 @@ def self_closing_sub(match): return '<%s %s>'%(match.group(1), match.group(2), match.group(1)) 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 if mime_type is None: mime_type = guess_type(path)[0] - with open(path, 'rb') as f: - html = f.read().decode(codec, 'replace') + if path_is_html: + html = path + else: + with open(path, 'rb') as f: + html = f.read().decode(codec, 'replace') html = EntityDeclarationProcessor(html).processed_html has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index 84204027c9..8d1016d1dc 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -22,7 +22,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader from calibre.gui2.viewer.position import PagePosition from calibre.gui2.viewer.config import config, ConfigDialog from calibre.ebooks.oeb.display.webview import load_html - +from calibre.utils.config import tweaks # }}} def load_builtin_fonts(): @@ -59,10 +59,12 @@ class Document(QWebPage): # {{{ def __init__(self, shortcuts, parent=None, debug_javascript=False): QWebPage.__init__(self, parent) 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 # javascript. In python get/set the value as: self.bridge_value. In # javascript, get/set the value as: py_bridge.value self.bridge_value = None + self.first_load = True self.debug_javascript = debug_javascript self.anchor_positions = {} @@ -104,6 +106,13 @@ class Document(QWebPage): # {{{ self.mainFrame().javaScriptWindowObjectCleared.connect( 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): raw = config().parse().user_css raw = '::selection {background:#ffff00; color:#000;}\nbody {background-color: white;}\n'+raw @@ -175,9 +184,12 @@ class Document(QWebPage): # {{{ 'document.body.style.marginLeft').toString()) self.initial_right_margin = unicode(self.javascript( 'document.body.style.marginRight').toString()) + if self.in_paged_mode: + self.switch_to_paged_mode() if self.in_fullscreen_mode: self.switch_to_fullscreen_mode() self.read_anchor_positions(use_cache=False) + self.first_load = False def read_anchor_positions(self, use_cache=True): self.bridge_value = tuple(self.index_anchors) @@ -190,6 +202,22 @@ class Document(QWebPage): # {{{ 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): self.in_fullscreen_mode = True self.javascript(''' @@ -233,20 +261,21 @@ class Document(QWebPage): # {{{ def javascript(self, string, typ=None): ans = self.mainFrame().evaluateJavaScript(string) - if typ == 'int': + if typ in {'int', int}: ans = ans.toInt() if ans[1]: return ans[0] return 0 + if typ in {'float', float}: + ans = ans.toReal() + return ans[0] if ans[1] else 0.0 if typ == 'string': return unicode(ans.toString()) return ans def javaScriptConsoleMessage(self, msg, lineno, msgid): if self.debug_javascript: - prints( 'JS:', msgid, lineno) prints(msg) - prints(' ') else: return QWebPage.javaScriptConsoleMessage(self, msg, lineno, msgid) @@ -263,13 +292,7 @@ class Document(QWebPage): # {{{ self.mainFrame().setScrollPosition(QPoint(x, y)) def jump_to_anchor(self, anchor): - self.javascript('document.location.hash = "%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) + self.javascript('paged_display.jump_to_anchor("%s")'%anchor) def element_ypos(self, elem): ans, ok = elem.evaluateJavaScript('$(this).offset().top').toInt() @@ -314,15 +337,22 @@ class Document(QWebPage): # {{{ @dynamic_property def scroll_fraction(self): def fget(self): - try: - return abs(float(self.ypos)/(self.height-self.window_height)) - except ZeroDivisionError: - return 0. + if self.in_paged_mode: + return self.javascript('paged_display.current_pos()', + typ='float') + else: + try: + return abs(float(self.ypos)/(self.height-self.window_height)) + except ZeroDivisionError: + return 0. def fset(self, val): - npos = val * (self.height - self.window_height) - if npos < 0: - npos = 0 - self.scroll_to(x=self.xpos, y=npos) + if self.in_paged_mode: + self.javascript('paged_display.scroll_to_pos(%f)'%val) + else: + 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) @property @@ -363,6 +393,7 @@ class DocumentView(QWebView): # {{{ DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern) def initialize_view(self, debug_javascript=False): + self.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) self.flipper = SlideFlip(self) self.is_auto_repeat_event = False self.debug_javascript = debug_javascript @@ -570,7 +601,7 @@ class DocumentView(QWebView): # {{{ if self.manager is not None: 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) entries = set() for ie in getattr(path, 'index_entries', []): @@ -579,10 +610,12 @@ class DocumentView(QWebView): # {{{ if ie.end_anchor: entries.add(ie.end_anchor) self.document.index_anchors = entries - self.turn_off_internal_scrollbars() def initialize_scrollbar(self): 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() if delta > 0: self._ignore_scrollbar_signals = True @@ -623,7 +656,6 @@ class DocumentView(QWebView): # {{{ self.manager.scrolled(self.document.scroll_fraction, onload=True) - self.turn_off_internal_scrollbars() if self.flipper.isVisible(): if self.flipper.running: self.flipper.setVisible(False) @@ -631,12 +663,6 @@ class DocumentView(QWebView): # {{{ self.flipper(self.current_page_image(), 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 def test_line(cls, img, y): 'Test if line contains pixels of exactly the same color' @@ -651,6 +677,7 @@ class DocumentView(QWebView): # {{{ overlap = self.height() img = QImage(self.width(), overlap, QImage.Format_ARGB32_Premultiplied) painter = QPainter(img) + painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, QRegion(0, 0, self.width(), overlap)) painter.end() return img @@ -670,6 +697,28 @@ class DocumentView(QWebView): # {{{ return 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 if self.document.at_top: if self.manager is not None: @@ -700,6 +749,26 @@ class DocumentView(QWebView): # {{{ return 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 document_height = self.document.height ddelta = document_height - window_height @@ -762,25 +831,38 @@ class DocumentView(QWebView): # {{{ #print 'After all:', self.document.ypos 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) - 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) def scroll_to(self, pos, notify=True): if self._ignore_scrollbar_signals: return - old_pos = self.document.ypos - if isinstance(pos, basestring): - self.document.jump_to_anchor(pos) - else: - if pos >= 1: - self.document.scroll_to(0, self.document.height) + old_pos = (self.document.xpos if self.document.in_paged_mode else + self.document.ypos) + if self.document.in_paged_mode: + if isinstance(pos, basestring): + self.document.jump_to_anchor(pos) else: - y = int(math.ceil( - pos*(self.document.height-self.document.window_height))) - self.document.scroll_to(0, y) - if notify and self.manager is not None and self.document.ypos != old_pos: + self.document.scroll_fraction = pos + else: + if isinstance(pos, basestring): + 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) @dynamic_property @@ -813,9 +895,8 @@ class DocumentView(QWebView): # {{{ return QWebView.changeEvent(self, event) def paintEvent(self, event): - self.turn_off_internal_scrollbars() - painter = QPainter(self) + painter.setRenderHints(self.renderHints()) self.document.mainFrame().render(painter, event.region()) if not self.isEnabled(): 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: (self.manager.font_size_larger if event.delta() > 0 else 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 self.document.wheel_flips_pages: self.next_page() @@ -866,6 +959,17 @@ class DocumentView(QWebView): # {{{ if not self.handle_key_press(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): handled = True key = self.shortcuts.get_match(event) @@ -877,21 +981,33 @@ class DocumentView(QWebView): # {{{ finally: self.is_auto_repeat_event = False elif key == 'Down': - if (not self.document.line_scrolling_stops_on_pagebreaks and - self.document.at_bottom): - self.manager.next_document() + if self.document.in_paged_mode: + self.paged_col_scroll() 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': - if (not self.document.line_scrolling_stops_on_pagebreaks and - self.document.at_top): - self.manager.previous_document() + if self.document.in_paged_mode: + self.paged_col_scroll(forward=False) 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': - 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': - self.scroll_by(x=15) + if self.document.in_paged_mode: + self.paged_col_scroll() + else: + self.scroll_by(x=15) else: handled = False return handled diff --git a/src/calibre/gui2/viewer/javascript.py b/src/calibre/gui2/viewer/javascript.py index c4814cc04e..1594a1d5db 100644 --- a/src/calibre/gui2/viewer/javascript.py +++ b/src/calibre/gui2/viewer/javascript.py @@ -30,10 +30,11 @@ class JavaScriptLoader(object): CS = { 'cfi':'ebooks.oeb.display.cfi', 'indexing':'ebooks.oeb.display.indexing', + 'paged':'ebooks.oeb.display.paged', } ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images', - 'hyphenation', 'hyphenator', 'cfi', 'indexing',) + 'hyphenation', 'hyphenator', 'cfi', 'indexing', 'paged') def __init__(self, dynamic_coffeescript=False): diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index ee0d3bd361..d2886d6337 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -747,6 +747,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): # There hasn't been a resize event for some time # restore the current page position. self.resize_in_progress = False + self.view.document.after_resize() if self.window_mode_changed: # This resize is part of a window mode change, special case it self.handle_window_mode_toggle() @@ -1003,6 +1004,12 @@ def main(args=sys.argv): QApplication.setApplicationName(APP_UID) main = EbookViewer(args[1] if len(args) > 1 else None, 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 main.show() if opts.raise_window: