Implement --open-at

This commit is contained in:
Kovid Goyal 2019-08-16 11:09:50 +05:30
parent 031b14fc78
commit 59b6c17ac2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 50 additions and 26 deletions

View File

@ -108,7 +108,7 @@ View an e-book.
'full screen when started.')) 'full screen when started.'))
a('--open-at', default=None, help=_( a('--open-at', default=None, help=_(
'The position at which to open the specified book. The position is ' '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 ' 'Alternately, you can use the form toc:something and it will open '
'at the location of the first Table of Contents entry that contains ' 'at the location of the first Table of Contents entry that contains '
'the string "something".')) 'the string "something".'))
@ -133,17 +133,13 @@ def main(args=sys.argv):
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(args) opts, args = parser.parse_args(args)
open_at = None if opts.open_at and not (opts.open_at.startswith('toc:') or opts.open_at.startswith('epubcfi(/')):
if opts.open_at is not None: raise SystemExit('Not a valid --open-at value: {}'.format(opts.open_at))
if opts.open_at.startswith('toc:'):
open_at = opts.open_at
else:
open_at = float(opts.open_at.replace(',', '.'))
listener = None listener = None
if vprefs['singleinstance']: if vprefs['singleinstance']:
try: try:
listener = ensure_single_instance(args, open_at) listener = ensure_single_instance(args, opts.open_at)
except Exception as e: except Exception as e:
import traceback import traceback
error_dialog(None, _('Failed to start viewer'), as_unicode(e), det_msg=traceback.format_exc(), show=True) 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.file_event_hook = acc
app.load_builtin_fonts() app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png'))) 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() main.set_exception_handler()
if len(args) > 1: if len(args) > 1:
acc.events.append(args[-1]) acc.events.append(args[-1])

View File

@ -205,6 +205,10 @@ class TOC(QStandardItemModel):
if primary_contains(query, item.text()): if primary_contains(query, item.text()):
yield item yield item
def node_id_for_text(self, query):
for item in self.find_items(query):
return item.node_id
def search(self, query): def search(self, query):
cq = self.current_query cq = self.current_query
if cq['items'] and -1 < cq['index'] < len(cq['items']): if cq['items'] and -1 < cq['index'] < len(cq['items']):

View File

@ -2,6 +2,8 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
# TODO: Change the help screen for the standalone viewer
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
import json import json
@ -49,6 +51,7 @@ class EbookViewer(MainWindow):
def __init__(self, open_at=None, continue_reading=None): def __init__(self, open_at=None, continue_reading=None):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.pending_open_at = open_at
self.base_window_title = _('E-book viewer') self.base_window_title = _('E-book viewer')
self.setWindowTitle(self.base_window_title) self.setWindowTitle(self.base_window_title)
self.in_full_screen_mode = None self.in_full_screen_mode = None
@ -190,7 +193,8 @@ class EbookViewer(MainWindow):
self.load_ebook(entry['pathtoebook']) self.load_ebook(entry['pathtoebook'])
def load_ebook(self, pathtoebook, open_at=None, reload_book=False): 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.setWindowTitle(_('Loading book… — {}').format(self.base_window_title))
self.web_view.show_preparing_message() self.web_view.show_preparing_message()
self.save_annotations() self.save_annotations()
@ -215,6 +219,7 @@ class EbookViewer(MainWindow):
self.book_prepared.emit(True, {'base': ans, 'pathtoebook': pathtoebook, 'open_at': open_at}) self.book_prepared.emit(True, {'base': ans, 'pathtoebook': pathtoebook, 'open_at': open_at})
def load_finished(self, ok, data): def load_finished(self, ok, data):
open_at, self.pending_open_at = self.pending_open_at, None
if not ok: if not ok:
self.setWindowTitle(self.base_window_title) self.setWindowTitle(self.base_window_title)
error_dialog(self, _('Loading book failed'), _( 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.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'
self.load_book_data() self.load_book_data()
self.update_window_title() 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): def load_book_data(self):
self.load_book_annotations() self.load_book_annotations()

View File

@ -396,9 +396,9 @@ class WebView(RestartingWebEngineView):
for func, args in iteritems(self.pending_bridge_ready_actions): for func, args in iteritems(self.pending_bridge_ready_actions):
getattr(self.bridge, func)(*args) 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,) 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): def execute_when_ready(self, action, *args):
if self.bridge.ready: if self.bridge.ready:

View File

@ -427,7 +427,7 @@ class View:
cfi = '/' + rest cfi = '/' + rest
return name, cfi return name, cfi
def display_book(self, book, initial_cfi): def display_book(self, book, initial_cfi, initial_toc_node):
self.hide_overlays() self.hide_overlays()
self.iframe.focus() self.iframe.focus()
is_current_book = self.book and self.book.key == book.key is_current_book = self.book and self.book.key == book.key
@ -445,17 +445,24 @@ class View:
pos = {'replace_history':True} pos = {'replace_history':True}
unkey = username_key(get_interface_data().username) unkey = username_key(get_interface_data().username)
name = book.manifest.spine[0] name = book.manifest.spine[0]
cfi = initial_cfi or None cfi = None
q = parse_url_params() if initial_cfi and initial_cfi.startswith('epubcfi(/'):
if q.bookpos and q.bookpos.startswith('epubcfi(/'): cfi = initial_cfi
cfi = q.bookpos else:
elif book.last_read_position and book.last_read_position[unkey]: q = parse_url_params()
cfi = book.last_read_position[unkey] 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) cfiname, internal_cfi = self.parse_cfi(cfi, book)
if cfiname and internal_cfi: if cfiname and internal_cfi:
name = cfiname name = cfiname
pos.type, pos.cfi = 'cfi', internal_cfi 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() sd = get_session_data()
c = sd.get('controls_help_shown_count', 0) c = sd.get('controls_help_shown_count', 0)
if c < 1: if c < 1:
@ -533,20 +540,25 @@ class View:
if idx is -1: if idx is -1:
error_dialog(_('Destination does not exist'), _( error_dialog(_('Destination does not exist'), _(
'The file {} does not exist in this book').format(name)) '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}) self.show_name(name, initial_position={'type':'anchor', 'anchor':frag, 'replace_history':False})
return True
def goto_toc_node(self, node_id): def goto_toc_node(self, node_id):
toc = self.book.manifest.toc toc = self.book.manifest.toc
found = False
def process_node(x): def process_node(x):
nonlocal found
if x.id is node_id: if x.id is node_id:
self.goto_named_destination(x.dest or '', x.frag or '') self.goto_named_destination(x.dest or '', x.frag or '')
found = True
return return
for c in x.children: for c in x.children:
process_node(c) process_node(c)
if toc: if toc:
process_node(toc) process_node(toc)
return found
def on_next_spine_item(self, data): def on_next_spine_item(self, data):
spine = self.book.manifest.spine spine = self.book.manifest.spine

View File

@ -125,7 +125,7 @@ def show_error(title, msg, details):
error_dialog(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 nonlocal book
if end_type is 'load': if end_type is 'load':
book = new_book(key, {}) book = new_book(key, {})
@ -137,7 +137,7 @@ def manifest_received(key, initial_cfi, pathtoebook, end_type, xhr, ev):
book.is_complete = True book.is_complete = True
v'delete book.manifest["metadata"]' v'delete book.manifest["metadata"]'
v'delete book.manifest["last_read_positions"]' v'delete book.manifest["last_read_positions"]'
view.display_book(book, initial_cfi) view.display_book(book, initial_cfi, initial_toc_node)
else: else:
error_dialog(_('Could not open book'), _( error_dialog(_('Could not open book'), _(
'Failed to load book manifest, click "Show details" for more info'), 'Failed to load book manifest, click "Show details" for more info'),
@ -193,8 +193,8 @@ def show_preparing_message(msg):
@from_python @from_python
def start_book_load(key, initial_cfi, pathtoebook): def start_book_load(key, initial_cfi, initial_toc_node, pathtoebook):
xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, pathtoebook), ok_code=0) xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, initial_toc_node, pathtoebook), ok_code=0)
xhr.responseType = 'json' xhr.responseType = 'json'
xhr.send() xhr.send()