From 7b75c67e15a7f47bb36fcdfb65bcf4a0424fe89e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 28 Aug 2018 10:13:11 +0530 Subject: [PATCH] More work on the new viewer --- src/calibre/gui2/viewer2/main.py | 8 ++- src/calibre/gui2/viewer2/ui.py | 45 ++++++++++++++- src/calibre/gui2/viewer2/web_view.py | 84 ++++++++++++++++++++-------- 3 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/calibre/gui2/viewer2/main.py b/src/calibre/gui2/viewer2/main.py index 98117c6c18..dca9de93b0 100644 --- a/src/calibre/gui2/viewer2/main.py +++ b/src/calibre/gui2/viewer2/main.py @@ -96,14 +96,14 @@ def ensure_single_instance(args, open_at): def option_parser(): - from gui2.main_window import option_parser + from calibre.gui2.main_window import option_parser parser = option_parser(_('''\ %prog [options] file View an e-book. ''')) a = parser.add_option - a('--raise-window', default=False, action='strore_true', + a('--raise-window', default=False, action='store_true', help=_('If specified, viewer window will try to come to the ' 'front when started.')) a('--full-screen', '--fullscreen', '-f', default=False, action='store_true', @@ -168,6 +168,10 @@ def main(args=sys.argv): t.daemon = True t.start() QTimer.singleShot(0, acc.flush) + if opts.raise_window: + main.raise_() + if opts.full_screen: + main.showFullScreen() app.exec_() if listener is not None: diff --git a/src/calibre/gui2/viewer2/ui.py b/src/calibre/gui2/viewer2/ui.py index b995f65a01..c61178ddbd 100644 --- a/src/calibre/gui2/viewer2/ui.py +++ b/src/calibre/gui2/viewer2/ui.py @@ -5,18 +5,34 @@ from __future__ import absolute_import, division, print_function, unicode_literals import os +from threading import Thread -from PyQt5.Qt import pyqtSignal +from PyQt5.Qt import QDockWidget, Qt, pyqtSignal +from calibre.gui2 import error_dialog from calibre.gui2.main_window import MainWindow +from calibre.gui2.viewer2.convert_book import prepare_book +from calibre.gui2.viewer2.web_view import WebView, set_book_path +from calibre.utils.ipc.simple_worker import WorkerError class EbookViewer(MainWindow): msg_from_anotherinstance = pyqtSignal(object) + book_prepared = pyqtSignal(object, object) def __init__(self): - MainWindow.__init__(self) + MainWindow.__init__(self, None) + self.book_prepared.connect(self.load_finished, type=Qt.QueuedConnection) + self.web_view = WebView(self) + self.setCentralWidget(self.web_view) + + def create_dock(title, name, areas=Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea): + ans = QDockWidget(title, self) + ans.setObjectName(name) + ans.close() + + self.toc_dock = create_dock(_('Table of Contents'), 'toc-dock') def handle_commandline_arg(self, arg): if arg and os.path.isfile(arg) and os.access(arg, os.R_OK): @@ -29,3 +45,28 @@ class EbookViewer(MainWindow): return self.load_ebook(path, open_at=open_at) self.raise_() + + def load_ebook(self, pathtoebook, open_at=None): + # TODO: Implement open_at + t = Thread(name='LoadBook', target=self._load_ebook_worker, args=(pathtoebook, open_at)) + t.daemon = True + t.start() + + def _load_ebook_worker(self, pathtoebook, open_at): + try: + ans = prepare_book(pathtoebook) + except WorkerError as e: + self.book_prepared.emit(False, {'exception': e, 'tb': e.orig_tb, 'pathtoebook': pathtoebook}) + except Exception as e: + import traceback + self.book_prepared.emit(False, {'exception': e, 'tb': traceback.format_exc(), 'pathtoebook': pathtoebook}) + else: + self.book_prepared.emit(True, {'base': ans, 'pathtoebook': pathtoebook, 'open_at': open_at}) + + def load_finished(self, ok, data): + if not ok: + error_dialog(self, _('Loading book failed'), _( + 'Failed to open the book at {0}. Click "Show details" for more info.').format(data['pathtoebook']), + det_msg=data['tb'], show=True) + return + set_book_path(data['base']) diff --git a/src/calibre/gui2/viewer2/web_view.py b/src/calibre/gui2/viewer2/web_view.py index 7a519b292d..9d6fbe6e86 100644 --- a/src/calibre/gui2/viewer2/web_view.py +++ b/src/calibre/gui2/viewer2/web_view.py @@ -4,17 +4,20 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from PyQt5.Qt import QApplication, QBuffer, QByteArray +import os + +from PyQt5.Qt import QApplication, QBuffer, QByteArray, QSize from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler from PyQt5.QtWebEngineWidgets import ( QWebEnginePage, QWebEngineProfile, QWebEngineScript ) -from calibre import prints +from calibre import as_unicode, prints from calibre.constants import ( FAKE_HOST, FAKE_PROTOCOL, __version__, is_running_from_develop ) -from calibre.gui2 import open_url +from calibre.ebooks.oeb.polish.utils import guess_type +from calibre.gui2 import error_dialog, open_url from calibre.gui2.webengine import ( Bridge, RestartingWebEngineView, create_script, from_js, insert_scripts, secure_webengine, to_js @@ -28,8 +31,22 @@ except ImportError: # Override network access to load data from the book {{{ +def set_book_path(path=None): + set_book_path.path = os.path.abspath(path) + + def get_data(name): - raise NotImplementedError('TODO: implement this') + bdir = getattr(set_book_path, 'path', None) + if bdir is None: + return None, None + path = os.path.abspath(os.path.join(bdir, name)) + if not path.startswith(bdir): + return None, None + try: + with lopen(path, 'rb') as f: + return f.read(), guess_type(name) + except EnvironmentError as err: + prints('Failed to read from book file: {} with error: {}'.format(name, as_unicode(err))) class UrlSchemeHandler(QWebEngineUrlSchemeHandler): @@ -46,24 +63,26 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): rq.fail(rq.UrlNotFound) return name = url.path()[1:] - try: - data, mime_type = get_data(name) - if data is None: - rq.fail(rq.UrlNotFound) - return - if isinstance(data, type('')): - data = data.encode('utf-8') - mime_type = { - # Prevent warning in console about mimetype of fonts - 'application/vnd.ms-opentype':'application/x-font-ttf', - 'application/x-font-truetype':'application/x-font-ttf', - 'application/font-sfnt': 'application/x-font-ttf', - }.get(mime_type, mime_type) - self.send_reply(rq, mime_type, data) - except Exception: - import traceback - traceback.print_exc() - rq.fail(rq.RequestFailed) + if name.startswith('book/'): + name = name.partition('/')[2] + try: + data, mime_type = get_data(name) + if data is None: + rq.fail(rq.UrlNotFound) + return + if isinstance(data, type('')): + data = data.encode('utf-8') + mime_type = { + # Prevent warning in console about mimetype of fonts + 'application/vnd.ms-opentype':'application/x-font-ttf', + 'application/x-font-truetype':'application/x-font-ttf', + 'application/font-sfnt': 'application/x-font-ttf', + }.get(mime_type, mime_type) + self.send_reply(rq, mime_type, data) + except Exception: + import traceback + traceback.print_exc() + rq.fail(rq.RequestFailed) def send_reply(self, rq, mime_type, data): if sip.isdeleted(rq): @@ -151,3 +170,24 @@ class WebView(RestartingWebEngineView): def __init__(self, parent=None): RestartingWebEngineView.__init__(self, parent) + self.dead_renderer_error_shown = False + self.render_process_failed.connect(self.render_process_died) + w = QApplication.instance().desktop().availableGeometry(self).width() + self._size_hint = QSize(int(w/3), int(w/2)) + self._page = WebPage(self) + self.setPage(self._page) + self.setAcceptDrops(False) + + def render_process_died(self): + if self.dead_renderer_error_shown: + return + self.dead_renderer_error_shown = True + error_dialog(self, _('Render process crashed'), _( + 'The Qt WebEngine Render process has crashed.' + ' You should try restarting the viewer.') , show=True) + + def sizeHint(self): + return self._size_hint + + def refresh(self): + self.pageAction(QWebEnginePage.Reload).trigger()