Two-way syncing work again

This commit is contained in:
Kovid Goyal 2018-07-27 21:57:16 +05:30
parent 5e55554454
commit 561edbe1ea
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 72 additions and 44 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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()