mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
start refactoring main window
- moved progress handling into separate progress.py - moved deck browser code into separate deckbrowser.py - started reworking the state code; views will be rolled into this in the future - the main window has been stripped of the study options, inline editor, congrats screen and so on, and now consists of a single main widget which has a webview placed inside it. The stripped features will be implemented either in separate windows, or as part of the web view
This commit is contained in:
parent
798b0af128
commit
d948b00c54
16 changed files with 986 additions and 4099 deletions
7
anki
7
anki
|
@ -13,15 +13,14 @@ if __name__ == "__main__":
|
||||||
runningDir=os.path.dirname(sys.argv[0])
|
runningDir=os.path.dirname(sys.argv[0])
|
||||||
modDir=runningDir
|
modDir=runningDir
|
||||||
|
|
||||||
# set up paths for local development
|
|
||||||
sys.path.insert(0, os.path.join(modDir, "libanki"))
|
sys.path.insert(0, os.path.join(modDir, "libanki"))
|
||||||
sys.path.insert(0, os.path.join(os.path.join(modDir, ".."), "libanki"))
|
sys.path.insert(0, os.path.join(os.path.join(modDir, ".."), "libanki"))
|
||||||
|
|
||||||
import ankiqt
|
import aqt
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ankiqt.forms
|
import aqt.forms
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception("You need to run tools/build_ui.sh in order for anki to work.")
|
raise Exception("You need to run tools/build_ui.sh in order for anki to work.")
|
||||||
|
|
||||||
ankiqt.run()
|
aqt.run()
|
||||||
|
|
|
@ -94,7 +94,7 @@ class AddCards(QDialog):
|
||||||
self.onLink)
|
self.onLink)
|
||||||
|
|
||||||
def onLink(self, url):
|
def onLink(self, url):
|
||||||
browser = ui.dialogs.get("CardList", self.parent)
|
browser = ui.dialogs.open("CardList", self.parent)
|
||||||
browser.dialog.filterEdit.setText("fid:" + url.toString())
|
browser.dialog.filterEdit.setText("fid:" + url.toString())
|
||||||
browser.updateSearch()
|
browser.updateSearch()
|
||||||
browser.onFact()
|
browser.onFact()
|
||||||
|
|
|
@ -1038,10 +1038,13 @@ where id in %s""" % ids2str(sf))
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def setupHooks(self):
|
def setupHooks(self):
|
||||||
|
print "setupHooks()"
|
||||||
|
return
|
||||||
addHook("postUndoRedo", self.postUndoRedo)
|
addHook("postUndoRedo", self.postUndoRedo)
|
||||||
addHook("currentCardDeleted", self.updateSearch)
|
addHook("currentCardDeleted", self.updateSearch)
|
||||||
|
|
||||||
def teardownHooks(self):
|
def teardownHooks(self):
|
||||||
|
return
|
||||||
removeHook("postUndoRedo", self.postUndoRedo)
|
removeHook("postUndoRedo", self.postUndoRedo)
|
||||||
removeHook("currentCardDeleted", self.updateSearch)
|
removeHook("currentCardDeleted", self.updateSearch)
|
||||||
|
|
||||||
|
|
|
@ -298,7 +298,7 @@ order by n""", id=card.id)
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
modified = False
|
modified = False
|
||||||
self.deck.startProgress()
|
self.mw.startProgress()
|
||||||
self.deck.updateProgress(_("Applying changes..."))
|
self.deck.updateProgress(_("Applying changes..."))
|
||||||
reset=True
|
reset=True
|
||||||
if self.needFormatRebuild:
|
if self.needFormatRebuild:
|
||||||
|
|
|
@ -14,8 +14,8 @@ defaultConf = {
|
||||||
'confVer': 3,
|
'confVer': 3,
|
||||||
# remove?
|
# remove?
|
||||||
'colourTimes': True,
|
'colourTimes': True,
|
||||||
'deckBrowserNameLength': 30,
|
|
||||||
'deckBrowserOrder': 0,
|
'deckBrowserOrder': 0,
|
||||||
|
# too long?
|
||||||
'deckBrowserRefreshPeriod': 3600,
|
'deckBrowserRefreshPeriod': 3600,
|
||||||
'factEditorAdvanced': False,
|
'factEditorAdvanced': False,
|
||||||
'showStudyScreen': True,
|
'showStudyScreen': True,
|
||||||
|
@ -96,7 +96,7 @@ class Config(object):
|
||||||
path = self._dbPath()
|
path = self._dbPath()
|
||||||
self.db = DB(path, level=None, text=str)
|
self.db = DB(path, level=None, text=str)
|
||||||
self.db.executescript("""
|
self.db.executescript("""
|
||||||
create table if not exists recentDecks (path not null);
|
create table if not exists decks (path text primary key);
|
||||||
create table if not exists config (conf text not null);
|
create table if not exists config (conf text not null);
|
||||||
insert or ignore into config values ('');""")
|
insert or ignore into config values ('');""")
|
||||||
conf = self.db.scalar("select conf from config")
|
conf = self.db.scalar("select conf from config")
|
||||||
|
@ -111,6 +111,24 @@ insert or ignore into config values ('');""")
|
||||||
cPickle.dumps(self._conf))
|
cPickle.dumps(self._conf))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
# recent deck support
|
||||||
|
def recentDecks(self):
|
||||||
|
"Return a list of paths to remembered decks."
|
||||||
|
# have to convert to unicode manually because of the text factory
|
||||||
|
return [unicode(d[0]) for d in
|
||||||
|
self.db.execute("select path from decks")]
|
||||||
|
|
||||||
|
def addRecentDeck(self, path):
|
||||||
|
"Add PATH to the list of recent decks if not already. Must be unicode."
|
||||||
|
self.db.execute("insert or ignore into decks values (?)",
|
||||||
|
path.encode("utf-8"))
|
||||||
|
|
||||||
|
def delRecentDeck(self, path):
|
||||||
|
"Remove PATH from the list if it exists. Must be unicode."
|
||||||
|
self.db.execute("delete from decks where path = ?",
|
||||||
|
path.encode("utf-8"))
|
||||||
|
|
||||||
|
# helpers
|
||||||
def _addDefaults(self):
|
def _addDefaults(self):
|
||||||
if self.get('confVer') >= defaultConf['confVer']:
|
if self.get('confVer') >= defaultConf['confVer']:
|
||||||
return
|
return
|
||||||
|
|
289
aqt/deckbrowser.py
Normal file
289
aqt/deckbrowser.py
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
import time, os, stat
|
||||||
|
from PyQt4.QtCore import *
|
||||||
|
from PyQt4.QtGui import *
|
||||||
|
from anki import Deck
|
||||||
|
from anki.utils import fmtTimeSpan
|
||||||
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
class DeckBrowser(object):
|
||||||
|
|
||||||
|
def __init__(self, mw):
|
||||||
|
self.mw = mw
|
||||||
|
self._browserLastRefreshed = 0
|
||||||
|
self._decks = []
|
||||||
|
addHook("deckClosing", self.onClose)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
if (time.time() - self._browserLastRefreshed >
|
||||||
|
self.mw.config['deckBrowserRefreshPeriod']):
|
||||||
|
t = time.time()
|
||||||
|
self._checkDecks()
|
||||||
|
print "check decks", time.time() - t
|
||||||
|
else:
|
||||||
|
self._reorderDecks()
|
||||||
|
if self._decks:
|
||||||
|
buf = self._header()
|
||||||
|
buf += "<center><h1>Decks</h1><table cellspacing=0 width=90%>"
|
||||||
|
t = time.time
|
||||||
|
for c, deck in enumerate(self._decks):
|
||||||
|
buf += self._deckRow(c, deck)
|
||||||
|
buf += "</table>"
|
||||||
|
buf += self._buttons()
|
||||||
|
buf += self._summary()
|
||||||
|
else:
|
||||||
|
buf = ("""\
|
||||||
|
<br>
|
||||||
|
<font size=+1>
|
||||||
|
Welcome to Anki! Click <b>'Download'</b> to get started. You can return here
|
||||||
|
later by using File>Close.
|
||||||
|
</font>
|
||||||
|
<br>
|
||||||
|
""")
|
||||||
|
# FIXME: ensure deck open button is focused
|
||||||
|
self.mw.web.setHtml(buf)
|
||||||
|
|
||||||
|
def onClose(self, deck):
|
||||||
|
print "onClose"
|
||||||
|
return
|
||||||
|
if deck.finishScheduler:
|
||||||
|
self.deck.finishScheduler()
|
||||||
|
self.deck.reset()
|
||||||
|
# update counts
|
||||||
|
for d in self.browserDecks:
|
||||||
|
if d['path'] == self.deck.path:
|
||||||
|
d['due'] = self.deck.failedSoonCount + self.deck.revCount
|
||||||
|
d['new'] = self.deck.newCount
|
||||||
|
d['mod'] = self.deck.modified
|
||||||
|
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
|
||||||
|
ok = deck['state'] == 'ok'
|
||||||
|
if ok:
|
||||||
|
sub = _("%s ago") % fmtTimeSpan(
|
||||||
|
time.time() - deck['mod'])
|
||||||
|
elif deck['state'] == 'missing':
|
||||||
|
sub = _("(moved or removed)")
|
||||||
|
elif deck['state'] == 'corrupt':
|
||||||
|
sub = _("(corrupt)")
|
||||||
|
elif deck['state'] == 'in use':
|
||||||
|
sub = _("(already open)")
|
||||||
|
sub = "<font size=-1>%s</font>" % sub
|
||||||
|
buf += "<td>%s<br>%s</td>" % (deck['name'], sub)
|
||||||
|
if ok:
|
||||||
|
# due
|
||||||
|
col = '<td><b><font color=#0000ff>%s</font></b></td>'
|
||||||
|
if deck['due'] > 0:
|
||||||
|
s = col % str(deck['due'])
|
||||||
|
else:
|
||||||
|
s = col % ""
|
||||||
|
buf += s
|
||||||
|
# new
|
||||||
|
if deck['new']:
|
||||||
|
s = str(deck['new'])
|
||||||
|
else:
|
||||||
|
s = ""
|
||||||
|
buf += "<td>%s</td>" % s
|
||||||
|
else:
|
||||||
|
buf += "<td></td><td></td>"
|
||||||
|
# open
|
||||||
|
# openButton = QPushButton(_("Open"))
|
||||||
|
# if c < 9:
|
||||||
|
# if sys.platform.startswith("darwin"):
|
||||||
|
# extra = ""
|
||||||
|
# # appears to be broken on osx
|
||||||
|
# #extra = _(" (Command+Option+%d)") % (c+1)
|
||||||
|
# #openButton.setShortcut(_("Ctrl+Alt+%d" % (c+1)))
|
||||||
|
# else:
|
||||||
|
# extra = _(" (Alt+%d)") % (c+1)
|
||||||
|
# openButton.setShortcut(_("Alt+%d" % (c+1)))
|
||||||
|
# else:
|
||||||
|
# extra = ""
|
||||||
|
# openButton.setToolTip(_("Open this deck%s") % extra)
|
||||||
|
# self.connect(openButton, SIGNAL("clicked()"),
|
||||||
|
# lambda d=deck['path']: self.loadDeck(d))
|
||||||
|
# layout.addWidget(openButton, c+1, 5)
|
||||||
|
# if c == 0:
|
||||||
|
# focusButton = openButton
|
||||||
|
# more
|
||||||
|
# moreButton = QPushButton(_("More"))
|
||||||
|
# moreMenu = QMenu()
|
||||||
|
# a = moreMenu.addAction(QIcon(":/icons/edit-undo.png"),
|
||||||
|
# _("Hide From List"))
|
||||||
|
# a.connect(a, SIGNAL("triggered()"),
|
||||||
|
# lambda c=c: self.onDeckBrowserForget(c))
|
||||||
|
# a = moreMenu.addAction(QIcon(":/icons/editdelete.png"),
|
||||||
|
# _("Delete"))
|
||||||
|
# a.connect(a, SIGNAL("triggered()"),
|
||||||
|
# lambda c=c: self.onDeckBrowserDelete(c))
|
||||||
|
# moreButton.setMenu(moreMenu)
|
||||||
|
# self.moreMenus.append(moreMenu)
|
||||||
|
# layout.addWidget(moreButton, c+1, 6)
|
||||||
|
buf += "</tr>"
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _buttons(self):
|
||||||
|
# refresh = QPushButton(_("Refresh"))
|
||||||
|
# refresh.setToolTip(_("Check due counts again (F5)"))
|
||||||
|
# refresh.setShortcut(_("F5"))
|
||||||
|
# self.connect(refresh, SIGNAL("clicked()"),
|
||||||
|
# self.refresh)
|
||||||
|
# layout.addItem(QSpacerItem(1,20, QSizePolicy.Preferred,
|
||||||
|
# QSizePolicy.Preferred), c+2, 5)
|
||||||
|
# layout.addWidget(refresh, c+3, 5)
|
||||||
|
# more = QPushButton(_("More"))
|
||||||
|
# moreMenu = QMenu()
|
||||||
|
# a = moreMenu.addAction(QIcon(":/icons/edit-undo.png"),
|
||||||
|
# _("Forget Inaccessible Decks"))
|
||||||
|
# a.connect(a, SIGNAL("triggered()"),
|
||||||
|
# self.onDeckBrowserForgetInaccessible)
|
||||||
|
# more.setMenu(moreMenu)
|
||||||
|
# layout.addWidget(more, c+3, 6)
|
||||||
|
# self.moreMenus.append(moreMenu)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _summary(self):
|
||||||
|
return ""
|
||||||
|
# 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']
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
rev = ngettext(
|
||||||
|
"<b><font color=#0000ff>%d</font></b> review",
|
||||||
|
"<b><font color=#0000ff>%d</font></b> reviews",
|
||||||
|
revC) % revC
|
||||||
|
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 ""
|
||||||
|
|
||||||
|
def _checkDecks(self, forget=False):
|
||||||
|
self._decks = []
|
||||||
|
decks = self.mw.config.recentDecks()
|
||||||
|
if not decks:
|
||||||
|
return
|
||||||
|
missingDecks = []
|
||||||
|
tx = time.time()
|
||||||
|
self.mw.progress.start(max=len(decks))
|
||||||
|
for c, d in enumerate(decks):
|
||||||
|
self.mw.progress.update(_("Checking deck %(x)d of %(y)d...") % {
|
||||||
|
'x': c+1, 'y': len(decks)})
|
||||||
|
base = os.path.basename(d)
|
||||||
|
if not os.path.exists(d):
|
||||||
|
missingDecks.append(d)
|
||||||
|
self._decks.append({'name': base, 'state': 'missing'})
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
mod = os.stat(d)[stat.ST_MTIME]
|
||||||
|
t = time.time()
|
||||||
|
deck = Deck(d)
|
||||||
|
counts = deck.sched.counts()
|
||||||
|
self._decks.append({
|
||||||
|
'path': d,
|
||||||
|
'state': 'ok',
|
||||||
|
'name': deck.name(),
|
||||||
|
'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()
|
||||||
|
})
|
||||||
|
deck.close()
|
||||||
|
# reset modification time for the sake of backup systems
|
||||||
|
try:
|
||||||
|
os.utime(d, (mod, mod))
|
||||||
|
except:
|
||||||
|
# some misbehaving filesystems may fail here
|
||||||
|
pass
|
||||||
|
except Exception, e:
|
||||||
|
if "File is in use" in unicode(e):
|
||||||
|
state = "in use"
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
state = "corrupt"
|
||||||
|
self._decks.append({'name': base, 'state':state})
|
||||||
|
if forget:
|
||||||
|
for d in missingDecks:
|
||||||
|
self.mw.config.delRecentDeck(d)
|
||||||
|
self.mw.progress.finish()
|
||||||
|
self._browserLastRefreshed = time.time()
|
||||||
|
self._reorderDecks()
|
||||||
|
|
||||||
|
def _reorderDecks(self):
|
||||||
|
print "reorder decks"
|
||||||
|
return
|
||||||
|
if self.mw.config['deckBrowserOrder'] == 0:
|
||||||
|
self._decks.sort(key=itemgetter('mod'),
|
||||||
|
reverse=True)
|
||||||
|
else:
|
||||||
|
def custcmp(a, b):
|
||||||
|
x = cmp(not not b['due'], not not a['due'])
|
||||||
|
if x:
|
||||||
|
return x
|
||||||
|
x = cmp(not not b['new'], not not a['new'])
|
||||||
|
if x:
|
||||||
|
return x
|
||||||
|
return cmp(a['mod'], b['mod'])
|
||||||
|
self._decks.sort(cmp=custcmp)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self._browserLastRefreshed = 0
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def onDeckBrowserForget(self, c):
|
||||||
|
if aqt.utils.askUser(_("""\
|
||||||
|
Hide %s from the list? You can File>Open it again later.""") %
|
||||||
|
self._decks[c]['name']):
|
||||||
|
self.mw.config['recentDeckPaths'].remove(self._decks[c]['path'])
|
||||||
|
del self._decks[c]
|
||||||
|
self.doLater(100, self.showDeckBrowser)
|
||||||
|
|
||||||
|
def onDeckBrowserDelete(self, c):
|
||||||
|
deck = self._decks[c]['path']
|
||||||
|
if aqt.utils.askUser(_("""\
|
||||||
|
Delete %s? If this deck is synchronized the online version will \
|
||||||
|
not be touched.""") %
|
||||||
|
self._decks[c]['name']):
|
||||||
|
del self._decks[c]
|
||||||
|
os.unlink(deck)
|
||||||
|
try:
|
||||||
|
shutil.rmtree(re.sub(".anki$", ".media", deck))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
#self.config['recentDeckPaths'].remove(deck)
|
||||||
|
self.doLater(100, self.showDeckBrowser)
|
||||||
|
|
||||||
|
def onDeckBrowserForgetInaccessible(self):
|
||||||
|
self._checkDecks(forget=True)
|
||||||
|
|
||||||
|
def doLater(self, msecs, func):
|
||||||
|
timer = QTimer(self)
|
||||||
|
timer.setSingleShot(True)
|
||||||
|
timer.start(msecs)
|
||||||
|
self.connect(timer, SIGNAL("timeout()"), func)
|
|
@ -152,7 +152,7 @@ class DeckProperties(QDialog):
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
n = _("Deck Properties")
|
n = _("Deck Properties")
|
||||||
self.d.startProgress()
|
self.mw.startProgress()
|
||||||
self.d.setUndoStart(n)
|
self.d.setUndoStart(n)
|
||||||
needSync = False
|
needSync = False
|
||||||
# syncing
|
# syncing
|
||||||
|
|
|
@ -250,7 +250,7 @@ class GraphWindow(object):
|
||||||
self.hbox.addWidget(buttonBox)
|
self.hbox.addWidget(buttonBox)
|
||||||
|
|
||||||
def showHideAll(self):
|
def showHideAll(self):
|
||||||
self.deck.startProgress(len(self.widgets))
|
self.mw.startProgress(len(self.widgets))
|
||||||
for w in self.widgets:
|
for w in self.widgets:
|
||||||
self.deck.updateProgress(_("Processing..."))
|
self.deck.updateProgress(_("Processing..."))
|
||||||
w.showHide()
|
w.showHide()
|
||||||
|
@ -280,7 +280,7 @@ class GraphWindow(object):
|
||||||
QDesktopServices.openUrl(QUrl(aqt.appWiki + "Graphs"))
|
QDesktopServices.openUrl(QUrl(aqt.appWiki + "Graphs"))
|
||||||
|
|
||||||
def onRefresh(self):
|
def onRefresh(self):
|
||||||
self.deck.startProgress(len(self.widgets))
|
self.mw.startProgress(len(self.widgets))
|
||||||
self.dg.stats = None
|
self.dg.stats = None
|
||||||
for w in self.widgets:
|
for w in self.widgets:
|
||||||
self.deck.updateProgress(_("Processing..."))
|
self.deck.updateProgress(_("Processing..."))
|
||||||
|
|
1448
aqt/main.py
1448
aqt/main.py
File diff suppressed because it is too large
Load diff
|
@ -261,7 +261,7 @@ class ModelProperties(QDialog):
|
||||||
def reject(self):
|
def reject(self):
|
||||||
"Save user settings on close."
|
"Save user settings on close."
|
||||||
# update properties
|
# update properties
|
||||||
self.deck.startProgress()
|
self.mw.startProgress()
|
||||||
mname = unicode(self.dialog.name.text())
|
mname = unicode(self.dialog.name.text())
|
||||||
if not mname:
|
if not mname:
|
||||||
mname = _("Model")
|
mname = _("Model")
|
||||||
|
|
129
aqt/progress.py
Normal file
129
aqt/progress.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
import time
|
||||||
|
from PyQt4.QtGui import *
|
||||||
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
# Progress info
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
class ProgressManager(object):
|
||||||
|
|
||||||
|
def __init__(self, mw):
|
||||||
|
self.mw = mw
|
||||||
|
self.app = QApplication.instance()
|
||||||
|
self._win = None
|
||||||
|
self._levels = 0
|
||||||
|
self._mainThread = QThread.currentThread()
|
||||||
|
|
||||||
|
# SQLite progress handler
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def setupDB(self):
|
||||||
|
"Install a handler in the current deck."
|
||||||
|
self.lastDbProgress = 0
|
||||||
|
self.inDB = False
|
||||||
|
self.mw.deck.db.set_progress_handler(self._dbProgress, 100000)
|
||||||
|
|
||||||
|
def _dbProgress(self):
|
||||||
|
"Called from SQLite."
|
||||||
|
# do nothing if we don't have a progress window
|
||||||
|
if not self._progressWin:
|
||||||
|
return
|
||||||
|
# make sure we're not executing too frequently
|
||||||
|
if (time.time() - self.lastDbProgress) < 0.2:
|
||||||
|
return
|
||||||
|
self.lastDbProgress = time.time()
|
||||||
|
# and we're in the main thread
|
||||||
|
if self._mainThread != QThread.currentThread():
|
||||||
|
return
|
||||||
|
# ensure timers don't fire
|
||||||
|
self.inDB = True
|
||||||
|
# handle GUI events
|
||||||
|
self._maybeShow()
|
||||||
|
self.app.processEvents(QEventLoop.ExcludeUserInputEvents)
|
||||||
|
self.inDB = False
|
||||||
|
|
||||||
|
# DB-safe timers
|
||||||
|
##########################################################################
|
||||||
|
# QTimer may fire in processEvents(). We provide a custom timer which
|
||||||
|
# automatically defers until the DB is not busy.
|
||||||
|
|
||||||
|
def timer(self, ms, func, repeat):
|
||||||
|
def handler():
|
||||||
|
if self.inDB:
|
||||||
|
# retry in 100ms
|
||||||
|
self.timer(100, func, repeat)
|
||||||
|
else:
|
||||||
|
func()
|
||||||
|
t = QTimer(self.mw)
|
||||||
|
if not repeat:
|
||||||
|
t.setSingleShot(True)
|
||||||
|
t.connect(t, SIGNAL("timeout()"), handler)
|
||||||
|
t.start(ms)
|
||||||
|
|
||||||
|
# Creating progress dialogs
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
def start(self, max=0, min=0, label=None, parent=None, immediate=False):
|
||||||
|
self._levels += 1
|
||||||
|
if self._levels > 1:
|
||||||
|
return
|
||||||
|
# disable UI
|
||||||
|
self.mw.app.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
|
self.mw.setEnabled(False)
|
||||||
|
# setup window
|
||||||
|
parent = parent or self.app.activeWindow() or self.mw
|
||||||
|
label = label or _("Processing...")
|
||||||
|
self._win = QProgressDialog(label, "", min, max, parent)
|
||||||
|
self._win.setWindowTitle("Anki")
|
||||||
|
self._win.setCancelButton(None)
|
||||||
|
self._win.setAutoClose(False)
|
||||||
|
self._win.setAutoReset(False)
|
||||||
|
# we need to manually manage minimum time to show, as qt gets confused
|
||||||
|
# by the db handler
|
||||||
|
self._win.setMinimumDuration(100000)
|
||||||
|
if immediate:
|
||||||
|
self._shown = True
|
||||||
|
print "show"
|
||||||
|
self._win.show()
|
||||||
|
else:
|
||||||
|
self._shown = False
|
||||||
|
self._counter = min
|
||||||
|
self._min = min
|
||||||
|
self._max = max
|
||||||
|
self._firstTime = time.time()
|
||||||
|
self._lastTime = time.time()
|
||||||
|
|
||||||
|
def update(self, label=None, value=None, process=True):
|
||||||
|
#print self._min, self._counter, self._max, label, time.time() - self._lastTime
|
||||||
|
self._maybeShow()
|
||||||
|
self._lastTime = time.time()
|
||||||
|
if label:
|
||||||
|
self._win.setLabelText(label)
|
||||||
|
if self._max and self._shown:
|
||||||
|
self._counter = value or (self._counter+1)
|
||||||
|
self._win.setValue(self._counter)
|
||||||
|
if process:
|
||||||
|
self.app.processEvents()
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self._levels -= 1
|
||||||
|
if self._levels == 0:
|
||||||
|
self._win.cancel()
|
||||||
|
self.app.restoreOverrideCursor()
|
||||||
|
self.mw.setEnabled(True)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"Restore the interface after an error."
|
||||||
|
if self._levels:
|
||||||
|
self._levels = 1
|
||||||
|
self.finishProgress()
|
||||||
|
|
||||||
|
def _maybeShow(self):
|
||||||
|
if not self._shown and (time.time() - self._firstTime) > 2:
|
||||||
|
print "show2"
|
||||||
|
self._shown = True
|
||||||
|
self._win.show()
|
|
@ -26,6 +26,7 @@ class StatusView(object):
|
||||||
warnTime = 10
|
warnTime = 10
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
|
return
|
||||||
self.main = parent
|
self.main = parent
|
||||||
self.statusbar = parent.mainWin.statusbar
|
self.statusbar = parent.mainWin.statusbar
|
||||||
self.shown = []
|
self.shown = []
|
||||||
|
|
50
aqt/utils.py
50
aqt/utils.py
|
@ -285,53 +285,3 @@ def getBase(deck, card):
|
||||||
return '<base href="%s">' % base
|
return '<base href="%s">' % base
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
class ProgressWin(object):
|
|
||||||
|
|
||||||
def __init__(self, parent, max=0, min=0, title=None, immediate=False):
|
|
||||||
if not title:
|
|
||||||
title = "Anki"
|
|
||||||
self.diag = QProgressDialog("", "", min, max, parent)
|
|
||||||
self.diag.setWindowTitle(title)
|
|
||||||
self.diag.setCancelButton(None)
|
|
||||||
self.diag.setAutoClose(False)
|
|
||||||
self.diag.setAutoReset(False)
|
|
||||||
# qt doesn't seem to honour this consistently, and it's not triggered
|
|
||||||
# by the db progress handler, so we set it high and use maybeShow() below
|
|
||||||
if immediate:
|
|
||||||
self.diag.show()
|
|
||||||
else:
|
|
||||||
self.diag.setMinimumDuration(100000)
|
|
||||||
self.counter = min
|
|
||||||
self.min = min
|
|
||||||
self.max = max
|
|
||||||
self.firstTime = time.time()
|
|
||||||
self.lastTime = time.time()
|
|
||||||
self.app = QApplication.instance()
|
|
||||||
self.shown = False
|
|
||||||
if max == 0:
|
|
||||||
self.diag.setLabelText(_("Processing..."))
|
|
||||||
|
|
||||||
def maybeShow(self):
|
|
||||||
if time.time() - self.firstTime > 2 and not self.shown:
|
|
||||||
self.shown = True
|
|
||||||
self.diag.show()
|
|
||||||
|
|
||||||
def update(self, label=None, value=None, process=True):
|
|
||||||
#print self.min, self.counter, self.max, label, time.time() - self.lastTime
|
|
||||||
self.maybeShow()
|
|
||||||
self.lastTime = time.time()
|
|
||||||
if label:
|
|
||||||
self.diag.setLabelText(label)
|
|
||||||
if value is None:
|
|
||||||
value = self.counter
|
|
||||||
self.counter += 1
|
|
||||||
else:
|
|
||||||
self.counter = value + 1
|
|
||||||
if self.max:
|
|
||||||
self.diag.setValue(value)
|
|
||||||
if process:
|
|
||||||
self.app.processEvents()
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
self.diag.cancel()
|
|
||||||
|
|
19
aqt/view.py
19
aqt/view.py
|
@ -12,7 +12,6 @@ import types, time, re, os, urllib, sys, difflib
|
||||||
import unicodedata as ucd
|
import unicodedata as ucd
|
||||||
from aqt.utils import mungeQA, getBase
|
from aqt.utils import mungeQA, getBase
|
||||||
from anki.utils import fmtTimeSpan
|
from anki.utils import fmtTimeSpan
|
||||||
from PyQt4.QtWebKit import QWebPage, QWebView
|
|
||||||
|
|
||||||
failedCharColour = "#FF0000"
|
failedCharColour = "#FF0000"
|
||||||
passedCharColour = "#00FF00"
|
passedCharColour = "#00FF00"
|
||||||
|
@ -330,21 +329,3 @@ class View(object):
|
||||||
"Tell the user the deck is finished."
|
"Tell the user the deck is finished."
|
||||||
self.main.mainWin.congratsLabel.setText(
|
self.main.mainWin.congratsLabel.setText(
|
||||||
self.main.deck.deckFinishedMsg())
|
self.main.deck.deckFinishedMsg())
|
||||||
|
|
||||||
class AnkiWebView(QWebView):
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QWebView.__init__(self, *args)
|
|
||||||
self.setObjectName("mainText")
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
3104
designer/main.ui
3104
designer/main.ui
File diff suppressed because it is too large
Load diff
1
icons/.gitignore
vendored
1
icons/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
/.directory
|
|
Loading…
Reference in a new issue