diff --git a/imgsrc/srv/copy.svg b/imgsrc/srv/copy.svg
new file mode 100644
index 0000000000..86e0d1eedc
--- /dev/null
+++ b/imgsrc/srv/copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/imgsrc/srv/link.svg b/imgsrc/srv/link.svg
new file mode 100644
index 0000000000..6999b4722d
--- /dev/null
+++ b/imgsrc/srv/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py
index c4b7acbada..6f9151e7db 100644
--- a/src/calibre/gui2/viewer/ui.py
+++ b/src/calibre/gui2/viewer/ui.py
@@ -12,13 +12,15 @@ from hashlib import sha256
from threading import Thread
from PyQt5.Qt import (
- QDockWidget, QEvent, QModelIndex, Qt, QVBoxLayout, QWidget, pyqtSignal
+ QDockWidget, QEvent, QModelIndex, QPixmap, Qt, QUrl, QVBoxLayout, QWidget,
+ pyqtSignal
)
from calibre import prints
from calibre.constants import config_dir
from calibre.customize.ui import available_input_formats
from calibre.gui2 import choose_files, error_dialog
+from calibre.gui2.image_popup import ImagePopup
from calibre.gui2.main_window import MainWindow
from calibre.gui2.viewer.annotations import (
merge_annotations, parse_annotations, save_annots_to_epub, serialize_annotations
@@ -28,7 +30,7 @@ from calibre.gui2.viewer.convert_book import prepare_book, update_book
from calibre.gui2.viewer.lookup import Lookup
from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView
from calibre.gui2.viewer.web_view import (
- WebView, get_session_pref, set_book_path, vprefs
+ WebView, get_path_for_name, get_session_pref, set_book_path, vprefs
)
from calibre.utils.date import utcnow
from calibre.utils.ipc.simple_worker import WorkerError
@@ -68,6 +70,7 @@ class EbookViewer(MainWindow):
self.base_window_title = _('E-book viewer')
self.setWindowTitle(self.base_window_title)
self.in_full_screen_mode = None
+ self.image_popup = ImagePopup(self)
try:
os.makedirs(annotations_dir)
except EnvironmentError:
@@ -119,6 +122,7 @@ class EbookViewer(MainWindow):
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.web_view.selection_changed.connect(self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
+ self.web_view.view_image.connect(self.view_image, type=Qt.QueuedConnection)
self.setCentralWidget(self.web_view)
self.restore_state()
if continue_reading:
@@ -188,6 +192,21 @@ class EbookViewer(MainWindow):
def bookmark_activated(self, cfi):
self.web_view.goto_cfi(cfi)
+
+ def view_image(self, name):
+ path = get_path_for_name(name)
+ if path:
+ pmap = QPixmap()
+ if pmap.load(path):
+ self.image_popup.current_img = pmap
+ self.image_popup.current_url = QUrl.fromLocalFile(path)
+ self.image_popup()
+ else:
+ error_dialog(self, _('Invalid image'), _(
+ "Failed to load the image {}").format(name), show=True)
+ else:
+ error_dialog(self, _('Image not found'), _(
+ "Failed to find the image {}").format(name), show=True)
# }}}
# Load book {{{
diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py
index 90449a34c6..5913f1fe18 100644
--- a/src/calibre/gui2/viewer/web_view.py
+++ b/src/calibre/gui2/viewer/web_view.py
@@ -57,12 +57,18 @@ def set_book_path(path, pathtoebook):
set_book_path.parsed_manifest = json_loads(set_book_path.manifest)
-def get_data(name):
+def get_path_for_name(name):
bdir = getattr(set_book_path, 'path', None)
if bdir is None:
- return None, None
+ return
path = os.path.abspath(os.path.join(bdir, name))
- if not path.startswith(bdir):
+ if path.startswith(bdir):
+ return path
+
+
+def get_data(name):
+ path = get_path_for_name(name)
+ if path is None:
return None, None
try:
with lopen(path, 'rb') as f:
@@ -195,6 +201,8 @@ class ViewerBridge(Bridge):
report_cfi = from_js(object, object)
ask_for_open = from_js(object)
selection_changed = from_js(object)
+ copy_selection = from_js(object)
+ view_image = from_js(object)
create_view = to_js()
show_preparing_message = to_js()
@@ -245,6 +253,13 @@ class WebPage(QWebEnginePage):
secure_webengine(self, for_viewer=True)
apply_font_settings(self)
self.bridge = ViewerBridge(self)
+ self.bridge.copy_selection.connect(self.trigger_copy)
+
+ def trigger_copy(self, what):
+ if what:
+ QApplication.instance().clipboard().setText(what)
+ else:
+ self.triggerAction(self.Copy)
def javaScriptConsoleMessage(self, level, msg, linenumber, source_id):
if level >= QWebEnginePage.ErrorMessageLevel and source_id == 'userscript:viewer.js':
@@ -319,6 +334,7 @@ class WebView(RestartingWebEngineView):
toggle_full_screen = pyqtSignal()
ask_for_open = pyqtSignal(object)
selection_changed = pyqtSignal(object)
+ view_image = pyqtSignal(object)
def __init__(self, parent=None):
self._host_widget = None
@@ -342,6 +358,7 @@ class WebView(RestartingWebEngineView):
self.bridge.toggle_full_screen.connect(self.toggle_full_screen)
self.bridge.ask_for_open.connect(self.ask_for_open)
self.bridge.selection_changed.connect(self.selection_changed)
+ self.bridge.view_image.connect(self.view_image)
self.bridge.report_cfi.connect(self.call_callback)
self.pending_bridge_ready_actions = {}
self.setPage(self._page)
diff --git a/src/calibre/srv/render_book.py b/src/calibre/srv/render_book.py
index 12f23bba9c..30f566ef23 100644
--- a/src/calibre/srv/render_book.py
+++ b/src/calibre/srv/render_book.py
@@ -315,6 +315,7 @@ class Container(ContainerBase):
resource_template = link_uid + '|{}|'
xlink_xpath = XPath('//*[@xl:href]')
link_xpath = XPath('//h:a[@href]')
+ img_xpath = XPath('//h:img[@src]')
res_link_xpath = XPath('//h:link[@href]')
def link_replacer(base, url):
@@ -354,6 +355,9 @@ class Container(ContainerBase):
elif mt in OEB_DOCS:
self.virtualized_names.add(name)
root = self.parsed(name)
+ for img in img_xpath(root):
+ img.set('data-calibre-src', self.href_to_name(img.get('src'), name))
+ changed.add(name)
for link in res_link_xpath(root):
ltype = (link.get('type') or 'text/css').lower()
rel = (link.get('rel') or 'stylesheet').lower()
diff --git a/src/pyj/read_book/extract.pyj b/src/pyj/read_book/extract.pyj
new file mode 100644
index 0000000000..20a632dccb
--- /dev/null
+++ b/src/pyj/read_book/extract.pyj
@@ -0,0 +1,15 @@
+# vim:fileencoding=utf-8
+# License: GPL v3 Copyright: 2019, Kovid Goyal
+from __python__ import bound_methods, hash_literals
+
+
+
+def get_elements(x, y):
+ nonlocal img_id_counter
+ ans = {'link': None, 'img': None}
+ for elem in document.elementsFromPoint(x, y):
+ if elem.tagName.toLowerCase() is 'a' and elem.getAttribute('href') and not ans.link:
+ ans.link = elem.getAttribute('href')
+ elif elem.tagName.toLowerCase() is 'img' and elem.getAttribute('data-calibre-src') and not ans.img:
+ ans.img = elem.getAttribute('data-calibre-src')
+ return ans
diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj
index 1bef85e35b..a27ce96ad3 100644
--- a/src/pyj/read_book/iframe.pyj
+++ b/src/pyj/read_book/iframe.pyj
@@ -7,6 +7,7 @@ from gettext import gettext as _
from iframe_comm import IframeClient
from read_book.cfi import at_current, scroll_to as scroll_to_cfi
+from read_book.extract import get_elements
from read_book.flow_mode import (
anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction,
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
@@ -91,7 +92,7 @@ class IframeBoss:
'find': self.find,
'window_size': self.received_window_size,
'get_current_cfi': self.get_current_cfi,
- 'set_forward_keypresses': self.set_forward_keypresses
+ 'set_forward_keypresses': self.set_forward_keypresses,
}
self.comm = IframeClient(handlers)
self.last_window_ypos = 0
@@ -370,7 +371,7 @@ class IframeBoss:
def oncontextmenu(self, evt):
if self.content_ready:
evt.preventDefault()
- self.send_message('show_chrome')
+ self.send_message('show_chrome', elements=get_elements(evt.clientX, evt.clientY))
def send_message(self, action, **data):
self.comm.send_message(action, data)
diff --git a/src/pyj/read_book/overlay.pyj b/src/pyj/read_book/overlay.pyj
index 430f73e6cb..d22798b89b 100644
--- a/src/pyj/read_book/overlay.pyj
+++ b/src/pyj/read_book/overlay.pyj
@@ -203,8 +203,9 @@ def simple_overlay_title(title, overlay, container):
class MainOverlay: # {{{
- def __init__(self, overlay):
+ def __init__(self, overlay, elements):
self.overlay = overlay
+ self.elements = elements or {}
self.timer = None
self.timer_id = unique_id()
if window.Intl?.DateTimeFormat:
@@ -286,11 +287,26 @@ class MainOverlay: # {{{
ac(_('Lookup/search word'), _('Lookup or search for the currently selected word'),
def(): self.overlay.hide(), ui_operations.toggle_lookup();, 'library')
))
+ copy_actions = E.ul()
+ if self.overlay.view.currently_showing.selected_text:
+ copy_actions.appendChild(ac(_('Copy selection'), _('Copy the current selection'), def():
+ self.overlay.hide(), ui_operations.copy_selection()
+ , 'copy'))
+ if self.elements.link:
+ copy_actions.appendChild(ac(_('Copy link'), _('Copy the current link'), def():
+ self.overlay.hide(), ui_operations.copy_selection(self.elements.link)
+ , 'link'))
+ if self.elements.img:
+ copy_actions.appendChild(ac(_('View image'), _('View the current image'), def():
+ self.overlay.hide(), ui_operations.view_image(self.elements.img)
+ , 'image'))
+ if copy_actions.childNodes.length:
+ actions_div.appendChild(copy_actions)
+
actions_div.appendChild(E.ul(
ac(_('Inspector'), _('Show the content inspector'),
def(): self.overlay.hide(), ui_operations.toggle_inspector();, 'bug')
))
-
container.appendChild(set_css(E.div(class_=MAIN_OVERLAY_TS_CLASS, # top section
onclick=def (evt):evt.stopPropagation();,
@@ -504,8 +520,8 @@ class Overlay:
self.panels[-1].show(c)
self.update_visibility()
- def show(self):
- self.panels = [MainOverlay(self)]
+ def show(self, elements):
+ self.panels = [MainOverlay(self, elements)]
self.show_current_panel()
def hide(self):
diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj
index 0266247524..61621c6cc1 100644
--- a/src/pyj/read_book/view.pyj
+++ b/src/pyj/read_book/view.pyj
@@ -349,14 +349,17 @@ class View:
def focus_iframe(self):
self.iframe.contentWindow.focus()
- def show_chrome(self):
+ def show_chrome(self, data):
self.show_chrome_counter += 1
- self.get_current_cfi('show-chrome-' + self.show_chrome_counter, self.do_show_chrome)
+ elements = {}
+ if data and data.elements:
+ elements = data.elements
+ self.get_current_cfi('show-chrome-' + self.show_chrome_counter, self.do_show_chrome.bind(None, elements))
- def do_show_chrome(self, request_id, cfi_data):
+ def do_show_chrome(self, elements, request_id, cfi_data):
self.hide_overlays()
self.update_cfi_data(cfi_data)
- self.overlay.show()
+ self.overlay.show(elements)
def show_search(self):
self.hide_overlays()
diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj
index c1283c7f0c..9a2be20f14 100644
--- a/src/pyj/viewer-main.pyj
+++ b/src/pyj/viewer-main.pyj
@@ -284,6 +284,10 @@ if window is window.top:
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)
document.body.appendChild(E.div(id='view'))
window.onerror = onerror