mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-11-29 09:45:01 -05:00
416 lines
14 KiB
Plaintext
416 lines
14 KiB
Plaintext
# vim:fileencoding=utf-8
|
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
from __python__ import bound_methods, hash_literals
|
|
|
|
import traceback
|
|
from elementmaker import E
|
|
from gettext import gettext as _, install
|
|
|
|
import initialize # noqa: unused-import
|
|
from ajax import ajax, workaround_qt_bug
|
|
from book_list.globals import get_session_data, set_session_data
|
|
from book_list.library_data import library_data
|
|
from book_list.theme import get_color, css_for_variables
|
|
from dom import get_widget_css, set_css
|
|
from modals import create_modal_container
|
|
from qt import from_python, to_python
|
|
from read_book.db import new_book
|
|
from read_book.footnotes import main as footnotes_main
|
|
from read_book.globals import runtime, set_system_colors, ui_operations, default_color_schemes
|
|
from read_book.iframe import main as iframe_main
|
|
from read_book.shortcuts import add_standalone_viewer_shortcuts
|
|
from read_book.view import View
|
|
from session import local_storage, session_defaults
|
|
from utils import debounce, encode_query_with_path, parse_url_params
|
|
from viewer.constants import FAKE_HOST, FAKE_PROTOCOL, READER_BACKGROUND_URL
|
|
|
|
runtime.is_standalone_viewer = True
|
|
runtime.FAKE_HOST = FAKE_HOST
|
|
runtime.SANDBOX_HOST = FAKE_HOST.rpartition('.')[0] + '.sandbox'
|
|
runtime.FAKE_PROTOCOL = FAKE_PROTOCOL
|
|
book = None
|
|
view = None
|
|
|
|
|
|
def file_received(name, file_data, proceed, end_type, xhr, ev):
|
|
end_type = workaround_qt_bug(xhr, end_type)
|
|
if end_type is 'abort':
|
|
return
|
|
if end_type is not 'load':
|
|
show_error(_('Failed to load file from book'), _(
|
|
'Could not load the file: {} with error: {}').format(name, xhr.error_html))
|
|
return
|
|
if not xhr.responseType or xhr.responseType is 'text':
|
|
result = xhr.responseText
|
|
else if xhr.responseType is 'blob':
|
|
result = xhr.response
|
|
else:
|
|
show_error(_('Failed to load file from book'), _(
|
|
'Could not load the file: {} unknown response type: {}').format(name, xhr.responseType))
|
|
return
|
|
|
|
proceed(result, name, file_data.mimetype, book)
|
|
|
|
|
|
def get_file(book, name, proceed):
|
|
entry = book.manifest.files[name]
|
|
if not entry:
|
|
raise ValueError(f'No file named {name} in the book manifest')
|
|
xhr = ajax('book/' + name, file_received.bind(None, name, entry, proceed), ok_code=0)
|
|
if entry.is_html or entry.mimetype.startswith('text/') or entry.mimetype is 'application/javascript':
|
|
xhr.responseType = 'text'
|
|
else:
|
|
xhr.responseType = 'blob'
|
|
xhr.send()
|
|
|
|
|
|
def get_mathjax_files(proceed):
|
|
proceed({})
|
|
|
|
|
|
def update_url_state(replace):
|
|
if view and view.currently_showing:
|
|
bookpos = view.currently_showing.bookpos
|
|
if bookpos:
|
|
query = {'bookpos': bookpos}
|
|
query = encode_query_with_path(query)
|
|
if replace:
|
|
window.history.replaceState(None, '', query)
|
|
else:
|
|
window.history.pushState(None, '', query)
|
|
|
|
|
|
def on_pop_state():
|
|
if view and view.currently_showing:
|
|
data = parse_url_params()
|
|
if data.bookpos and data.bookpos.startswith('epubcfi(/'):
|
|
view.goto_cfi(data.bookpos)
|
|
|
|
|
|
def show_error(title, msg, details):
|
|
to_python.show_error(title, msg, details)
|
|
|
|
|
|
def manifest_received(key, initial_position, pathtoebook, highlights, end_type, xhr, ev):
|
|
nonlocal book
|
|
end_type = workaround_qt_bug(xhr, end_type)
|
|
if end_type is 'load':
|
|
book = new_book(key, {})
|
|
data = xhr.response
|
|
book.manifest = data[0]
|
|
book.metadata = book.manifest.metadata = data[1]
|
|
book.manifest.pathtoebook = pathtoebook
|
|
book.highlights = highlights
|
|
book.stored_files = {}
|
|
book.is_complete = True
|
|
v'delete book.manifest["metadata"]'
|
|
v'delete book.manifest["last_read_positions"]'
|
|
view.display_book(book, initial_position)
|
|
else:
|
|
show_error(_('Could not open book'), _(
|
|
'Failed to load book manifest, click "Show details" for more info'),
|
|
xhr.error_html or None)
|
|
|
|
|
|
class SessionData:
|
|
|
|
def __init__(self, prefs):
|
|
defaults = session_defaults()
|
|
self.data = {k: defaults[k] if prefs[k] is undefined else prefs[k] for k in defaults}
|
|
|
|
def get(self, key, defval):
|
|
ans = self.data[key]
|
|
if ans is undefined or ans is None:
|
|
if defval is undefined:
|
|
defval = None
|
|
return defval
|
|
return ans
|
|
|
|
def set(self, key, val):
|
|
if val is None:
|
|
self.data[key] = session_defaults()[key]
|
|
else:
|
|
self.data[key] = val
|
|
to_python.set_session_data(key, val)
|
|
|
|
def clear(self):
|
|
defaults = session_defaults()
|
|
self.data = {k: defaults[k] for k in defaults}
|
|
to_python.set_session_data('*', None)
|
|
|
|
|
|
class LocalStorage:
|
|
|
|
def __init__(self, data):
|
|
self.data = data
|
|
|
|
def get(self, key, defval):
|
|
ans = self.data[key]
|
|
if ans is undefined or ans is None:
|
|
if defval is undefined:
|
|
defval = None
|
|
return defval
|
|
return ans
|
|
|
|
def set(self, key, val):
|
|
self.data[key] = val
|
|
to_python.set_local_storage(key, val)
|
|
|
|
def clear(self):
|
|
self.data = {}
|
|
to_python.set_local_storage('*', None)
|
|
|
|
|
|
def create_session_data(prefs, local_storage_data):
|
|
sd = SessionData(prefs)
|
|
set_session_data(sd)
|
|
local_storage.storage = LocalStorage(local_storage_data)
|
|
|
|
|
|
@from_python
|
|
def create_view(prefs, local_storage, field_metadata, ui_data):
|
|
nonlocal view
|
|
set_system_colors(ui_data.system_colors)
|
|
runtime.all_font_families = ui_data.all_font_families
|
|
library_data.field_metadata = field_metadata
|
|
document.documentElement.style.fontFamily = f'"{ui_data.ui_font_family}", sans-serif'
|
|
document.documentElement.style.fontSize = ui_data.ui_font_sz
|
|
runtime.QT_VERSION = ui_data.QT_VERSION
|
|
if view is None:
|
|
create_session_data(prefs, local_storage)
|
|
view = View(document.getElementById('view'))
|
|
window.addEventListener('resize', debounce(view.on_resize.bind(self), 250))
|
|
to_python.view_created({'default_color_schemes': default_color_schemes})
|
|
if ui_data.show_home_page_on_ready:
|
|
view.overlay.open_book(False)
|
|
|
|
|
|
@from_python
|
|
def set_system_palette(system_colors):
|
|
set_system_colors(system_colors)
|
|
if view:
|
|
view.update_color_scheme()
|
|
|
|
|
|
@from_python
|
|
def highlight_action(uuid, which):
|
|
if view:
|
|
view.highlight_action(uuid, which)
|
|
|
|
|
|
@from_python
|
|
def generic_action(which, data):
|
|
if which is 'set-notes-in-highlight':
|
|
view.set_notes_for_highlight(data.uuid, data.notes or '')
|
|
|
|
|
|
@from_python
|
|
def show_home_page():
|
|
view.overlay.open_book(False)
|
|
|
|
|
|
@from_python
|
|
def start_book_load(key, initial_position, pathtoebook, highlights):
|
|
xhr = ajax('manifest', manifest_received.bind(None, key, initial_position, pathtoebook, highlights), ok_code=0)
|
|
xhr.responseType = 'json'
|
|
xhr.send()
|
|
|
|
|
|
@from_python
|
|
def goto_toc_node(node_id):
|
|
view.goto_toc_node(node_id)
|
|
|
|
|
|
@from_python
|
|
def goto_cfi(cfi):
|
|
view.goto_cfi(cfi)
|
|
|
|
|
|
@from_python
|
|
def full_screen_state_changed(viewer_in_full_screen):
|
|
runtime.viewer_in_full_screen = viewer_in_full_screen
|
|
|
|
|
|
@from_python
|
|
def get_current_cfi(request_id):
|
|
view.get_current_cfi(request_id, ui_operations.report_cfi)
|
|
|
|
|
|
@from_python
|
|
def goto_frac(frac):
|
|
if view:
|
|
view.goto_frac(frac)
|
|
|
|
|
|
@from_python
|
|
def background_image_changed(img_id):
|
|
img = document.getElementById(img_id)
|
|
if img:
|
|
img.src = READER_BACKGROUND_URL + '?' + Date().getTime()
|
|
|
|
|
|
@from_python
|
|
def trigger_shortcut(which):
|
|
if view:
|
|
view.on_handle_shortcut({'name': which})
|
|
|
|
|
|
@from_python
|
|
def show_search_result(sr):
|
|
if view:
|
|
view.show_search_result(sr)
|
|
|
|
@from_python
|
|
def prepare_for_close():
|
|
if view:
|
|
view.prepare_for_close()
|
|
else:
|
|
ui_operations.close_prep_finished(None)
|
|
|
|
|
|
@from_python
|
|
def viewer_font_size_changed():
|
|
if view:
|
|
view.viewer_font_size_changed()
|
|
|
|
|
|
def onerror(msg, script_url, line_number, column_number, error_object):
|
|
if not error_object:
|
|
# cross domain error
|
|
return False
|
|
fname = script_url.rpartition('/')[-1] or script_url
|
|
msg += '<br><span style="font-size:smaller">' + 'Error at {}:{}:{}'.format(fname, line_number, column_number or '') + '</span>'
|
|
details = ''
|
|
console.log(error_object)
|
|
details = traceback.format_exception(error_object).join('')
|
|
show_error(_('Unhandled error'), msg, details)
|
|
return True
|
|
|
|
|
|
if window is window.top:
|
|
# main
|
|
TRANSLATIONS_DATA = v'__TRANSLATIONS_DATA__'
|
|
if TRANSLATIONS_DATA:
|
|
install(TRANSLATIONS_DATA)
|
|
add_standalone_viewer_shortcuts()
|
|
ui_operations.get_file = get_file
|
|
ui_operations.get_mathjax_files = get_mathjax_files
|
|
ui_operations.update_url_state = update_url_state
|
|
ui_operations.show_error = show_error
|
|
ui_operations.redisplay_book = def():
|
|
view.redisplay_book()
|
|
ui_operations.reload_book = def():
|
|
to_python.reload_book()
|
|
ui_operations.forward_gesture = def(gesture):
|
|
view.forward_gesture(gesture)
|
|
ui_operations.update_color_scheme = def():
|
|
view.update_color_scheme()
|
|
ui_operations.update_font_size = def():
|
|
view.update_font_size()
|
|
ui_operations.focus_iframe = def():
|
|
view.focus_iframe()
|
|
ui_operations.goto_cfi = def(cfi):
|
|
return view.goto_cfi(cfi)
|
|
ui_operations.goto_frac = def(frac):
|
|
return view.goto_frac(frac)
|
|
ui_operations.goto_book_position = def(bpos):
|
|
return view.goto_book_position(bpos)
|
|
ui_operations.goto_reference = def(ref):
|
|
return view.goto_reference(ref)
|
|
ui_operations.toggle_toc = def():
|
|
to_python.toggle_toc()
|
|
ui_operations.toggle_bookmarks = def():
|
|
to_python.toggle_bookmarks()
|
|
ui_operations.toggle_highlights = def():
|
|
to_python.toggle_highlights()
|
|
ui_operations.new_bookmark = def(request_id, data):
|
|
if request_id is 'new-bookmark':
|
|
to_python.new_bookmark(data)
|
|
ui_operations.toggle_inspector = def():
|
|
to_python.toggle_inspector()
|
|
ui_operations.content_file_changed = def(name):
|
|
to_python.content_file_changed(name)
|
|
ui_operations.show_search = def(text):
|
|
to_python.show_search(text)
|
|
ui_operations.find_next = def(previous):
|
|
to_python.find_next(previous)
|
|
ui_operations.reset_interface = def():
|
|
sd = get_session_data()
|
|
defaults = session_defaults()
|
|
m = sd.get('standalone_misc_settings', {})
|
|
v'delete m.show_actions_toolbar'
|
|
sd.set('standalone_misc_settings', m)
|
|
sd.set('book_scrollbar', False)
|
|
view.book_scrollbar.apply_visibility()
|
|
sd.set('header', defaults.header)
|
|
sd.set('footer', defaults.footer)
|
|
view.update_header_footer()
|
|
to_python.reset_interface()
|
|
ui_operations.open_url = def(url):
|
|
to_python.open_url(url)
|
|
ui_operations.quit = def():
|
|
to_python.quit()
|
|
ui_operations.toggle_lookup = def(force_show):
|
|
to_python.toggle_lookup(v'!!force_show')
|
|
ui_operations.selection_changed = def(selected_text, annot_id):
|
|
to_python.selection_changed(selected_text, annot_id or None)
|
|
ui_operations.update_current_toc_nodes = def(current_node_id, top_level_node_id):
|
|
to_python.update_current_toc_nodes(current_node_id, top_level_node_id)
|
|
ui_operations.toggle_full_screen = def():
|
|
to_python.toggle_full_screen()
|
|
ui_operations.report_cfi = def(request_id, data):
|
|
to_python.report_cfi(request_id, data)
|
|
ui_operations.ask_for_open = def(path):
|
|
to_python.ask_for_open(path)
|
|
ui_operations.copy_selection = def(text):
|
|
to_python.copy_selection(text or None)
|
|
ui_operations.view_image = def(name):
|
|
to_python.view_image(name)
|
|
ui_operations.copy_image = def(name):
|
|
to_python.copy_image(name)
|
|
ui_operations.change_background_image = def(img_id):
|
|
to_python.change_background_image(img_id)
|
|
ui_operations.quit = def():
|
|
to_python.quit()
|
|
ui_operations.overlay_visibility_changed = def(visible):
|
|
to_python.overlay_visibility_changed(visible)
|
|
ui_operations.reference_mode_changed = def(enabled):
|
|
to_python.reference_mode_changed(enabled)
|
|
ui_operations.show_loading_message = def(msg):
|
|
to_python.show_loading_message(msg)
|
|
ui_operations.export_shortcut_map = def(smap):
|
|
to_python.export_shortcut_map(smap)
|
|
ui_operations.print_book = def():
|
|
to_python.print_book()
|
|
ui_operations.clear_history = def():
|
|
to_python.clear_history()
|
|
ui_operations.customize_toolbar = def():
|
|
to_python.customize_toolbar()
|
|
ui_operations.autoscroll_state_changed = def(active):
|
|
to_python.autoscroll_state_changed(active)
|
|
ui_operations.search_result_not_found = def(sr):
|
|
to_python.search_result_not_found(sr)
|
|
ui_operations.scrollbar_context_menu = def(x, y, frac):
|
|
to_python.scrollbar_context_menu(x, y, frac)
|
|
ui_operations.close_prep_finished = def(cfi):
|
|
to_python.close_prep_finished(cfi)
|
|
ui_operations.highlights_changed = def(highlights):
|
|
to_python.highlights_changed(highlights)
|
|
|
|
document.body.appendChild(E.div(id='view'))
|
|
window.onerror = onerror
|
|
create_modal_container()
|
|
document.head.appendChild(E.style(css_for_variables() + '\n\n' + get_widget_css()))
|
|
set_css(document.body, background_color=get_color('window-background'), color=get_color('window-foreground'))
|
|
setTimeout(def():
|
|
window.onpopstate = on_pop_state
|
|
, 0) # We do this after event loop ticks over to avoid catching popstate events that some browsers send on page load
|
|
else:
|
|
# iframe
|
|
div = document.getElementById('calibre-viewer-footnote-iframe')
|
|
if div:
|
|
footnotes_main()
|
|
else:
|
|
iframe_main()
|
|
add_standalone_viewer_shortcuts()
|