mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement --open-at
This commit is contained in:
parent
031b14fc78
commit
59b6c17ac2
@ -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])
|
||||||
|
@ -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']):
|
||||||
|
@ -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()
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user