mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Two-way syncing work again
This commit is contained in:
parent
5e55554454
commit
561edbe1ea
@ -285,7 +285,7 @@ class RapydScript(Command): # {{{
|
||||
def run(self, opts):
|
||||
from calibre.utils.rapydscript import compile_srv, compile_editor
|
||||
if opts.only_module:
|
||||
locals()['compile_' + opts.only]()
|
||||
locals()['compile_' + opts.only_module]()
|
||||
else:
|
||||
compile_editor()
|
||||
compile_srv()
|
||||
|
@ -6,7 +6,6 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
||||
# TODO:
|
||||
# live css
|
||||
# check that clicking on both internal and external links works
|
||||
# check syncing of position back and forth
|
||||
# check all buttons in preview panel
|
||||
# pass user stylesheet with css for split
|
||||
|
||||
@ -18,9 +17,10 @@ from functools import partial
|
||||
from threading import Thread
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QApplication, QBuffer, QByteArray, QIcon, QMenu, QSize, QTimer, QToolBar, QUrl,
|
||||
QVBoxLayout, QWidget, pyqtSignal, pyqtSlot
|
||||
QApplication, QBuffer, QByteArray, QFile, QIcon, QMenu, QSize, QTimer, QToolBar, QObject,
|
||||
QUrl, QVBoxLayout, QWidget, pyqtSignal, pyqtSlot
|
||||
)
|
||||
from PyQt5.QtWebChannel import QWebChannel
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineView
|
||||
@ -259,7 +259,7 @@ def insert_scripts(profile, *scripts):
|
||||
sc.insert(script)
|
||||
|
||||
|
||||
def create_script(name, src, world=QWebEngineScript.ApplicationWorld, injection_point=QWebEngineScript.DocumentCreation, on_subframes=True):
|
||||
def create_script(name, src, world=QWebEngineScript.ApplicationWorld, injection_point=QWebEngineScript.DocumentReady, on_subframes=True):
|
||||
script = QWebEngineScript()
|
||||
script.setSourceCode(src)
|
||||
script.setName(name)
|
||||
@ -279,8 +279,19 @@ def create_profile():
|
||||
from calibre.utils.rapydscript import compile_editor
|
||||
compile_editor()
|
||||
js = P('editor.js', data=True, allow_user_override=False)
|
||||
js += P('csscolorparser.js', data=True, allow_user_override=False)
|
||||
insert_scripts(ans, create_script('editor.js', js))
|
||||
cparser = P('csscolorparser.js', data=True, allow_user_override=False)
|
||||
qwebchannel_js = QFile(':/qtwebchannel/qwebchannel.js')
|
||||
if not qwebchannel_js.open(QBuffer.ReadOnly):
|
||||
raise RuntimeError(
|
||||
'Failed to load qwebchannel.js with error: %s' % qwebchannel_js.errorString())
|
||||
qwebchannel_js = bytes(qwebchannel_js.readAll()).decode('utf-8')
|
||||
qwebchannel_js += 'window.QWebChannel = QWebChannel;'
|
||||
|
||||
insert_scripts(ans,
|
||||
create_script('qwebchannel.js', qwebchannel_js),
|
||||
create_script('csscolorparser.js', cparser),
|
||||
create_script('editor.js', js),
|
||||
)
|
||||
url_handler = UrlSchemeHandler(ans)
|
||||
ans.installUrlSchemeHandler(QByteArray(FAKE_PROTOCOL.encode('ascii')), url_handler)
|
||||
s = ans.settings()
|
||||
@ -291,15 +302,44 @@ def create_profile():
|
||||
return ans
|
||||
|
||||
|
||||
class WebPage(QWebEnginePage):
|
||||
class Bridge(QObject):
|
||||
|
||||
sync_requested = pyqtSignal(object, object, object)
|
||||
split_requested = pyqtSignal(object, object)
|
||||
go_to_sourceline_address = pyqtSignal(int, 'QStringList')
|
||||
go_to_anchor = pyqtSignal('QString')
|
||||
set_split_mode = pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QObject.__init__(self, parent)
|
||||
|
||||
@pyqtSlot(native_string_type, native_string_type, native_string_type)
|
||||
def request_sync(self, tag_name, href, sourceline_address):
|
||||
try:
|
||||
self.sync_requested.emit(unicode_type(tag_name), unicode_type(href), json.loads(unicode_type(sourceline_address)))
|
||||
except (TypeError, ValueError, OverflowError, AttributeError):
|
||||
pass
|
||||
|
||||
@pyqtSlot(native_string_type, native_string_type)
|
||||
def request_split(self, loc, totals):
|
||||
actions['split-in-preview'].setChecked(False)
|
||||
loc, totals = json.loads(unicode_type(loc)), json.loads(unicode_type(totals))
|
||||
if not loc or not totals:
|
||||
return error_dialog(self.view(), _('Invalid location'),
|
||||
_('Cannot split on the body tag'), show=True)
|
||||
self.split_requested.emit(loc, totals)
|
||||
|
||||
|
||||
class WebPage(QWebEnginePage):
|
||||
|
||||
def __init__(self, parent):
|
||||
QWebEnginePage.__init__(self, create_profile(), parent)
|
||||
secure_webengine(self, for_viewer=True)
|
||||
# TOD: Implement this
|
||||
self.channel = c = QWebChannel(self)
|
||||
self.bridge = Bridge(self)
|
||||
self.setWebChannel(c, QWebEngineScript.ApplicationWorld)
|
||||
c.registerObject('bridge', self.bridge)
|
||||
# TODO: Implement this
|
||||
# css = '[data-in-split-mode="1"] [data-is-block="1"]:hover { cursor: pointer !important; border-top: solid 5px green !important }'
|
||||
|
||||
def javaScriptConsoleMessage(self, level, msg, linenumber, source_id):
|
||||
@ -313,25 +353,8 @@ class WebPage(QWebEnginePage):
|
||||
open_url(url)
|
||||
return False
|
||||
|
||||
@pyqtSlot(native_string_type, native_string_type, native_string_type)
|
||||
def request_sync(self, tag_name, href, sourceline_address):
|
||||
try:
|
||||
self.sync_requested.emit(unicode_type(tag_name), unicode_type(href), json.loads(unicode_type(sourceline_address)))
|
||||
except (TypeError, ValueError, OverflowError, AttributeError):
|
||||
pass
|
||||
|
||||
def go_to_anchor(self, anchor, lnum):
|
||||
self.runjs('window.calibre_preview_integration.go_to_anchor(%s, %s)' % (
|
||||
json.dumps(anchor), json.dumps(unicode_type(lnum))))
|
||||
|
||||
@pyqtSlot(native_string_type, native_string_type)
|
||||
def request_split(self, loc, totals):
|
||||
actions['split-in-preview'].setChecked(False)
|
||||
loc, totals = json.loads(unicode_type(loc)), json.loads(unicode_type(totals))
|
||||
if not loc or not totals:
|
||||
return error_dialog(self.view(), _('Invalid location'),
|
||||
_('Cannot split on the body tag'), show=True)
|
||||
self.split_requested.emit(loc, totals)
|
||||
def go_to_anchor(self, anchor):
|
||||
self.bridge.go_to_anchor.emit(anchor or '')
|
||||
|
||||
def runjs(self, src, callback=None):
|
||||
if callback is None:
|
||||
@ -344,12 +367,10 @@ class WebPage(QWebEnginePage):
|
||||
if lnum is None:
|
||||
return
|
||||
tags = [x.lower() for x in tags]
|
||||
self.runjs('window.calibre_preview_integration.go_to_sourceline_address(%d, %s)' % (lnum, json.dumps(tags)))
|
||||
self.bridge.go_to_sourceline_address.emit(lnum, tags)
|
||||
|
||||
def split_mode(self, enabled):
|
||||
self.runjs(
|
||||
'window.calibre_preview_integration.split_mode(%s)' % (
|
||||
'true' if enabled else 'false'))
|
||||
self.bridge.set_split_mode.emit(1 if enabled else 0)
|
||||
|
||||
|
||||
class WebView(QWebEngineView):
|
||||
@ -427,8 +448,8 @@ class Preview(QWidget):
|
||||
self.setLayout(l)
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
self.view = WebView(self)
|
||||
self.view._page.sync_requested.connect(self.request_sync)
|
||||
self.view._page.split_requested.connect(self.request_split)
|
||||
self.view._page.bridge.sync_requested.connect(self.request_sync)
|
||||
self.view._page.bridge.split_requested.connect(self.request_split)
|
||||
self.view._page.loadFinished.connect(self.load_finished)
|
||||
self.inspector = self.view.inspector
|
||||
l.addWidget(self.view)
|
||||
@ -502,7 +523,7 @@ class Preview(QWidget):
|
||||
else:
|
||||
name = c.href_to_name(href, self.current_name) if href else None
|
||||
if name == self.current_name:
|
||||
return self.view._page.go_to_anchor(urlparse(href).fragment, lnum)
|
||||
return self.view._page.go_to_anchor(urlparse(href).fragment)
|
||||
if name and c.exists(name) and c.mime_map[name] in OEB_DOCS:
|
||||
return self.link_clicked.emit(name, urlparse(href).fragment or TOP)
|
||||
self.sync_requested.emit(self.current_name, lnum)
|
||||
|
@ -246,6 +246,9 @@ class PreviewIntegration:
|
||||
def __init__(self):
|
||||
self.blocks_found = False
|
||||
self.in_split_mode = False
|
||||
if window is window.top:
|
||||
setTimeout(self.connect_channel, 10)
|
||||
window.document.body.addEventListener('click', self.onclick, True)
|
||||
|
||||
def go_to_line(self, lnum):
|
||||
for node in document.querySelectorAll(f'[data-lnum="{lnum}"]'):
|
||||
@ -282,7 +285,7 @@ class PreviewIntegration:
|
||||
elem.setAttribute('data-is-block', '1')
|
||||
self.blocks_found = True
|
||||
|
||||
def split_mode(self, enabled):
|
||||
def set_split_mode(self, enabled):
|
||||
self.in_split_mode = enabled
|
||||
document.body.setAttribute('data-in-split-mode', '1' if enabled else '0')
|
||||
if enabled:
|
||||
@ -303,10 +306,15 @@ class PreviewIntegration:
|
||||
parent = parent.parentNode
|
||||
loc.reverse()
|
||||
totals.reverse()
|
||||
window.py_bridge.request_split(JSON.stringify(loc), JSON.stringify(totals))
|
||||
self.bridge.request_split(JSON.stringify(loc), JSON.stringify(totals))
|
||||
|
||||
def onload(self):
|
||||
window.document.body.addEventListener('click', this.onclick, True)
|
||||
def connect_channel(self):
|
||||
self.qwebchannel = new window.QWebChannel(window.qt.webChannelTransport, def(channel):
|
||||
self.bridge = channel.objects.bridge
|
||||
self.bridge.go_to_sourceline_address.connect(self.go_to_sourceline_address)
|
||||
self.bridge.go_to_anchor.connect(self.go_to_anchor)
|
||||
self.bridge.set_split_mode.connect(self.set_split_mode)
|
||||
)
|
||||
|
||||
def onclick(self, event):
|
||||
event.preventDefault()
|
||||
@ -321,17 +329,17 @@ class PreviewIntegration:
|
||||
tn = e.tagName?.toLowerCase()
|
||||
href = e.getAttribute('href')
|
||||
e = e.parentNode
|
||||
window.py_bridge.request_sync(tn, href, JSON.stringify(address))
|
||||
self.bridge.request_sync(tn, href, JSON.stringify(address))
|
||||
return False
|
||||
|
||||
def go_to_anchor(self, anchor, lnum):
|
||||
def go_to_anchor(self, anchor):
|
||||
elem = document.getElementById(anchor)
|
||||
if not elem:
|
||||
elem = document.querySelector(f'[name="{anchor}"]')
|
||||
if elem:
|
||||
elem.scrollIntoView()
|
||||
address = get_sourceline_address(elem)
|
||||
window.py_bridge.request_sync('', '', address)
|
||||
self.bridge.request_sync('', '', address)
|
||||
|
||||
def live_css(self, sourceline, tags):
|
||||
target = None
|
||||
@ -360,5 +368,4 @@ class PreviewIntegration:
|
||||
return JSON.stringify(ans)
|
||||
|
||||
|
||||
window.calibre_preview_integration = PreviewIntegration()
|
||||
window.onload = window.calibre_preview_integration.onload
|
||||
calibre_preview_integration = PreviewIntegration()
|
||||
|
Loading…
x
Reference in New Issue
Block a user