From 59b6c17ac24dede439b58577cdf903783e5430d6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Aug 2019 11:09:50 +0530 Subject: [PATCH] Implement --open-at --- src/calibre/gui2/viewer/main.py | 14 +++++--------- src/calibre/gui2/viewer/toc.py | 4 ++++ src/calibre/gui2/viewer/ui.py | 16 +++++++++++++-- src/calibre/gui2/viewer/web_view.py | 4 ++-- src/pyj/read_book/view.pyj | 30 ++++++++++++++++++++--------- src/pyj/viewer-main.pyj | 8 ++++---- 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index ff902abfe6..6f2624350f 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -108,7 +108,7 @@ View an e-book. 'full screen when started.')) a('--open-at', default=None, help=_( 'The position at which to open the specified book. The position is ' - 'a location as displayed in the top left corner of the viewer. ' + 'a location you can get by using the Goto action in the viewer controls. ' 'Alternately, you can use the form toc:something and it will open ' 'at the location of the first Table of Contents entry that contains ' 'the string "something".')) @@ -133,17 +133,13 @@ def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) - open_at = None - if opts.open_at is not None: - if opts.open_at.startswith('toc:'): - open_at = opts.open_at - else: - open_at = float(opts.open_at.replace(',', '.')) + if opts.open_at and not (opts.open_at.startswith('toc:') or opts.open_at.startswith('epubcfi(/')): + raise SystemExit('Not a valid --open-at value: {}'.format(opts.open_at)) listener = None if vprefs['singleinstance']: try: - listener = ensure_single_instance(args, open_at) + listener = ensure_single_instance(args, opts.open_at) except Exception as e: import traceback error_dialog(None, _('Failed to start viewer'), as_unicode(e), det_msg=traceback.format_exc(), show=True) @@ -153,7 +149,7 @@ def main(args=sys.argv): app.file_event_hook = acc app.load_builtin_fonts() app.setWindowIcon(QIcon(I('viewer.png'))) - main = EbookViewer(open_at=open_at, continue_reading=opts.continue_reading) + main = EbookViewer(open_at=opts.open_at, continue_reading=opts.continue_reading) main.set_exception_handler() if len(args) > 1: acc.events.append(args[-1]) diff --git a/src/calibre/gui2/viewer/toc.py b/src/calibre/gui2/viewer/toc.py index e786f0b9c7..32110ede1f 100644 --- a/src/calibre/gui2/viewer/toc.py +++ b/src/calibre/gui2/viewer/toc.py @@ -205,6 +205,10 @@ class TOC(QStandardItemModel): if primary_contains(query, item.text()): yield item + def node_id_for_text(self, query): + for item in self.find_items(query): + return item.node_id + def search(self, query): cq = self.current_query if cq['items'] and -1 < cq['index'] < len(cq['items']): diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 5d24df7b52..14da07b55e 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2018, Kovid Goyal +# TODO: Change the help screen for the standalone viewer + from __future__ import absolute_import, division, print_function, unicode_literals import json @@ -49,6 +51,7 @@ class EbookViewer(MainWindow): def __init__(self, open_at=None, continue_reading=None): MainWindow.__init__(self, None) + self.pending_open_at = open_at self.base_window_title = _('E-book viewer') self.setWindowTitle(self.base_window_title) self.in_full_screen_mode = None @@ -190,7 +193,8 @@ class EbookViewer(MainWindow): self.load_ebook(entry['pathtoebook']) def load_ebook(self, pathtoebook, open_at=None, reload_book=False): - # TODO: Implement open_at + if open_at: + self.pending_open_at = open_at self.setWindowTitle(_('Loading book… — {}').format(self.base_window_title)) self.web_view.show_preparing_message() self.save_annotations() @@ -215,6 +219,7 @@ class EbookViewer(MainWindow): self.book_prepared.emit(True, {'base': ans, 'pathtoebook': pathtoebook, 'open_at': open_at}) def load_finished(self, ok, data): + open_at, self.pending_open_at = self.pending_open_at, None if not ok: self.setWindowTitle(self.base_window_title) error_dialog(self, _('Loading book failed'), _( @@ -227,7 +232,14 @@ class EbookViewer(MainWindow): self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json' self.load_book_data() self.update_window_title() - self.web_view.start_book_load(initial_cfi=self.initial_cfi_for_current_book()) + initial_cfi = self.initial_cfi_for_current_book() + initial_toc_node = None + if open_at: + if open_at.startswith('toc:'): + initial_toc_node = self.toc_model.node_id_for_text(open_at[len('toc:'):]) + elif open_at.startswith('epubcfi(/'): + initial_cfi = open_at + self.web_view.start_book_load(initial_cfi=initial_cfi, initial_toc_node=initial_toc_node) def load_book_data(self): self.load_book_annotations() diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 5c63b346ae..1c9c89f0cc 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -396,9 +396,9 @@ class WebView(RestartingWebEngineView): for func, args in iteritems(self.pending_bridge_ready_actions): getattr(self.bridge, func)(*args) - def start_book_load(self, initial_cfi=None): + def start_book_load(self, initial_cfi=None, initial_toc_node=None): key = (set_book_path.path,) - self.execute_when_ready('start_book_load', key, initial_cfi, set_book_path.pathtoebook) + self.execute_when_ready('start_book_load', key, initial_cfi, initial_toc_node, set_book_path.pathtoebook) def execute_when_ready(self, action, *args): if self.bridge.ready: diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index e18949caaa..1ae52c8ffe 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -427,7 +427,7 @@ class View: cfi = '/' + rest return name, cfi - def display_book(self, book, initial_cfi): + def display_book(self, book, initial_cfi, initial_toc_node): self.hide_overlays() self.iframe.focus() is_current_book = self.book and self.book.key == book.key @@ -445,17 +445,24 @@ class View: pos = {'replace_history':True} unkey = username_key(get_interface_data().username) name = book.manifest.spine[0] - cfi = initial_cfi or None - q = parse_url_params() - if q.bookpos and q.bookpos.startswith('epubcfi(/'): - cfi = q.bookpos - elif book.last_read_position and book.last_read_position[unkey]: - cfi = book.last_read_position[unkey] + cfi = None + if initial_cfi and initial_cfi.startswith('epubcfi(/'): + cfi = initial_cfi + else: + q = parse_url_params() + if q.bookpos and q.bookpos.startswith('epubcfi(/'): + cfi = q.bookpos + elif book.last_read_position and book.last_read_position[unkey]: + cfi = book.last_read_position[unkey] cfiname, internal_cfi = self.parse_cfi(cfi, book) if cfiname and internal_cfi: name = cfiname pos.type, pos.cfi = 'cfi', internal_cfi - self.show_name(name, initial_position=pos) + navigated = False + if jstype(initial_toc_node) is 'number': + navigated = self.goto_toc_node(initial_toc_node) + if not navigated: + self.show_name(name, initial_position=pos) sd = get_session_data() c = sd.get('controls_help_shown_count', 0) if c < 1: @@ -533,20 +540,25 @@ class View: if idx is -1: error_dialog(_('Destination does not exist'), _( 'The file {} does not exist in this book').format(name)) - return + return False self.show_name(name, initial_position={'type':'anchor', 'anchor':frag, 'replace_history':False}) + return True def goto_toc_node(self, node_id): toc = self.book.manifest.toc + found = False def process_node(x): + nonlocal found if x.id is node_id: self.goto_named_destination(x.dest or '', x.frag or '') + found = True return for c in x.children: process_node(c) if toc: process_node(toc) + return found def on_next_spine_item(self, data): spine = self.book.manifest.spine diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index 9d6ad671dd..5b75f4838e 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -125,7 +125,7 @@ def show_error(title, msg, details): error_dialog(title, msg, details) -def manifest_received(key, initial_cfi, pathtoebook, end_type, xhr, ev): +def manifest_received(key, initial_cfi, initial_toc_node, pathtoebook, end_type, xhr, ev): nonlocal book if end_type is 'load': book = new_book(key, {}) @@ -137,7 +137,7 @@ def manifest_received(key, initial_cfi, pathtoebook, end_type, xhr, ev): book.is_complete = True v'delete book.manifest["metadata"]' v'delete book.manifest["last_read_positions"]' - view.display_book(book, initial_cfi) + view.display_book(book, initial_cfi, initial_toc_node) else: error_dialog(_('Could not open book'), _( 'Failed to load book manifest, click "Show details" for more info'), @@ -193,8 +193,8 @@ def show_preparing_message(msg): @from_python -def start_book_load(key, initial_cfi, pathtoebook): - xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, pathtoebook), ok_code=0) +def start_book_load(key, initial_cfi, initial_toc_node, pathtoebook): + xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, initial_toc_node, pathtoebook), ok_code=0) xhr.responseType = 'json' xhr.send()