mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 23:42:23 -04:00
add js/py bridge, jquery
This commit is contained in:
parent
d948b00c54
commit
0a4efd018f
5 changed files with 156 additions and 63 deletions
|
@ -9,15 +9,48 @@ from anki import Deck
|
||||||
from anki.utils import fmtTimeSpan
|
from anki.utils import fmtTimeSpan
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
_css = """
|
||||||
|
body { background-color: #ddd; }
|
||||||
|
td { border-bottom: 1px solid #000;}
|
||||||
|
#outer { margin-top: 1em; }
|
||||||
|
.sub { color: #555; }
|
||||||
|
"""
|
||||||
|
|
||||||
|
_body = """
|
||||||
|
<center>
|
||||||
|
<div id="outer">
|
||||||
|
<h1>%s</h1>
|
||||||
|
<table cellspacing=0 width=90%%>
|
||||||
|
%s
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<div id="today">
|
||||||
|
</div>
|
||||||
|
<a href="#" onClick="py.run('full');">show today's stats</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
class DeckBrowser(object):
|
class DeckBrowser(object):
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw):
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
|
self.web = mw.web
|
||||||
self._browserLastRefreshed = 0
|
self._browserLastRefreshed = 0
|
||||||
self._decks = []
|
self._decks = []
|
||||||
addHook("deckClosing", self.onClose)
|
addHook("deckClosing", self.onClose)
|
||||||
|
|
||||||
|
def _bridge(self, str):
|
||||||
|
if str == "refresh":
|
||||||
|
pass
|
||||||
|
elif str == "full":
|
||||||
|
self.onFull()
|
||||||
|
|
||||||
|
def _link(self, url):
|
||||||
|
pass
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
|
self.web.setBridge(self._bridge)
|
||||||
|
self.web.setLinkHandler(self._link)
|
||||||
if (time.time() - self._browserLastRefreshed >
|
if (time.time() - self._browserLastRefreshed >
|
||||||
self.mw.config['deckBrowserRefreshPeriod']):
|
self.mw.config['deckBrowserRefreshPeriod']):
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
@ -26,14 +59,10 @@ class DeckBrowser(object):
|
||||||
else:
|
else:
|
||||||
self._reorderDecks()
|
self._reorderDecks()
|
||||||
if self._decks:
|
if self._decks:
|
||||||
buf = self._header()
|
buf = ""
|
||||||
buf += "<center><h1>Decks</h1><table cellspacing=0 width=90%>"
|
|
||||||
t = time.time
|
|
||||||
for c, deck in enumerate(self._decks):
|
for c, deck in enumerate(self._decks):
|
||||||
buf += self._deckRow(c, deck)
|
buf += self._deckRow(c, deck)
|
||||||
buf += "</table>"
|
self.web.stdHtml(_body%(_("Decks"), buf), _css)
|
||||||
buf += self._buttons()
|
|
||||||
buf += self._summary()
|
|
||||||
else:
|
else:
|
||||||
buf = ("""\
|
buf = ("""\
|
||||||
<br>
|
<br>
|
||||||
|
@ -44,7 +73,7 @@ later by using File>Close.
|
||||||
<br>
|
<br>
|
||||||
""")
|
""")
|
||||||
# FIXME: ensure deck open button is focused
|
# FIXME: ensure deck open button is focused
|
||||||
self.mw.web.setHtml(buf)
|
|
||||||
|
|
||||||
def onClose(self, deck):
|
def onClose(self, deck):
|
||||||
print "onClose"
|
print "onClose"
|
||||||
|
@ -61,12 +90,6 @@ later by using File>Close.
|
||||||
d['time'] = self.deck._dailyStats.reviewTime
|
d['time'] = self.deck._dailyStats.reviewTime
|
||||||
d['reps'] = self.deck._dailyStats.reps
|
d['reps'] = self.deck._dailyStats.reps
|
||||||
|
|
||||||
def _header(self):
|
|
||||||
return "<html><head><style>td { border-bottom: 1px solid #000; margin:0px; padding:0px;} </style></head><body>"
|
|
||||||
|
|
||||||
def _footer(self):
|
|
||||||
return "</body></html>"
|
|
||||||
|
|
||||||
def _deckRow(self, c, deck):
|
def _deckRow(self, c, deck):
|
||||||
buf = "<tr>"
|
buf = "<tr>"
|
||||||
# name and status
|
# name and status
|
||||||
|
@ -81,7 +104,7 @@ later by using File>Close.
|
||||||
elif deck['state'] == 'in use':
|
elif deck['state'] == 'in use':
|
||||||
sub = _("(already open)")
|
sub = _("(already open)")
|
||||||
sub = "<font size=-1>%s</font>" % sub
|
sub = "<font size=-1>%s</font>" % sub
|
||||||
buf += "<td>%s<br>%s</td>" % (deck['name'], sub)
|
buf += "<td><b>%s</b><br><span class=sub>%s</span></td>" % (deck['name'], sub)
|
||||||
if ok:
|
if ok:
|
||||||
# due
|
# due
|
||||||
col = '<td><b><font color=#0000ff>%s</font></b></td>'
|
col = '<td><b><font color=#0000ff>%s</font></b></td>'
|
||||||
|
@ -154,14 +177,14 @@ later by using File>Close.
|
||||||
# self.moreMenus.append(moreMenu)
|
# self.moreMenus.append(moreMenu)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _summary(self):
|
def onFull(self):
|
||||||
return ""
|
|
||||||
# summarize
|
# summarize
|
||||||
reps = 0
|
reps = 0
|
||||||
mins = 0
|
mins = 0
|
||||||
revC = 0
|
revC = 0
|
||||||
newC = 0
|
newC = 0
|
||||||
for d in self._decks:
|
for d in self._decks:
|
||||||
|
if d['state']=='ok':
|
||||||
reps += d['reps']
|
reps += d['reps']
|
||||||
mins += d['time']
|
mins += d['time']
|
||||||
revC += d['due']
|
revC += d['due']
|
||||||
|
@ -171,7 +194,7 @@ later by using File>Close.
|
||||||
"Studied <b>%(reps)d cards</b> in <b>%(time)s</b> today.",
|
"Studied <b>%(reps)d cards</b> in <b>%(time)s</b> today.",
|
||||||
reps) % {
|
reps) % {
|
||||||
'reps': reps,
|
'reps': reps,
|
||||||
'time': anki.utils.fmtTimeSpan(mins, point=2),
|
'time': fmtTimeSpan(mins, point=2),
|
||||||
}
|
}
|
||||||
rev = ngettext(
|
rev = ngettext(
|
||||||
"<b><font color=#0000ff>%d</font></b> review",
|
"<b><font color=#0000ff>%d</font></b> review",
|
||||||
|
@ -180,7 +203,7 @@ later by using File>Close.
|
||||||
new = ngettext("<b>%d</b> new card", "<b>%d</b> new cards", newC) % newC
|
new = ngettext("<b>%d</b> new card", "<b>%d</b> new cards", newC) % newC
|
||||||
line2 = _("Due: %(rev)s, %(new)s") % {
|
line2 = _("Due: %(rev)s, %(new)s") % {
|
||||||
'rev': rev, 'new': new}
|
'rev': rev, 'new': new}
|
||||||
return ""
|
return self.web.eval("$('#today').html('%s');" % (line1+"<br>"+line2))
|
||||||
|
|
||||||
def _checkDecks(self, forget=False):
|
def _checkDecks(self, forget=False):
|
||||||
self._decks = []
|
self._decks = []
|
||||||
|
@ -203,6 +226,8 @@ later by using File>Close.
|
||||||
t = time.time()
|
t = time.time()
|
||||||
deck = Deck(d)
|
deck = Deck(d)
|
||||||
counts = deck.sched.counts()
|
counts = deck.sched.counts()
|
||||||
|
dtime = deck.sched.timeToday()
|
||||||
|
dreps = deck.sched.repsToday()
|
||||||
self._decks.append({
|
self._decks.append({
|
||||||
'path': d,
|
'path': d,
|
||||||
'state': 'ok',
|
'state': 'ok',
|
||||||
|
@ -210,9 +235,9 @@ later by using File>Close.
|
||||||
'due': counts[0],
|
'due': counts[0],
|
||||||
'new': counts[1],
|
'new': counts[1],
|
||||||
'mod': deck.mod,
|
'mod': deck.mod,
|
||||||
# these multiple deck check time by a factor of 6
|
# these multiply deck check time by a factor of 6
|
||||||
'time': 0, #deck.sched.timeToday(),
|
'time': dtime,
|
||||||
'reps': 0, #deck.sched.repsToday()
|
'reps': dreps
|
||||||
})
|
})
|
||||||
deck.close()
|
deck.close()
|
||||||
# reset modification time for the sake of backup systems
|
# reset modification time for the sake of backup systems
|
||||||
|
|
44
aqt/main.py
44
aqt/main.py
|
@ -8,7 +8,6 @@ from operator import itemgetter
|
||||||
|
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
from PyQt4.QtGui import *
|
from PyQt4.QtGui import *
|
||||||
from PyQt4.QtWebKit import QWebPage, QWebView
|
|
||||||
from PyQt4 import pyqtconfig
|
from PyQt4 import pyqtconfig
|
||||||
QtConfig = pyqtconfig.Configuration()
|
QtConfig = pyqtconfig.Configuration()
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ from anki.hooks import runHook, addHook, removeHook
|
||||||
import anki.consts
|
import anki.consts
|
||||||
|
|
||||||
import aqt, aqt.utils, aqt.view, aqt.help, aqt.status, aqt.facteditor, \
|
import aqt, aqt.utils, aqt.view, aqt.help, aqt.status, aqt.facteditor, \
|
||||||
aqt.progress
|
aqt.progress, aqt.webview
|
||||||
from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
|
from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
|
||||||
saveState, restoreState
|
saveState, restoreState
|
||||||
config = aqt.config
|
config = aqt.config
|
||||||
|
@ -328,7 +327,7 @@ Please do not file a bug report with Anki.<br>""")
|
||||||
diag.setMinimumHeight(400)
|
diag.setMinimumHeight(400)
|
||||||
diag.setMinimumWidth(500)
|
diag.setMinimumWidth(500)
|
||||||
diag.exec_()
|
diag.exec_()
|
||||||
self.clearProgress()
|
self.progress.clear()
|
||||||
|
|
||||||
# Main window setup
|
# Main window setup
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -337,7 +336,7 @@ Please do not file a bug report with Anki.<br>""")
|
||||||
# main window
|
# main window
|
||||||
self.form = aqt.forms.main.Ui_MainWindow()
|
self.form = aqt.forms.main.Ui_MainWindow()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.web = AnkiWebView(self.form.centralwidget)
|
self.web = aqt.webview.AnkiWebView(self.form.centralwidget)
|
||||||
self.web.setObjectName("mainText")
|
self.web.setObjectName("mainText")
|
||||||
self.web.setFocusPolicy(Qt.ClickFocus)
|
self.web.setFocusPolicy(Qt.ClickFocus)
|
||||||
self.mainLayout = QVBoxLayout()
|
self.mainLayout = QVBoxLayout()
|
||||||
|
@ -345,9 +344,9 @@ Please do not file a bug report with Anki.<br>""")
|
||||||
self.mainLayout.setContentsMargins(0,0,0,0)
|
self.mainLayout.setContentsMargins(0,0,0,0)
|
||||||
self.form.centralwidget.setLayout(self.mainLayout)
|
self.form.centralwidget.setLayout(self.mainLayout)
|
||||||
#self.help = aqt.help.HelpArea(self.form.helpFrame, self.config, self)
|
#self.help = aqt.help.HelpArea(self.form.helpFrame, self.config, self)
|
||||||
self.connect(self.web.pageAction(QWebPage.Reload),
|
#self.connect(self.web.pageAction(QWebPage.Reload),
|
||||||
SIGNAL("triggered()"),
|
# SIGNAL("triggered()"),
|
||||||
self.onReload)
|
# self.onReload)
|
||||||
# congrats
|
# congrats
|
||||||
# self.connect(self.mainWin.learnMoreButton,
|
# self.connect(self.mainWin.learnMoreButton,
|
||||||
# SIGNAL("clicked()"),
|
# SIGNAL("clicked()"),
|
||||||
|
@ -2710,34 +2709,3 @@ It can take a long time. Proceed?""")):
|
||||||
self.form.decksLabel.hide()
|
self.form.decksLabel.hide()
|
||||||
self.form.decksLine.hide()
|
self.form.decksLine.hide()
|
||||||
self.form.studyOptsLabel.hide()
|
self.form.studyOptsLabel.hide()
|
||||||
|
|
||||||
|
|
||||||
# Main web view
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
class AnkiWebView(QWebView):
|
|
||||||
def __init__(self, *args):
|
|
||||||
QWebView.__init__(self, *args)
|
|
||||||
self.setObjectName("mainText")
|
|
||||||
self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
|
||||||
self.setLinkHandler()
|
|
||||||
self.connect(self, SIGNAL("linkClicked(QUrl)"), self._linkHandler)
|
|
||||||
def keyPressEvent(self, evt):
|
|
||||||
if evt.matches(QKeySequence.Copy):
|
|
||||||
self.triggerPageAction(QWebPage.Copy)
|
|
||||||
evt.accept()
|
|
||||||
QWebView.keyPressEvent(self, evt)
|
|
||||||
def contextMenuEvent(self, evt):
|
|
||||||
QWebView.contextMenuEvent(self, evt)
|
|
||||||
def dropEvent(self, evt):
|
|
||||||
pass
|
|
||||||
def _linkHandler(self, url):
|
|
||||||
self.linkHandler(url)
|
|
||||||
def setLinkHandler(self, handler=None):
|
|
||||||
if handler:
|
|
||||||
self.linkHandler = handler
|
|
||||||
else:
|
|
||||||
self.linkHandler = self._openLinksExternally
|
|
||||||
def _openLinksExternally(self, url):
|
|
||||||
QDesktopServices.openUrl(QUrl(url))
|
|
||||||
|
|
||||||
|
|
83
aqt/webview.py
Normal file
83
aqt/webview.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from PyQt4.QtCore import *
|
||||||
|
from PyQt4.QtGui import *
|
||||||
|
from PyQt4.QtWebKit import QWebPage, QWebView
|
||||||
|
from PyQt4 import pyqtconfig
|
||||||
|
QtConfig = pyqtconfig.Configuration()
|
||||||
|
|
||||||
|
# Bridge for Qt<->JS
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
class Bridge(QObject):
|
||||||
|
@pyqtSlot(str, result=str)
|
||||||
|
def run(self, str):
|
||||||
|
return unicode(self._bridge(unicode(str)))
|
||||||
|
def setBridge(self, func):
|
||||||
|
self._bridge = func
|
||||||
|
|
||||||
|
# Page for debug messages
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
class AnkiWebPage(QWebPage):
|
||||||
|
|
||||||
|
def __init__(self, parent, jsErr):
|
||||||
|
QWebPage.__init__(self, parent)
|
||||||
|
self._jsErr = jsErr
|
||||||
|
def javaScriptConsoleMessage(self, msg, line, srcID):
|
||||||
|
self._jsErr(msg, line, srcID)
|
||||||
|
|
||||||
|
# Main web view
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
class AnkiWebView(QWebView):
|
||||||
|
def __init__(self, parent):
|
||||||
|
QWebView.__init__(self, parent)
|
||||||
|
self.setObjectName("mainText")
|
||||||
|
self._bridge = Bridge()
|
||||||
|
self._page = AnkiWebPage(parent, self._jsErr)
|
||||||
|
self._loadFinishedCB = None
|
||||||
|
self.setPage(self._page)
|
||||||
|
self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
||||||
|
self.page().mainFrame().addToJavaScriptWindowObject("py", self._bridge)
|
||||||
|
self.setLinkHandler()
|
||||||
|
self.connect(self, SIGNAL("linkClicked(QUrl)"), self._linkHandler)
|
||||||
|
self.connect(self, SIGNAL("loadFinished(bool)"), self._loadFinished)
|
||||||
|
def keyPressEvent(self, evt):
|
||||||
|
if evt.matches(QKeySequence.Copy):
|
||||||
|
self.triggerPageAction(QWebPage.Copy)
|
||||||
|
evt.accept()
|
||||||
|
QWebView.keyPressEvent(self, evt)
|
||||||
|
def contextMenuEvent(self, evt):
|
||||||
|
QWebView.contextMenuEvent(self, evt)
|
||||||
|
def dropEvent(self, evt):
|
||||||
|
pass
|
||||||
|
def setLinkHandler(self, handler=None):
|
||||||
|
if handler:
|
||||||
|
self.linkHandler = handler
|
||||||
|
else:
|
||||||
|
self.linkHandler = self._openLinksExternally
|
||||||
|
def setHtml(self, html, loadCB=None):
|
||||||
|
if loadCB:
|
||||||
|
self._loadFinishedCB = loadCB
|
||||||
|
QWebView.setHtml(self, html)
|
||||||
|
def stdHtml(self, body, css="", loadCB=None):
|
||||||
|
self.setHtml("""
|
||||||
|
<html><head><style>%s</style><script src="qrc:/jquery.min.js"></script></head>
|
||||||
|
<body>%s</body></html>""" % (css, body), loadCB)
|
||||||
|
def setBridge(self, bridge):
|
||||||
|
self._bridge.setBridge(bridge)
|
||||||
|
def eval(self, js):
|
||||||
|
self.page().mainFrame().evaluateJavaScript(js)
|
||||||
|
def _openLinksExternally(self, url):
|
||||||
|
QDesktopServices.openUrl(QUrl(url))
|
||||||
|
def _jsErr(self, msg, line, srcID):
|
||||||
|
sys.stderr.write(_("JS error on line %d: %s") % (line, msg+"\n"))
|
||||||
|
def _linkHandler(self, url):
|
||||||
|
self.linkHandler(unicode(url.toString()))
|
||||||
|
def _loadFinished(self):
|
||||||
|
if self._loadFinishedCB:
|
||||||
|
self._loadFinishedCB(self)
|
|
@ -94,5 +94,6 @@
|
||||||
<file>icons/text_under.png</file>
|
<file>icons/text_under.png</file>
|
||||||
<file>icons/view-pim-news.png</file>
|
<file>icons/view-pim-news.png</file>
|
||||||
<file>icons/view_text.png</file>
|
<file>icons/view_text.png</file>
|
||||||
|
<file>jquery.min.js</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
16
designer/jquery.min.js
vendored
Normal file
16
designer/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue