use a channel for bridge

acceptNavRequest was not being called for rapid updates, so it's not
usable
This commit is contained in:
Damien Elmes 2016-06-06 15:50:03 +10:00
parent 913f22a9f0
commit 9abc9fde9f
8 changed files with 82 additions and 72 deletions

View File

@ -1383,7 +1383,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
frm.fields.addItems(fields) frm.fields.addItems(fields)
self._dupesButton = None self._dupesButton = None
# links # links
frm.webView.onAnkiLink = self.dupeLinkClicked frm.webView.onBridgeCmd = self.dupeLinkClicked
def onFin(code): def onFin(code):
saveGeom(d, "findDupes") saveGeom(d, "findDupes")
d.finished.connect(onFin) d.finished.connect(onFin)
@ -1410,7 +1410,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
t += _("Found %(a)s across %(b)s.") % dict(a=part1, b=part2) t += _("Found %(a)s across %(b)s.") % dict(a=part1, b=part2)
t += "<p><ol>" t += "<p><ol>"
for val, nids in res: for val, nids in res:
t += '''<li><a href=# onclick="openAnkiLink('%s')">%s</a>: %s</a>''' % ( t += '''<li><a href=# onclick="pycmd('%s')">%s</a>: %s</a>''' % (
"nid:" + ",".join(str(id) for id in nids), "nid:" + ",".join(str(id) for id in nids),
ngettext("%d note", "%d notes", len(nids)) % len(nids), ngettext("%d note", "%d notes", len(nids)) % len(nids),
cgi.escape(val)) cgi.escape(val))
@ -1693,11 +1693,11 @@ class BrowserToolbar(Toolbar):
def borderImg(link, icon, on, title, tooltip=None): def borderImg(link, icon, on, title, tooltip=None):
if on: if on:
fmt = '''\ fmt = '''\
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')">\ <a class=hitem title="%s" href=# onclick="pycmd('%s')">\
<img valign=bottom style='border: 1px solid #aaa;' src="qrc:/icons/%s.png"> %s</a>''' <img valign=bottom style='border: 1px solid #aaa;' src="qrc:/icons/%s.png"> %s</a>'''
else: else:
fmt = '''\ fmt = '''\
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')"><img style="padding: 1px;" valign=bottom src="qrc:/icons/%s.png"> %s</a>''' <a class=hitem title="%s" href=# onclick="pycmd('%s')"><img style="padding: 1px;" valign=bottom src="qrc:/icons/%s.png"> %s</a>'''
return fmt % (tooltip or title, link, icon, title) return fmt % (tooltip or title, link, icon, title)
right = "<div>" right = "<div>"
right += borderImg("add", "add16", False, _("Add"), right += borderImg("add", "add16", False, _("Add"),

View File

@ -22,7 +22,7 @@ class DeckBrowser(object):
def show(self): def show(self):
clearAudioQueue() clearAudioQueue()
self.web.resetHandlers() self.web.resetHandlers()
self.web.onAnkiLink = self._linkHandler self.web.onBridgeCmd = self._linkHandler
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self._renderPage() self._renderPage()
@ -132,7 +132,7 @@ body { margin: 1em; -webkit-user-select: none; }
var draggedDeckId = ui.draggable.attr('id'); var draggedDeckId = ui.draggable.attr('id');
var ontoDeckId = $(this).attr('id'); var ontoDeckId = $(this).attr('id');
openAnkiLink("drag:" + draggedDeckId + "," + ontoDeckId); pycmd("drag:" + draggedDeckId + "," + ontoDeckId);
} }
</script> </script>
""" """
@ -172,9 +172,9 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
return "" return ""
return "<br><div style='width:50%;border: 1px solid #000;padding:5px;'>"+( return "<br><div style='width:50%;border: 1px solid #000;padding:5px;'>"+(
_("You have a lot of decks. Please see %(a)s. %(b)s") % dict( _("You have a lot of decks. Please see %(a)s. %(b)s") % dict(
a=("<a href=# onclick='openAnkiLink('lots')>%s</a>" % _( a=("<a href=# onclick='pycmd('lots')>%s</a>" % _(
"this page")), "this page")),
b=("<br><small><a href=# onclick='openAnkiLink(\"hidelots\")'>(" b=("<br><small><a href=# onclick='pycmd(\"hidelots\")'>("
"%s)</a></small>" % (_("hide"))+ "%s)</a></small>" % (_("hide"))+
"</div"))) "</div")))
@ -220,7 +220,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
buf = "<tr class='%s' id='%d'>" % (klass, did) buf = "<tr class='%s' id='%d'>" % (klass, did)
# deck link # deck link
if children: if children:
collapse = "<a class=collapse href=# onclick='openAnkiLink(\"collapse:%d\")'>%s</a>" % (did, prefix) collapse = "<a class=collapse href=# onclick='pycmd(\"collapse:%d\")'>%s</a>" % (did, prefix)
else: else:
collapse = "<span class=collapse></span>" collapse = "<span class=collapse></span>"
if deck['dyn']: if deck['dyn']:
@ -230,7 +230,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
buf += """ buf += """
<td class=decktd colspan=5>%s%s<a class="deck %s" <td class=decktd colspan=5>%s%s<a class="deck %s"
href=# onclick="openAnkiLink('open:%d')">%s</a></td>"""% ( href=# onclick="pycmd('open:%d')">%s</a></td>"""% (
indent(), collapse, extraclass, did, name) indent(), collapse, extraclass, did, name)
# due counts # due counts
def nonzeroColour(cnt, colour): def nonzeroColour(cnt, colour):
@ -243,7 +243,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
nonzeroColour(due, "#007700"), nonzeroColour(due, "#007700"),
nonzeroColour(new, "#000099")) nonzeroColour(new, "#000099"))
# options # options
buf += ("<td align=center class=opts><a onclick='openAnkiLink(\"opts:%d\");'>" buf += ("<td align=center class=opts><a onclick='pycmd(\"opts:%d\");'>"
"<img valign=right src='qrc:/icons/gears.png'></a></td></tr>" % did) "<img valign=right src='qrc:/icons/gears.png'></a></td></tr>" % did)
# children # children
buf += self._renderDeckTree(children, depth+1) buf += self._renderDeckTree(children, depth+1)
@ -347,7 +347,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
if b[0]: if b[0]:
b[0] = _("Shortcut key: %s") % shortcut(b[0]) b[0] = _("Shortcut key: %s") % shortcut(b[0])
buf += """ buf += """
<button title='%s' onclick='openAnkiLink(\"%s\");'>%s</button>""" % tuple(b) <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b)
self.bottom.draw(buf) self.bottom.draw(buf)
if isMac: if isMac:
size = 28 size = 28
@ -355,7 +355,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
size = 36 + self.mw.fontHeightDelta*3 size = 36 + self.mw.fontHeightDelta*3
self.bottom.web.setFixedHeight(size) self.bottom.web.setFixedHeight(size)
self.bottom.web.resetHandlers() self.bottom.web.resetHandlers()
self.bottom.web.onAnkiLink = self._linkHandler self.bottom.web.onBridgeCmd = self._linkHandler
def _onShared(self): def _onShared(self):
openLink(aqt.appShared+"decks/") openLink(aqt.appShared+"decks/")

View File

@ -456,7 +456,7 @@ the manual for information on how to restore from an automatic backup."))
# we don't have to change the webview, as we have a covering window # we don't have to change the webview, as we have a covering window
return return
self.web.resetHandlers() self.web.resetHandlers()
self.web.onAnkiLink = lambda url: self.delayedMaybeReset() self.web.onBridgeCmd = lambda url: self.delayedMaybeReset()
i = _("Waiting for editing to finish.") i = _("Waiting for editing to finish.")
b = self.button("refresh", _("Resume Now"), id="resume") b = self.button("refresh", _("Resume Now"), id="resume")
self.web.stdHtml(""" self.web.stdHtml("""
@ -487,7 +487,7 @@ h1 { margin-bottom: 0.2em; }
else: else:
key = "" key = ""
return ''' return '''
<button id="%s" class="%s" onclick="openAnkiLink('%s');return false;" <button id="%s" class="%s" onclick="pycmd('%s');return false;"
title="%s" %s>%s</button>''' % ( title="%s" %s>%s</button>''' % (
id, class_, link, key, extra, name) id, class_, link, key, extra, name)

View File

@ -18,7 +18,7 @@ class Overview(object):
def show(self): def show(self):
clearAudioQueue() clearAudioQueue()
self.web.resetHandlers() self.web.resetHandlers()
self.web.onAnkiLink = self._linkHandler self.web.onBridgeCmd = self._linkHandler
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self.refresh() self.refresh()
@ -199,14 +199,14 @@ text-align: center;
if b[0]: if b[0]:
b[0] = _("Shortcut key: %s") % shortcut(b[0]) b[0] = _("Shortcut key: %s") % shortcut(b[0])
buf += """ buf += """
<button title="%s" onclick='openAnkiLink("%s")'>%s</button>""" % tuple(b) <button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(b)
self.bottom.draw(buf) self.bottom.draw(buf)
if isMac: if isMac:
size = 28 size = 28
else: else:
size = 36 + self.mw.fontHeightDelta*3 size = 36 + self.mw.fontHeightDelta*3
self.bottom.web.setFixedHeight(size) self.bottom.web.setFixedHeight(size)
self.bottom.web.onAnkiLink = self._linkHandler self.bottom.web.onBridgeCmd = self._linkHandler
# Studying more # Studying more
###################################################################### ######################################################################

View File

@ -11,12 +11,7 @@ os.environ["LIBOVERLAY_SCROLLBAR"] = "0"
from anki.utils import isWin, isMac from anki.utils import isWin, isMac
from PyQt5.QtCore import * from PyQt5.Qt import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
def debug(): def debug():
from PyQt5.QtCore import pyqtRemoveInputHook from PyQt5.QtCore import pyqtRemoveInputHook

View File

@ -43,14 +43,14 @@ class Reviewer(object):
self.mw.col.reset() self.mw.col.reset()
self.web.resetHandlers() self.web.resetHandlers()
self.mw.keyHandler = self._keyHandler self.mw.keyHandler = self._keyHandler
self.web.onAnkiLink = self._linkHandler self.web.onBridgeCmd = self._linkHandler
self.web.setKeyHandler(self._catchEsc) self.web.setKeyHandler(self._catchEsc)
if isMac: if isMac:
self.bottom.web.setFixedHeight(46) self.bottom.web.setFixedHeight(46)
else: else:
self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4) self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4)
self.bottom.web.resetHandlers() self.bottom.web.resetHandlers()
self.bottom.web.onAnkiLink = self._linkHandler self.bottom.web.onBridgeCmd = self._linkHandler
self._reps = None self._reps = None
self.nextCard() self.nextCard()
@ -163,12 +163,12 @@ function _toggleStar (show) {
function _getTypedText () { function _getTypedText () {
if (typeans) { if (typeans) {
openAnkiLink("typeans:"+typeans.value); pycmd("typeans:"+typeans.value);
} }
}; };
function _typeAnsPress() { function _typeAnsPress() {
if (window.event.keyCode === 13) { if (window.event.keyCode === 13) {
openAnkiLink("ans"); pycmd("ans");
} }
} }
</script> </script>
@ -533,12 +533,12 @@ min-width: 60px; white-space: nowrap;
<tr> <tr>
<td align=left width=50 valign=top class=stat> <td align=left width=50 valign=top class=stat>
<br> <br>
<button title="%(editkey)s" onclick="openAnkiLink('edit');">%(edit)s</button></td> <button title="%(editkey)s" onclick="pycmd('edit');">%(edit)s</button></td>
<td align=center valign=top id=middle> <td align=center valign=top id=middle>
</td> </td>
<td width=50 align=right valign=top class=stat><span id=time class=stattxt> <td width=50 align=right valign=top class=stat><span id=time class=stattxt>
</span><br> </span><br>
<button onclick="openAnkiLink('more');">%(more)s %(downArrow)s</button> <button onclick="pycmd('more');">%(more)s %(downArrow)s</button>
</td> </td>
</tr> </tr>
</table> </table>
@ -596,7 +596,7 @@ function showAnswer(txt) {
self.bottom.web.setFocus() self.bottom.web.setFocus()
middle = ''' middle = '''
<span class=stattxt>%s</span><br> <span class=stattxt>%s</span><br>
<button title="%s" id=ansbut onclick='openAnkiLink("ans");'>%s</button>''' % ( <button title="%s" id=ansbut onclick='pycmd("ans");'>%s</button>''' % (
self._remaining(), _("Shortcut key: %s") % _("Space"), _("Show Answer")) self._remaining(), _("Shortcut key: %s") % _("Space"), _("Show Answer"))
# wrap it in a table so it has the same top margin as the ease buttons # wrap it in a table so it has the same top margin as the ease buttons
middle = "<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>" % middle middle = "<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>" % middle
@ -654,7 +654,7 @@ function showAnswer(txt) {
extra = "" extra = ""
due = self._buttonTime(i) due = self._buttonTime(i)
return ''' return '''
<td align=center>%s<button %s title="%s" onclick='openAnkiLink("ease%d");'>\ <td align=center>%s<button %s title="%s" onclick='pycmd("ease%d");'>\
%s</button></td>''' % (due, extra, _("Shortcut key: %s") % i, i, label) %s</button></td>''' % (due, extra, _("Shortcut key: %s") % i, i, label)
buf = "<center><table cellpading=0 cellspacing=0><tr>" buf = "<center><table cellpading=0 cellspacing=0><tr>"
for ease, label in self._answerButtonList(): for ease, label in self._answerButtonList():

View File

@ -10,7 +10,7 @@ class Toolbar(object):
self.mw = mw self.mw = mw
self.web = web self.web = web
self.web.resetHandlers() self.web.resetHandlers()
self.web.onAnkiLink = self._linkHandler self.web.onBridgeCmd = self._linkHandler
self.link_handlers = { self.link_handlers = {
"decks": self._deckLinkHandler, "decks": self._deckLinkHandler,
"study": self._studyLinkHandler, "study": self._studyLinkHandler,
@ -51,7 +51,7 @@ class Toolbar(object):
buf = "" buf = ""
for ln, name, title in links: for ln, name, title in links:
buf += ''' buf += '''
<a class=hitem title="%s" href=# onclick="openAnkiLink('%s')">%s</a>''' % ( <a class=hitem title="%s" href=# onclick="pycmd('%s')">%s</a>''' % (
title, ln, name) title, ln, name)
buf += "&nbsp;"*3 buf += "&nbsp;"*3
return buf return buf
@ -60,7 +60,7 @@ class Toolbar(object):
buf = "" buf = ""
for ln, icon, title in self._rightIconsList(): for ln, icon, title in self._rightIconsList():
buf += ''' buf += '''
<a class=hitem title="%s" href=# onclick='openAnkiLink("%s")'><img width="16px" height="16px" src="%s"></a>''' % ( <a class=hitem title="%s" href=# onclick='pycmd("%s")'><img width="16px" height="16px" src="%s"></a>''' % (
title, ln, icon) title, ln, icon)
return buf return buf

View File

@ -14,16 +14,53 @@ import anki.js
class AnkiWebPage(QWebEnginePage): class AnkiWebPage(QWebEnginePage):
def __init__(self, jsErr, acceptNavReq): def __init__(self, onBridgeCmd):
QWebEnginePage.__init__(self) QWebEnginePage.__init__(self)
self._jsErr = jsErr self._onBridgeCmd = onBridgeCmd
self._acceptNavReq = acceptNavReq self._setupBridge()
def _setupBridge(self):
class Bridge(QObject):
@pyqtSlot(str)
def cmd(self, str):
self.onCmd(str)
self._bridge = Bridge()
self._bridge.onCmd = self._onCmd
self._channel = QWebChannel(self)
self._channel.registerObject("py", self._bridge)
self.setWebChannel(self._channel)
js = QFile(':/qtwebchannel/qwebchannel.js')
assert js.open(QIODevice.ReadOnly)
js = bytes(js.readAll()).decode('utf-8')
script = QWebEngineScript()
script.setSourceCode(js + '''
var pycmd;
new QWebChannel(qt.webChannelTransport, function(channel) {
pycmd = channel.objects.py.cmd;
pycmd("domDone");
});
''')
script.setWorldId(QWebEngineScript.MainWorld)
script.setInjectionPoint(QWebEngineScript.DocumentReady)
script.setRunsOnSubFrames(False)
self.profile().scripts().insert(script)
def javaScriptConsoleMessage(self, lvl, msg, line, srcID): def javaScriptConsoleMessage(self, lvl, msg, line, srcID):
self._jsErr(lvl, msg, line, srcID) sys.stdout.write(
(_("JS error on line %(a)d: %(b)s") %
dict(a=line, b=msg+"\n")))
def acceptNavigationRequest(self, url, navType, isMainFrame): def acceptNavigationRequest(self, url, navType, isMainFrame):
return self._acceptNavReq(url, navType, isMainFrame) # load all other links in browser
openLink(url)
return False
def _onCmd(self, str):
self._onBridgeCmd(str)
# Main web view # Main web view
########################################################################## ##########################################################################
@ -33,7 +70,8 @@ class AnkiWebView(QWebEngineView):
def __init__(self, canFocus=True): def __init__(self, canFocus=True):
QWebEngineView.__init__(self) QWebEngineView.__init__(self)
self.setObjectName("mainText") self.setObjectName("mainText")
self._page = AnkiWebPage(self._jsErr, self._acceptNavReq) self._page = AnkiWebPage(self._onBridgeCmd)
self._loadFinishedCB = None self._loadFinishedCB = None
self.setPage(self._page) self.setPage(self._page)
self.resetHandlers() self.resetHandlers()
@ -104,14 +142,6 @@ button {
%s</style> %s</style>
<script> <script>
%s %s
openAnkiLink = function(txt) {
window.location = "http://anki/"+txt;
}
document.addEventListener("DOMContentLoaded", function(event) {
openAnkiLink("domDone");
});
</script> </script>
%s %s
@ -133,34 +163,19 @@ document.addEventListener("DOMContentLoaded", function(event) {
def _openLinksExternally(self, url): def _openLinksExternally(self, url):
openLink(url) openLink(url)
def _jsErr(self, lvl, msg, line, srcID): def _onBridgeCmd(self, cmd):
sys.stdout.write( if cmd == "domDone":
(_("JS error on line %(a)d: %(b)s") %
dict(a=line, b=msg+"\n")))
def _acceptNavReq(self, url, navType, isMainFrame):
# is it an anki link?
urlstr = url.toString()
#print("got url",urlstr)
prefix = "http://anki/"
if urlstr.startswith(prefix):
urlstr = urlstr[len(prefix):]
if urlstr == "domDone":
self.onLoadFinished() self.onLoadFinished()
else: else:
self.onAnkiLink(urlstr) self.onBridgeCmd(cmd)
return False
# load all other links in browser
openLink(url)
return False
def defaultOnAnkiLink(self, link): def defaultOnBridgeCmd(self, cmd):
print("unhandled anki link:", link) print("unhandled bridge cmd:", cmd)
def defaultOnLoadFinished(self): def defaultOnLoadFinished(self):
pass pass
def resetHandlers(self): def resetHandlers(self):
self.setKeyHandler(None) self.setKeyHandler(None)
self.onAnkiLink = self.defaultOnAnkiLink self.onBridgeCmd = self.defaultOnBridgeCmd
self.onLoadFinished = self.defaultOnLoadFinished self.onLoadFinished = self.defaultOnLoadFinished