diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py
index 6ac9900c6..6028d9ef3 100644
--- a/aqt/deckbrowser.py
+++ b/aqt/deckbrowser.py
@@ -142,7 +142,7 @@ a { font-size: 80%; }
def _renderPage(self):
if self._decks:
buf = ""
- css = self.mw._sharedCSS + self._css
+ css = self.mw.sharedCSS + self._css
max=len(self._decks)-1
for c, deck in enumerate(self._decks):
buf += self._deckRow(c, max, deck)
diff --git a/aqt/main.py b/aqt/main.py
index 6a970b734..8c67c9d7c 100755
--- a/aqt/main.py
+++ b/aqt/main.py
@@ -52,7 +52,7 @@ class AnkiQt(QMainWindow):
len(self.config['recentDeckPaths']) == 1):
self.maybeLoadLastDeck(args)
else:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
except:
showInfo("Error during startup:\n%s" % traceback.format_exc())
sys.exit(1)
@@ -77,33 +77,21 @@ class AnkiQt(QMainWindow):
self.setupAutoUpdate()
# screens
self.setupDeckBrowser()
+ self.setupOverview()
self.setupReviewer()
self.setupEditor()
self.setupStudyScreen()
- _sharedCSS = """
-body { background-color: #eee; margin-top: 1em; }
-a:hover { background-color: #aaa; }
-a.but { font-size: 80%; padding: 3; background-color: #ccc;
- border-radius: 2px; color: #000; margin: 0 5 0 5; text-decoration:
- none; display: inline-block; }
-h1 { margin-bottom: 0.2em; }
-hr { margin:5 0 5 0; border:0; height:1px; background-color:#ddd; }
-"""
-#a { text-decoration: none; }
-
# State machine
##########################################################################
def moveToState(self, state, *args):
- # eg noDeck -> noDeckState(oldState)
print "-> move from", self.state, "to", state
- getattr(self, "_"+state+"State")(self.state, *args)
self.state = state
+ getattr(self, "_"+state+"State")(self.state, *args)
- def _noDeckState(self, oldState):
- "Run when a deck is closed, or we open with an empty deck."
- # blank menus and load deck browser
+ def _deckBrowserState(self, oldState):
+ # shouldn't call this directly; call close
self.deck = None
self.currentCard = None
self.lastCard = None
@@ -114,35 +102,17 @@ hr { margin:5 0 5 0; border:0; height:1px; background-color:#ddd; }
def _deckLoadingState(self, oldState):
"Run once, when deck is loaded."
- #self.editor.deck = self.deck
- # on reset?
runHook("deckLoading", self.deck)
self.enableDeckMenuItems()
- if False: #self.config['showStudyScreen']:
- self.moveToState("studyScreen")
- else:
- self.moveToState("review")
-
- def _deckLoadedState(self, oldState):
- self.currentCard = None
- self.lastCard = None
- # to make programming easy, reset to reviews is fastest
- # but users expect edit windows to stay open. instead, we give each
- # module some hooks to respond to:
- # reset: called when we have modified the database and need to check
- # if things are still valid. edit windows should pull their data from
- # the database and close if the card/fact no longer exists. add window
- # doesn't have to do anything. editor.. what to do about the editor?
- # close: most windows will want to respond and close as there's no
- # longer a deck. we probably want to make thing like the graphs not
- # modal too.
- # could reset() while in review, in study options, in empty, in deck
- # finished, in editor, etc.
+ self.moveToState("overview")
def _deckClosingState(self, oldState):
"Run once, before a deck is closed."
runHook("deckClosing", self.deck)
+ def _overviewState(self, oldState):
+ self.overview.show()
+
def _reviewState(self, oldState):
self.reviewer.show()
@@ -176,6 +146,29 @@ hr { margin:5 0 5 0; border:0; height:1px; background-color:#ddd; }
runHook("guiReset")
self.moveToState("initial")
+
+ # HTML helpers
+ ##########################################################################
+
+ sharedCSS = """
+body { background-color: #eee; margin-top: 1em; }
+a:hover { background-color: #aaa; }
+a.but { font-size: 80%; padding: 3; background-color: #ccc;
+ border-radius: 2px; color: #000; margin: 0 5 0 5; text-decoration:
+ none; display: inline-block; }
+a.but:focus { background-color: #aaa; }
+h1 { margin-bottom: 0.2em; }
+hr { margin:5 0 5 0; border:0; height:1px; background-color:#ddd; }
+"""
+
+ def button(self, link, name, key=None):
+ if key:
+ key = _("Shortcut key: %s") % key
+ else:
+ key = ""
+ return '%s' % (
+ key, link, name)
+
# Signal handling
##########################################################################
@@ -649,7 +642,7 @@ counts are %d %d %d
aqt.utils.showCritical(_("""\
File is corrupt or not an Anki database. Click help for more info.\n
Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
return 0
self.config.addRecentDeck(self.deck.path)
self.setupMedia(self.deck)
@@ -724,13 +717,12 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
##########################################################################
def onClose(self):
- # allow focusOut to save
if self.inMainWindow() or not self.app.activeWindow():
- self.saveAndClose()
+ self.close()
else:
self.app.activeWindow().close()
- def saveAndClose(self, hideWelcome=False, parent=None):
+ def close(self, hideWelcome=False, parent=None):
"(Auto)save and close. Prompt if necessary. True if okay to proceed."
# allow any focusOut()s to run first
self.setFocus()
@@ -744,7 +736,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
self.deck.close()
self.deck = None
if not hideWelcome and not synced:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
self.hideWelcome = False
return True
@@ -758,7 +750,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onNew(self, path=None, prompt=None):
if not self.inMainWindow() and not path: return
- if not self.saveAndClose(hideWelcome=True): return
+ self.close()
register = not path
bad = ":/\\"
name = _("mydeck")
@@ -769,7 +761,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
name = aqt.utils.getOnlyText(
prompt, default=name, title=_("New Deck"))
if not name:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
return
found = False
for c in bad:
@@ -789,7 +781,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
defaultno=True):
os.unlink(path)
else:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
return
self.deck = DeckStorage.Deck(path)
self.deck.initUndo()
@@ -834,7 +826,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onOpenOnline(self):
if not self.inMainWindow(): return
self.ensureSyncParams()
- if not self.saveAndClose(hideWelcome=True): return
+ self.close()
# we need a disk-backed file for syncing
dir = unicode(tempfile.mkdtemp(prefix="anki"), sys.getfilesystemencoding())
path = os.path.join(dir, u"untitled.anki")
@@ -980,15 +972,16 @@ your deck."""))
self.moveToState("initial")
return file
- # Deck browser
+ # Components
##########################################################################
def setupDeckBrowser(self):
from aqt.deckbrowser import DeckBrowser
self.deckBrowser = DeckBrowser(self)
- # Reviewer
- ##########################################################################
+ def setupOverview(self):
+ from aqt.overview import Overview
+ self.overview = Overview(self)
def setupReviewer(self):
from aqt.reviewer import Reviewer
@@ -1014,15 +1007,13 @@ your deck."""))
if self.state == "editCurrentFact":
event.ignore()
return self.moveToState("saveEdit")
- if not self.saveAndClose(hideWelcome=True):
- event.ignore()
- else:
- if self.config['syncOnProgramOpen']:
- self.hideWelcome = True
- self.syncDeck(interactive=False)
- self.prepareForExit()
- event.accept()
- self.app.quit()
+ self.close()
+ if self.config['syncOnProgramOpen']:
+ self.hideWelcome = True
+ self.syncDeck(interactive=False)
+ self.prepareForExit()
+ event.accept()
+ self.app.quit()
# Edit current fact
##########################################################################
@@ -1822,7 +1813,7 @@ Are you sure?""" % deckName),
if self.loadAfterSync == -1:
# after sync all, so refresh browser list
self.browserLastRefreshed = 0
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
elif self.loadAfterSync and self.deckPath:
if self.loadAfterSync == 2:
name = re.sub("[<>]", "", self.syncName)
@@ -1849,9 +1840,9 @@ Are you sure?""" % deckName),
c.close()
self.loadDeck(self.deckPath)
else:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
except:
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
raise
finally:
self.deckPath = None
@@ -1883,7 +1874,7 @@ This deck already exists on your computer. Overwrite the local copy?"""),
"Unload a new deck if an initial sync failed."
self.deck = None
self.deckPath = None
- self.moveToState("noDeck")
+ self.moveToState("deckBrowser")
self.syncFinished = True
def setSyncStatus(self, text, *args):
diff --git a/aqt/overview.py b/aqt/overview.py
new file mode 100644
index 000000000..1c7b9d4f8
--- /dev/null
+++ b/aqt/overview.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+# Copyright: Damien Elmes
+# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
+
+import simplejson
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from anki.consts import NEW_CARDS_RANDOM
+
+class Overview(object):
+ "Deck overview."
+
+ def __init__(self, mw):
+ self.mw = mw
+ self.web = mw.web
+
+ def show(self):
+ self._setupToolbar()
+ self.mw.setKeyHandler(self._keyHandler)
+ self.web.setLinkHandler(self._linkHandler)
+ self._renderPage()
+
+ # Handlers
+ ############################################################
+
+ def _keyHandler(self, evt):
+ txt = evt.text()
+ if txt == "1":
+ self._linkHandler("studysel")
+ elif txt == "2":
+ self._linkHandler("studyall")
+ elif txt == "3":
+ self._linkHandler("cramsel")
+ elif txt == "4":
+ self._linkHandler("cramall")
+ elif txt == "o":
+ self._linkHandler("opts")
+ elif txt == "d":
+ self._linkHandler("list")
+ else:
+ evt.ignore()
+ return
+ evt.accept()
+
+ def _linkHandler(self, url):
+ if url == "studysel":
+ pass
+ elif url == "opts":
+ pass
+ elif url == "list":
+ self.mw.close()
+
+ # HTML
+ ############################################################
+
+ def _renderPage(self):
+ css = self.mw.sharedCSS + self._overviewCSS
+ fc = self._ovForecast()
+ tbl = self._overviewTable()
+ self.web.stdHtml(self._overviewBody % dict(
+ title=_("Overview"),
+ table=tbl,
+ fcsub=_("Due over next two weeks"),
+ fcdata=fc,
+ opts=self._ovOpts(),
+ ), css)
+
+ _overviewBody = """
+
+%(title)s
+%(table)s
+
+
+%(fcsub)s
+
+%(opts)s
+
+
+
+"""
+
+ _overviewCSS = """
+.due { text-align: right; color: green; }
+.new { text-align: right; color: blue; }
+.sub { font-size: 80%; color: #555; }
+"""
+
+ def _overviewTable(self):
+ counts = self._ovCounts()
+ but = self.mw.button
+ buf = ""
+ buf += " | %s | " % _("Due")
+ buf += "%s | |
" % _("New")
+ line = "%s | %s | "
+ line += "%s | %s |
"
+ buf += line % (
+ "%s" % _("Selected Groups"),
+ counts[0], counts[1],
+ but("studysel", _("Study"), "1") +
+ but("cramsel", _("Cram"), "3"))
+ buf += line % (
+ _("Whole Deck"),
+ counts[2], counts[3],
+ but("studyall", _("Study"), "2") +
+ but("cramall", _("Cram"), "4"))
+ buf += "
"
+ return buf
+
+ def _ovOpts(self):
+ if self.mw.deck.qconf['newCardOrder'] == NEW_CARDS_RANDOM:
+ ord = _("random")
+ else:
+ ord = _("order added")
+ but = self.mw.button
+ buf = """
+""" % (
+ _("New cards per day"), self.mw.deck.qconf['newPerDay'],
+ but("opts", _("Study Options"), "o"),
+ _("New card order"), ord,
+ but("list", "◀"+_("Deck List"), "d"))
+ return buf
+
+ # Data
+ ##########################################################################
+
+ def _ovCounts(self):
+ oldNew = self.mw.deck.qconf['newGroups']
+ oldRev = self.mw.deck.qconf['revGroups']
+ # we have the limited count already
+ selcnt = self.mw.deck.sched.selCounts()
+ allcnt = self.mw.deck.sched.allCounts()
+ return [
+ selcnt[1] + selcnt[2],
+ selcnt[0],
+ allcnt[1] + allcnt[2],
+ allcnt[0],
+ ]
+
+ def _ovForecast(self):
+ fc = self.mw.deck.sched.dueForecast(14)
+ if not sum(fc):
+ return "''"
+ return simplejson.dumps(tuple(enumerate(fc)))
+
+ # Toolbar
+ ##########################################################################
+
+ def _setupToolbar(self):
+ if not self.mw.config['showToolbar']:
+ return
+ self.mw.form.toolBar.show()
diff --git a/aqt/preferences.py b/aqt/preferences.py
index f61b6c782..af5d5ed03 100644
--- a/aqt/preferences.py
+++ b/aqt/preferences.py
@@ -83,7 +83,7 @@ class Preferences(QDialog):
self.origConfig.save()
self.parent.setLang()
if self.needDeckClose:
- self.parent.saveAndClose(parent=self)
+ self.parent.close(parent=self)
else:
self.parent.reset()
self.done(0)
diff --git a/aqt/reviewer.py b/aqt/reviewer.py
index b29d41b32..3a4b3bf48 100644
--- a/aqt/reviewer.py
+++ b/aqt/reviewer.py
@@ -31,116 +31,11 @@ class Reviewer(object):
self._setupToolbar()
self._reset()
- # Overview state
- ##########################################################################
-
- _overviewBody = """
-
-%(title)s
-%(table)s
-
-
-%(fcsub)s
-
-%(opts)s
-
-
-
-"""
-
- _overviewCSS = """
-.due { text-align: right; color: green; }
-.new { text-align: right; color: blue; }
-.sub { font-size: 80%; color: #555; }
-"""
-
- def _overview(self):
- css = self.mw._sharedCSS + self._overviewCSS
- fc = self._ovForecast()
- tbl = self._overviewTable()
- self.web.stdHtml(self._overviewBody % dict(
- title=_("Overview"),
- table=tbl,
- fcsub=_("Due over next two weeks"),
- fcdata=fc,
- opts=self._ovOpts(),
- ), css)
-
- def _overviewTable(self):
- counts = self._ovCounts()
- def but(link, name):
- return '%s' % (link, name)
- buf = ""
- buf += " | %s | " % _("Due")
- buf += "%s | |
" % _("New")
- line = "%s | %s | "
- line += "%s | %s |
"
- buf += line % (
- "%s" % _("Selected Groups"),
- counts[0], counts[1],
- but("studysel", _("Study")) +
- but("cramsel", _("Cram")))
- buf += line % (
- _("Whole Deck"),
- counts[2], counts[3],
- but("studyall", _("Study")) +
- but("cramall", _("Cram")))
- buf += "
"
- return buf
-
- def _ovCounts(self):
- oldNew = self.mw.deck.qconf['newGroups']
- oldRev = self.mw.deck.qconf['revGroups']
- # we have the limited count already
- selcnt = self.mw.deck.sched.selCounts()
- allcnt = self.mw.deck.sched.allCounts()
- return [
- selcnt[1] + selcnt[2],
- selcnt[0],
- allcnt[1] + allcnt[2],
- allcnt[0],
- ]
-
- def _ovForecast(self):
- fc = self.mw.deck.sched.dueForecast(14)
- if not sum(fc):
- return "''"
- return simplejson.dumps(tuple(enumerate(fc)))
-
- def _ovOpts(self):
- if self.mw.deck.qconf['newCardOrder'] == NEW_CARDS_RANDOM:
- ord = _("random")
- else:
- ord = _("order added")
- buf = """
-""" % (
- _("New cards per day"), self.mw.deck.qconf['newPerDay'],
- '%s' % _("Study Options"),
- _("New card order"), ord)
- return buf
-
# State control
##########################################################################
def _reset(self):
- self._overview()
+ pass
def setState(self, state):
"Change to STATE, and update the display."
@@ -148,7 +43,7 @@ $(function () {
self.state = state
if self.state == "initial":
return
- elif self.state == "noDeck":
+ elif self.state == "deckBrowser":
self.clearWindow()
self.drawWelcomeMessage()
self.flush()
@@ -157,7 +52,7 @@ $(function () {
def redisplay(self):
"Idempotently display the current state (prompt for question, etc)"
- if self.state == "noDeck" or self.state == "studyScreen":
+ if self.state == "deckBrowser" or self.state == "studyScreen":
return
self.buffer = ""
self.haveTop = self.needFutureWarning()
diff --git a/aqt/status.py b/aqt/status.py
index 4f6de5494..64b728844 100644
--- a/aqt/status.py
+++ b/aqt/status.py
@@ -31,7 +31,7 @@ class StatusView(object):
self.statusbar = parent.mainWin.statusbar
self.shown = []
self.hideBorders()
- self.setState("noDeck")
+ self.setState("deckBrowser")
self.timer = None
self.timerFlashStart = 0
self.thinkingTimer = QTimer(parent)
@@ -53,7 +53,7 @@ class StatusView(object):
self.state = state
if self.state == "initial":
self.showDeckStatus()
- elif self.state == "noDeck":
+ elif self.state == "deckBrowser":
self.hideDeckStatus()
elif self.state in ("showQuestion",
"deckFinished",