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')))
|
app.setWindowIcon(QIcon(I('viewer.png')))
|
||||||
main = EbookViewer()
|
main = EbookViewer()
|
||||||
main.set_exception_handler()
|
main.set_exception_handler()
|
||||||
if args:
|
if len(args) > 1:
|
||||||
acc.events.append(args[-1])
|
acc.events.append(args[-1])
|
||||||
acc.got_file.connect(main.handle_commandline_arg)
|
acc.got_file.connect(main.handle_commandline_arg)
|
||||||
main.show()
|
main.show()
|
||||||
|
@ -17,7 +17,8 @@ from PyQt5.Qt import (
|
|||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import config_dir
|
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.main_window import MainWindow
|
||||||
from calibre.gui2.viewer.annotations import (
|
from calibre.gui2.viewer.annotations import (
|
||||||
merge_annotations, parse_annotations, save_annots_to_epub, serialize_annotations
|
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.toggle_bookmarks.connect(self.toggle_bookmarks)
|
||||||
self.web_view.update_current_toc_nodes.connect(self.toc.update_current_toc_nodes)
|
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.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.setCentralWidget(self.web_view)
|
||||||
self.restore_state()
|
self.restore_state()
|
||||||
|
|
||||||
@ -161,6 +163,17 @@ class EbookViewer(MainWindow):
|
|||||||
|
|
||||||
# Load book {{{
|
# 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):
|
def load_ebook(self, pathtoebook, open_at=None, reload_book=False):
|
||||||
# TODO: Implement open_at
|
# TODO: Implement open_at
|
||||||
self.setWindowTitle(_('Loading book … — {}').format(self.base_window_title))
|
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']),
|
'Failed to open the book at {0}. Click "Show details" for more info.').format(data['pathtoebook']),
|
||||||
det_msg=data['tb'], show=True)
|
det_msg=data['tb'], show=True)
|
||||||
return
|
return
|
||||||
set_book_path(data['base'])
|
set_book_path(data['base'], data['pathtoebook'])
|
||||||
self.current_book_data = data
|
self.current_book_data = data
|
||||||
self.current_book_data['annotations_map'] = defaultdict(list)
|
self.current_book_data['annotations_map'] = defaultdict(list)
|
||||||
self.current_book_data['annotations_path_key'] = path_key(data['pathtoebook']) + '.json'
|
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 {{{
|
# Override network access to load data from the book {{{
|
||||||
|
|
||||||
def set_book_path(path=None):
|
def set_book_path(path, pathtoebook):
|
||||||
if path is not None:
|
set_book_path.pathtoebook = pathtoebook
|
||||||
set_book_path.path = os.path.abspath(path)
|
set_book_path.path = os.path.abspath(path)
|
||||||
set_book_path.metadata = get_data('calibre-book-metadata.json')[0]
|
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.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.metadata = get_data('calibre-book-metadata.json')[0]
|
||||||
set_book_path.parsed_metadata = json_loads(set_book_path.metadata)
|
set_book_path.parsed_metadata = json_loads(set_book_path.metadata)
|
||||||
set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
|
set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
|
||||||
|
|
||||||
|
|
||||||
def get_data(name):
|
def get_data(name):
|
||||||
@ -189,6 +189,7 @@ class ViewerBridge(Bridge):
|
|||||||
update_current_toc_nodes = from_js(object, object)
|
update_current_toc_nodes = from_js(object, object)
|
||||||
toggle_full_screen = from_js()
|
toggle_full_screen = from_js()
|
||||||
report_cfi = from_js(object, object)
|
report_cfi = from_js(object, object)
|
||||||
|
ask_for_open = from_js(object)
|
||||||
|
|
||||||
create_view = to_js()
|
create_view = to_js()
|
||||||
show_preparing_message = to_js()
|
show_preparing_message = to_js()
|
||||||
@ -305,6 +306,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
toggle_bookmarks = pyqtSignal()
|
toggle_bookmarks = pyqtSignal()
|
||||||
update_current_toc_nodes = pyqtSignal(object, object)
|
update_current_toc_nodes = pyqtSignal(object, object)
|
||||||
toggle_full_screen = pyqtSignal()
|
toggle_full_screen = pyqtSignal()
|
||||||
|
ask_for_open = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
self._host_widget = None
|
self._host_widget = None
|
||||||
@ -324,6 +326,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks)
|
self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks)
|
||||||
self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes)
|
self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes)
|
||||||
self.bridge.toggle_full_screen.connect(self.toggle_full_screen)
|
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.bridge.report_cfi.connect(self.call_callback)
|
||||||
self.pending_bridge_ready_actions = {}
|
self.pending_bridge_ready_actions = {}
|
||||||
self.setPage(self._page)
|
self.setPage(self._page)
|
||||||
@ -392,7 +395,7 @@ class WebView(RestartingWebEngineView):
|
|||||||
|
|
||||||
def start_book_load(self, initial_cfi=None):
|
def start_book_load(self, initial_cfi=None):
|
||||||
key = (set_book_path.path,)
|
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):
|
def execute_when_ready(self, action, *args):
|
||||||
if self.bridge.ready:
|
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 book_list.theme import get_color
|
||||||
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
|
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
|
||||||
from modals import error_dialog
|
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.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.font_size import create_font_size_panel
|
||||||
from read_book.prefs.main import create_prefs_panel
|
from read_book.prefs.main import create_prefs_panel
|
||||||
from read_book.toc import create_toc_panel
|
from read_book.toc import create_toc_panel
|
||||||
@ -220,7 +221,10 @@ class MainOverlay:
|
|||||||
back_action = ac(_('Back'), None, self.back, 'arrow-left')
|
back_action = ac(_('Back'), None, self.back, 'arrow-left')
|
||||||
forward_action = ac(_('Forward'), None, self.forward, 'arrow-right')
|
forward_action = ac(_('Forward'), None, self.forward, 'arrow-right')
|
||||||
if runtime.is_standalone_viewer:
|
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)
|
nav_actions = E.ul(back_action, forward_action)
|
||||||
else:
|
else:
|
||||||
reload_actions = E.ul(sync_action, delete_action, reload_action)
|
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)
|
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:
|
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.panels = [DeleteBook(self, _('Are you sure you want to reload this book?'), 'refresh', _('Reload book'), True)]
|
||||||
self.show_current_panel()
|
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):
|
def sync_book(self):
|
||||||
self.hide_current_panel()
|
self.hide_current_panel()
|
||||||
self.panels = [SyncBook(self)]
|
self.panels = [SyncBook(self)]
|
||||||
|
@ -18,6 +18,7 @@ from read_book.globals import (
|
|||||||
current_book, runtime, set_current_spine_item, ui_operations
|
current_book, runtime, set_current_spine_item, ui_operations
|
||||||
)
|
)
|
||||||
from read_book.goto import get_next_section
|
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.overlay import Overlay
|
||||||
from read_book.prefs.colors import resolve_color_scheme
|
from read_book.prefs.colors import resolve_color_scheme
|
||||||
from read_book.prefs.font_size import change_font_size_by
|
from read_book.prefs.font_size import change_font_size_by
|
||||||
@ -382,6 +383,8 @@ class View:
|
|||||||
self.content_popup_overlay.loaded_resources = {}
|
self.content_popup_overlay.loaded_resources = {}
|
||||||
self.timers.start_book(book)
|
self.timers.start_book(book)
|
||||||
self.book = current_book.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:
|
if ui_operations.update_last_read_time:
|
||||||
ui_operations.update_last_read_time(book)
|
ui_operations.update_last_read_time(book)
|
||||||
pos = {'replace_history':True}
|
pos = {'replace_history':True}
|
||||||
|
@ -42,6 +42,7 @@ defaults = {
|
|||||||
'word_actions': v'[]',
|
'word_actions': v'[]',
|
||||||
'standalone_font_settings': {},
|
'standalone_font_settings': {},
|
||||||
'standalone_misc_settings': {},
|
'standalone_misc_settings': {},
|
||||||
|
'standalone_recently_opened': v'[]',
|
||||||
}
|
}
|
||||||
|
|
||||||
is_local_setting = {
|
is_local_setting = {
|
||||||
@ -59,6 +60,7 @@ is_local_setting = {
|
|||||||
'controls_help_shown_count': True,
|
'controls_help_shown_count': True,
|
||||||
'standalone_font_settings': True,
|
'standalone_font_settings': True,
|
||||||
'standalone_misc_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)
|
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
|
nonlocal book
|
||||||
if end_type is 'load':
|
if end_type is 'load':
|
||||||
book = new_book(key, {})
|
book = new_book(key, {})
|
||||||
data = xhr.response
|
data = xhr.response
|
||||||
book.manifest = data[0]
|
book.manifest = data[0]
|
||||||
book.metadata = book.manifest.metadata = data[1]
|
book.metadata = book.manifest.metadata = data[1]
|
||||||
|
book.manifest.pathtoebook = pathtoebook
|
||||||
book.stored_files = {}
|
book.stored_files = {}
|
||||||
book.is_complete = True
|
book.is_complete = True
|
||||||
v'delete book.manifest["metadata"]'
|
v'delete book.manifest["metadata"]'
|
||||||
@ -181,6 +182,7 @@ def create_view(prefs, all_font_families):
|
|||||||
if view is None:
|
if view is None:
|
||||||
create_session_data(prefs)
|
create_session_data(prefs)
|
||||||
view = View(document.getElementById('view'))
|
view = View(document.getElementById('view'))
|
||||||
|
view.overlay.open_book(False)
|
||||||
|
|
||||||
|
|
||||||
@from_python
|
@from_python
|
||||||
@ -189,8 +191,8 @@ def show_preparing_message(msg):
|
|||||||
|
|
||||||
|
|
||||||
@from_python
|
@from_python
|
||||||
def start_book_load(key, initial_cfi):
|
def start_book_load(key, initial_cfi, pathtoebook):
|
||||||
xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi), ok_code=0)
|
xhr = ajax('manifest', manifest_received.bind(None, key, initial_cfi, pathtoebook), ok_code=0)
|
||||||
xhr.responseType = 'json'
|
xhr.responseType = 'json'
|
||||||
xhr.send()
|
xhr.send()
|
||||||
|
|
||||||
@ -257,6 +259,8 @@ if window is window.top:
|
|||||||
to_python.toggle_full_screen()
|
to_python.toggle_full_screen()
|
||||||
ui_operations.report_cfi = def(request_id, data):
|
ui_operations.report_cfi = def(request_id, data):
|
||||||
to_python.report_cfi(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'))
|
document.body.appendChild(E.div(id='view'))
|
||||||
window.onerror = onerror
|
window.onerror = onerror
|
||||||
create_modal_container()
|
create_modal_container()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user