mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Actions to copy text/URL and view image
This commit is contained in:
parent
ec01392e3a
commit
afd483f11a
1
imgsrc/srv/copy.svg
Normal file
1
imgsrc/srv/copy.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1696 384q40 0 68 28t28 68v1216q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-288h-544q-40 0-68-28t-28-68v-672q0-40 20-88t48-76l408-408q28-28 76-48t88-20h416q40 0 68 28t28 68v328q68-40 128-40h416zm-544 213l-299 299h299v-299zm-640-384l-299 299h299v-299zm196 647l316-316v-416h-384v416q0 40-28 68t-68 28h-416v640h512v-256q0-40 20-88t48-76zm956 804v-1152h-384v416q0 40-28 68t-68 28h-416v640h896z"/></svg>
|
After Width: | Height: | Size: 497 B |
1
imgsrc/srv/link.svg
Normal file
1
imgsrc/srv/link.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z"/></svg>
|
After Width: | Height: | Size: 868 B |
@ -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 {{{
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
15
src/pyj/read_book/extract.pyj
Normal file
15
src/pyj/read_book/extract.pyj
Normal file
@ -0,0 +1,15 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
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
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user