mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement opening of local ebook files
This commit is contained in:
parent
48a2990600
commit
95aa3a50c2
@ -156,7 +156,7 @@ def main(args=sys.argv):
|
||||
app.setWindowIcon(QIcon(I('viewer.png')))
|
||||
main = EbookViewer()
|
||||
main.set_exception_handler()
|
||||
if args:
|
||||
if len(args) > 1:
|
||||
acc.events.append(args[-1])
|
||||
acc.got_file.connect(main.handle_commandline_arg)
|
||||
main.show()
|
||||
|
@ -17,7 +17,8 @@ from PyQt5.Qt import (
|
||||
|
||||
from calibre import prints
|
||||
from calibre.constants import config_dir
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.customize.ui import available_input_formats
|
||||
from calibre.gui2 import choose_files, error_dialog
|
||||
from calibre.gui2.main_window import MainWindow
|
||||
from calibre.gui2.viewer.annotations import (
|
||||
merge_annotations, parse_annotations, save_annots_to_epub, serialize_annotations
|
||||
@ -92,6 +93,7 @@ class EbookViewer(MainWindow):
|
||||
self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks)
|
||||
self.web_view.update_current_toc_nodes.connect(self.toc.update_current_toc_nodes)
|
||||
self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
|
||||
self.web_view.ask_for_open.connect(self.ask_for_open, type=Qt.QueuedConnection)
|
||||
self.setCentralWidget(self.web_view)
|
||||
self.restore_state()
|
||||
|
||||
@ -161,6 +163,17 @@ class EbookViewer(MainWindow):
|
||||
|
||||
# Load book {{{
|
||||
|
||||
def ask_for_open(self, path=None):
|
||||
if path is None:
|
||||
files = choose_files(
|
||||
self, 'ebook viewer open dialog',
|
||||
_('Choose e-book'), [(_('E-books'), available_input_formats())],
|
||||
all_files=False, select_only_single_file=True)
|
||||
if not files:
|
||||
return
|
||||
path = files[0]
|
||||
self.load_ebook(path)
|
||||
|
||||
def load_ebook(self, pathtoebook, open_at=None, reload_book=False):
|
||||
# TODO: Implement open_at
|
||||
self.setWindowTitle(_('Loading book … — {}').format(self.base_window_title))
|
||||
@ -193,7 +206,7 @@ class EbookViewer(MainWindow):
|
||||
'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'])
|
||||
set_book_path(data['base'], data['pathtoebook'])
|
||||
self.current_book_data = data
|
||||
self.current_book_data['annotations_map'] = defaultdict(list)
|
||||
self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'
|
||||
|
@ -45,14 +45,14 @@ vprefs.defaults['main_window_geometry'] = None
|
||||
|
||||
# Override network access to load data from the book {{{
|
||||
|
||||
def set_book_path(path=None):
|
||||
if path is not None:
|
||||
set_book_path.path = os.path.abspath(path)
|
||||
set_book_path.metadata = get_data('calibre-book-metadata.json')[0]
|
||||
set_book_path.manifest, set_book_path.manifest_mime = get_data('calibre-book-manifest.json')
|
||||
set_book_path.metadata = get_data('calibre-book-metadata.json')[0]
|
||||
set_book_path.parsed_metadata = json_loads(set_book_path.metadata)
|
||||
set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
|
||||
def set_book_path(path, pathtoebook):
|
||||
set_book_path.pathtoebook = pathtoebook
|
||||
set_book_path.path = os.path.abspath(path)
|
||||
set_book_path.metadata = get_data('calibre-book-metadata.json')[0]
|
||||
set_book_path.manifest, set_book_path.manifest_mime = get_data('calibre-book-manifest.json')
|
||||
set_book_path.metadata = get_data('calibre-book-metadata.json')[0]
|
||||
set_book_path.parsed_metadata = json_loads(set_book_path.metadata)
|
||||
set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
|
||||
|
||||
|
||||
def get_data(name):
|
||||
@ -189,6 +189,7 @@ class ViewerBridge(Bridge):
|
||||
update_current_toc_nodes = from_js(object, object)
|
||||
toggle_full_screen = from_js()
|
||||
report_cfi = from_js(object, object)
|
||||
ask_for_open = from_js(object)
|
||||
|
||||
create_view = to_js()
|
||||
show_preparing_message = to_js()
|
||||
@ -305,6 +306,7 @@ class WebView(RestartingWebEngineView):
|
||||
toggle_bookmarks = pyqtSignal()
|
||||
update_current_toc_nodes = pyqtSignal(object, object)
|
||||
toggle_full_screen = pyqtSignal()
|
||||
ask_for_open = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self._host_widget = None
|
||||
@ -324,6 +326,7 @@ class WebView(RestartingWebEngineView):
|
||||
self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks)
|
||||
self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes)
|
||||
self.bridge.toggle_full_screen.connect(self.toggle_full_screen)
|
||||
self.bridge.ask_for_open.connect(self.ask_for_open)
|
||||
self.bridge.report_cfi.connect(self.call_callback)
|
||||
self.pending_bridge_ready_actions = {}
|
||||
self.setPage(self._page)
|
||||
@ -392,7 +395,7 @@ class WebView(RestartingWebEngineView):
|
||||
|
||||
def start_book_load(self, initial_cfi=None):
|
||||
key = (set_book_path.path,)
|
||||
self.execute_when_ready('start_book_load', key, initial_cfi)
|
||||
self.execute_when_ready('start_book_load', key, initial_cfi, set_book_path.pathtoebook)
|
||||
|
||||
def execute_when_ready(self, action, *args):
|
||||
if self.bridge.ready:
|
||||
|
62
src/pyj/read_book/open_book.pyj
Normal file
62
src/pyj/read_book/open_book.pyj
Normal file
@ -0,0 +1,62 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __python__ import bound_methods, hash_literals
|
||||
|
||||
from elementmaker import E
|
||||
from gettext import gettext as _
|
||||
|
||||
from book_list.globals import get_session_data
|
||||
from book_list.item_list import create_item, create_item_list
|
||||
from dom import unique_id
|
||||
from read_book.globals import ui_operations
|
||||
from widgets import create_button
|
||||
|
||||
|
||||
def create_open_book(container, book):
|
||||
container.appendChild(E.div(style='margin: 1rem'))
|
||||
container = container.lastChild
|
||||
container.appendChild(create_button(_('Open a book from your computer'), action=ui_operations.ask_for_open.bind(None, None)))
|
||||
sd = get_session_data()
|
||||
rl = sd.get('standalone_recently_opened')
|
||||
if rl.length:
|
||||
container.appendChild(E.div(id=unique_id()))
|
||||
c = container.lastChild
|
||||
items = []
|
||||
c.appendChild(E.div(style='margin-top: 1rem', _('Recently viewed books')))
|
||||
c.appendChild(E.div())
|
||||
for entry in rl:
|
||||
if book and book.manifest.pathtoebook is entry.pathtoebook:
|
||||
continue
|
||||
fname = str.replace(entry.pathtoebook, '\\', '/')
|
||||
if '/' in fname:
|
||||
fname = fname.rpartition('/')[-1]
|
||||
items.push(create_item(entry.title, ui_operations.ask_for_open.bind(None, entry.pathtoebook), fname))
|
||||
create_item_list(c.lastChild, items)
|
||||
|
||||
c.appendChild(E.div(style='margin: 1rem'))
|
||||
c.lastChild.appendChild(
|
||||
create_button(_('Clear recent list'), action=clear_recent_list.bind(None, c.id)))
|
||||
|
||||
|
||||
def clear_recent_list(container_id):
|
||||
sd = get_session_data()
|
||||
sd.set('standalone_recently_opened', v'[]')
|
||||
document.getElementById(container_id).style.display = 'none'
|
||||
|
||||
|
||||
def add_book_to_recently_viewed(book):
|
||||
sd = get_session_data()
|
||||
rl = sd.get('standalone_recently_opened')
|
||||
key = book.manifest.pathtoebook
|
||||
new_entry = {
|
||||
'key': key, 'pathtoebook': book.manifest.pathtoebook,
|
||||
'title': book.metadata.title, 'authors': book.metadata.authors,
|
||||
'timestamp': Date().toISOString(),
|
||||
}
|
||||
ans = v'[]'
|
||||
ans.push(new_entry)
|
||||
for entry in rl:
|
||||
if entry.key is not key:
|
||||
ans.push(entry)
|
||||
sd.set('standalone_recently_opened', ans.slice(0, 25))
|
||||
return ans
|
@ -10,8 +10,9 @@ from book_list.router import home
|
||||
from book_list.theme import get_color
|
||||
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
|
||||
from modals import error_dialog
|
||||
from read_book.globals import ui_operations, runtime
|
||||
from read_book.globals import runtime, ui_operations
|
||||
from read_book.goto import create_goto_panel
|
||||
from read_book.open_book import create_open_book
|
||||
from read_book.prefs.font_size import create_font_size_panel
|
||||
from read_book.prefs.main import create_prefs_panel
|
||||
from read_book.toc import create_toc_panel
|
||||
@ -220,7 +221,10 @@ class MainOverlay:
|
||||
back_action = ac(_('Back'), None, self.back, 'arrow-left')
|
||||
forward_action = ac(_('Forward'), None, self.forward, 'arrow-right')
|
||||
if runtime.is_standalone_viewer:
|
||||
reload_actions = E.ul(reload_action)
|
||||
reload_actions = E.ul(
|
||||
ac(_('Open book'), _('Open a new book'), self.overlay.open_book, 'book'),
|
||||
reload_action
|
||||
)
|
||||
nav_actions = E.ul(back_action, forward_action)
|
||||
else:
|
||||
reload_actions = E.ul(sync_action, delete_action, reload_action)
|
||||
@ -384,6 +388,30 @@ class FontSizeOverlay: # {{{
|
||||
create_font_size_panel(container, self.overlay.hide_current_panel)
|
||||
# }}}
|
||||
|
||||
class OpenBook: # {{{
|
||||
|
||||
def __init__(self, overlay, closeable):
|
||||
self.overlay = overlay
|
||||
self.closeable = closeable
|
||||
|
||||
def on_container_click(self, evt):
|
||||
pass # Dont allow panel to be closed by a click
|
||||
|
||||
def show(self, container):
|
||||
container.style.backgroundColor = get_color('window-background')
|
||||
close_button_style = '' if self.closeable else 'display: none'
|
||||
container.appendChild(E.div(
|
||||
style='padding: 1ex 1em; border-bottom: solid 1px currentColor; display:flex; justify-content: space-between',
|
||||
E.h2(_('Open a new book')),
|
||||
E.div(
|
||||
svgicon('close'), style=f'cursor:pointer; {close_button_style}',
|
||||
onclick=def(event):event.preventDefault(), event.stopPropagation(), self.overlay.hide_current_panel(event);,
|
||||
class_='simple-link'),
|
||||
))
|
||||
create_open_book(container, self.overlay.view?.book)
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
class Overlay:
|
||||
|
||||
@ -458,6 +486,13 @@ class Overlay:
|
||||
self.panels = [DeleteBook(self, _('Are you sure you want to reload this book?'), 'refresh', _('Reload book'), True)]
|
||||
self.show_current_panel()
|
||||
|
||||
def open_book(self, closeable):
|
||||
self.hide_current_panel()
|
||||
if jstype(closeable) is not 'boolean':
|
||||
closeable = True
|
||||
self.panels = [OpenBook(self, closeable)]
|
||||
self.show_current_panel()
|
||||
|
||||
def sync_book(self):
|
||||
self.hide_current_panel()
|
||||
self.panels = [SyncBook(self)]
|
||||
|
@ -18,6 +18,7 @@ from read_book.globals import (
|
||||
current_book, runtime, set_current_spine_item, ui_operations
|
||||
)
|
||||
from read_book.goto import get_next_section
|
||||
from read_book.open_book import add_book_to_recently_viewed
|
||||
from read_book.overlay import Overlay
|
||||
from read_book.prefs.colors import resolve_color_scheme
|
||||
from read_book.prefs.font_size import change_font_size_by
|
||||
@ -382,6 +383,8 @@ class View:
|
||||
self.content_popup_overlay.loaded_resources = {}
|
||||
self.timers.start_book(book)
|
||||
self.book = current_book.book = book
|
||||
if runtime.is_standalone_viewer:
|
||||
add_book_to_recently_viewed(book)
|
||||
if ui_operations.update_last_read_time:
|
||||
ui_operations.update_last_read_time(book)
|
||||
pos = {'replace_history':True}
|
||||
|
@ -42,6 +42,7 @@ defaults = {
|
||||
'word_actions': v'[]',
|
||||
'standalone_font_settings': {},
|
||||
'standalone_misc_settings': {},
|
||||
'standalone_recently_opened': v'[]',
|
||||
}
|
||||
|
||||
is_local_setting = {
|
||||
@ -59,6 +60,7 @@ is_local_setting = {
|
||||
'controls_help_shown_count': True,
|
||||
'standalone_font_settings': True,
|
||||
'standalone_misc_settings': True,
|
||||
'standalone_recently_opened': True,
|
||||
}
|
||||
|
||||
|
||||
|
@ -124,13 +124,14 @@ def show_error(title, msg, details):
|
||||
error_dialog(title, msg, details)
|
||||
|
||||
|
||||
def manifest_received(key, initial_cfi, end_type, xhr, ev):
|
||||
def manifest_received(key, initial_cfi, pathtoebook, end_type, xhr, ev):
|
||||
nonlocal book
|
||||
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.stored_files = {}
|
||||
book.is_complete = True
|
||||
v'delete book.manifest["metadata"]'
|
||||
@ -181,6 +182,7 @@ def create_view(prefs, all_font_families):
|
||||
if view is None:
|
||||
create_session_data(prefs)
|
||||
view = View(document.getElementById('view'))
|
||||
view.overlay.open_book(False)
|
||||
|
||||
|
||||
@from_python
|
||||
@ -189,8 +191,8 @@ def show_preparing_message(msg):
|
||||
|
||||
|
||||
@from_python
|
||||
def start_book_load(key, initial_cfi):
|
||||
xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi), ok_code=0)
|
||||
def start_book_load(key, initial_cfi, pathtoebook):
|
||||
xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, pathtoebook), ok_code=0)
|
||||
xhr.responseType = 'json'
|
||||
xhr.send()
|
||||
|
||||
@ -257,6 +259,8 @@ if window is window.top:
|
||||
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)
|
||||
document.body.appendChild(E.div(id='view'))
|
||||
window.onerror = onerror
|
||||
create_modal_container()
|
||||
|
Loading…
x
Reference in New Issue
Block a user