mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 15:32: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.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):
|
||||
|
||||
def __init__(self, mw):
|
||||
self.mw = mw
|
||||
self.web = mw.web
|
||||
self._browserLastRefreshed = 0
|
||||
self._decks = []
|
||||
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):
|
||||
self.web.setBridge(self._bridge)
|
||||
self.web.setLinkHandler(self._link)
|
||||
if (time.time() - self._browserLastRefreshed >
|
||||
self.mw.config['deckBrowserRefreshPeriod']):
|
||||
t = time.time()
|
||||
|
@ -26,14 +59,10 @@ class DeckBrowser(object):
|
|||
else:
|
||||
self._reorderDecks()
|
||||
if self._decks:
|
||||
buf = self._header()
|
||||
buf += "<center><h1>Decks</h1><table cellspacing=0 width=90%>"
|
||||
t = time.time
|
||||
buf = ""
|
||||
for c, deck in enumerate(self._decks):
|
||||
buf += self._deckRow(c, deck)
|
||||
buf += "</table>"
|
||||
buf += self._buttons()
|
||||
buf += self._summary()
|
||||
self.web.stdHtml(_body%(_("Decks"), buf), _css)
|
||||
else:
|
||||
buf = ("""\
|
||||
<br>
|
||||
|
@ -44,7 +73,7 @@ later by using File>Close.
|
|||
<br>
|
||||
""")
|
||||
# FIXME: ensure deck open button is focused
|
||||
self.mw.web.setHtml(buf)
|
||||
|
||||
|
||||
def onClose(self, deck):
|
||||
print "onClose"
|
||||
|
@ -61,12 +90,6 @@ later by using File>Close.
|
|||
d['time'] = self.deck._dailyStats.reviewTime
|
||||
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):
|
||||
buf = "<tr>"
|
||||
# name and status
|
||||
|
@ -81,7 +104,7 @@ later by using File>Close.
|
|||
elif deck['state'] == 'in use':
|
||||
sub = _("(already open)")
|
||||
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:
|
||||
# due
|
||||
col = '<td><b><font color=#0000ff>%s</font></b></td>'
|
||||
|
@ -154,24 +177,24 @@ later by using File>Close.
|
|||
# self.moreMenus.append(moreMenu)
|
||||
return ""
|
||||
|
||||
def _summary(self):
|
||||
return ""
|
||||
def onFull(self):
|
||||
# summarize
|
||||
reps = 0
|
||||
mins = 0
|
||||
revC = 0
|
||||
newC = 0
|
||||
for d in self._decks:
|
||||
reps += d['reps']
|
||||
mins += d['time']
|
||||
revC += d['due']
|
||||
newC += d['new']
|
||||
if d['state']=='ok':
|
||||
reps += d['reps']
|
||||
mins += d['time']
|
||||
revC += d['due']
|
||||
newC += d['new']
|
||||
line1 = ngettext(
|
||||
"Studied <b>%(reps)d card</b> in <b>%(time)s</b> today.",
|
||||
"Studied <b>%(reps)d cards</b> in <b>%(time)s</b> today.",
|
||||
reps) % {
|
||||
'reps': reps,
|
||||
'time': anki.utils.fmtTimeSpan(mins, point=2),
|
||||
'time': fmtTimeSpan(mins, point=2),
|
||||
}
|
||||
rev = ngettext(
|
||||
"<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
|
||||
line2 = _("Due: %(rev)s, %(new)s") % {
|
||||
'rev': rev, 'new': new}
|
||||
return ""
|
||||
return self.web.eval("$('#today').html('%s');" % (line1+"<br>"+line2))
|
||||
|
||||
def _checkDecks(self, forget=False):
|
||||
self._decks = []
|
||||
|
@ -203,6 +226,8 @@ later by using File>Close.
|
|||
t = time.time()
|
||||
deck = Deck(d)
|
||||
counts = deck.sched.counts()
|
||||
dtime = deck.sched.timeToday()
|
||||
dreps = deck.sched.repsToday()
|
||||
self._decks.append({
|
||||
'path': d,
|
||||
'state': 'ok',
|
||||
|
@ -210,9 +235,9 @@ later by using File>Close.
|
|||
'due': counts[0],
|
||||
'new': counts[1],
|
||||
'mod': deck.mod,
|
||||
# these multiple deck check time by a factor of 6
|
||||
'time': 0, #deck.sched.timeToday(),
|
||||
'reps': 0, #deck.sched.repsToday()
|
||||
# these multiply deck check time by a factor of 6
|
||||
'time': dtime,
|
||||
'reps': dreps
|
||||
})
|
||||
deck.close()
|
||||
# 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.QtGui import *
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView
|
||||
from PyQt4 import pyqtconfig
|
||||
QtConfig = pyqtconfig.Configuration()
|
||||
|
||||
|
@ -19,7 +18,7 @@ from anki.hooks import runHook, addHook, removeHook
|
|||
import anki.consts
|
||||
|
||||
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, \
|
||||
saveState, restoreState
|
||||
config = aqt.config
|
||||
|
@ -328,7 +327,7 @@ Please do not file a bug report with Anki.<br>""")
|
|||
diag.setMinimumHeight(400)
|
||||
diag.setMinimumWidth(500)
|
||||
diag.exec_()
|
||||
self.clearProgress()
|
||||
self.progress.clear()
|
||||
|
||||
# Main window setup
|
||||
##########################################################################
|
||||
|
@ -337,7 +336,7 @@ Please do not file a bug report with Anki.<br>""")
|
|||
# main window
|
||||
self.form = aqt.forms.main.Ui_MainWindow()
|
||||
self.form.setupUi(self)
|
||||
self.web = AnkiWebView(self.form.centralwidget)
|
||||
self.web = aqt.webview.AnkiWebView(self.form.centralwidget)
|
||||
self.web.setObjectName("mainText")
|
||||
self.web.setFocusPolicy(Qt.ClickFocus)
|
||||
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.form.centralwidget.setLayout(self.mainLayout)
|
||||
#self.help = aqt.help.HelpArea(self.form.helpFrame, self.config, self)
|
||||
self.connect(self.web.pageAction(QWebPage.Reload),
|
||||
SIGNAL("triggered()"),
|
||||
self.onReload)
|
||||
#self.connect(self.web.pageAction(QWebPage.Reload),
|
||||
# SIGNAL("triggered()"),
|
||||
# self.onReload)
|
||||
# congrats
|
||||
# self.connect(self.mainWin.learnMoreButton,
|
||||
# SIGNAL("clicked()"),
|
||||
|
@ -2710,34 +2709,3 @@ It can take a long time. Proceed?""")):
|
|||
self.form.decksLabel.hide()
|
||||
self.form.decksLine.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/view-pim-news.png</file>
|
||||
<file>icons/view_text.png</file>
|
||||
<file>jquery.min.js</file>
|
||||
</qresource>
|
||||
</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