move the overview into a separate file; rename noDeck state to deckBrowser

This commit is contained in:
Damien Elmes 2011-03-22 09:48:00 +09:00
parent 04040e60f2
commit 7604fa3797
6 changed files with 230 additions and 176 deletions

View file

@ -142,7 +142,7 @@ a { font-size: 80%; }
def _renderPage(self): def _renderPage(self):
if self._decks: if self._decks:
buf = "" buf = ""
css = self.mw._sharedCSS + self._css css = self.mw.sharedCSS + self._css
max=len(self._decks)-1 max=len(self._decks)-1
for c, deck in enumerate(self._decks): for c, deck in enumerate(self._decks):
buf += self._deckRow(c, max, deck) buf += self._deckRow(c, max, deck)

View file

@ -52,7 +52,7 @@ class AnkiQt(QMainWindow):
len(self.config['recentDeckPaths']) == 1): len(self.config['recentDeckPaths']) == 1):
self.maybeLoadLastDeck(args) self.maybeLoadLastDeck(args)
else: else:
self.moveToState("noDeck") self.moveToState("deckBrowser")
except: except:
showInfo("Error during startup:\n%s" % traceback.format_exc()) showInfo("Error during startup:\n%s" % traceback.format_exc())
sys.exit(1) sys.exit(1)
@ -77,33 +77,21 @@ class AnkiQt(QMainWindow):
self.setupAutoUpdate() self.setupAutoUpdate()
# screens # screens
self.setupDeckBrowser() self.setupDeckBrowser()
self.setupOverview()
self.setupReviewer() self.setupReviewer()
self.setupEditor() self.setupEditor()
self.setupStudyScreen() 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 # State machine
########################################################################## ##########################################################################
def moveToState(self, state, *args): def moveToState(self, state, *args):
# eg noDeck -> noDeckState(oldState)
print "-> move from", self.state, "to", state print "-> move from", self.state, "to", state
getattr(self, "_"+state+"State")(self.state, *args)
self.state = state self.state = state
getattr(self, "_"+state+"State")(self.state, *args)
def _noDeckState(self, oldState): def _deckBrowserState(self, oldState):
"Run when a deck is closed, or we open with an empty deck." # shouldn't call this directly; call close
# blank menus and load deck browser
self.deck = None self.deck = None
self.currentCard = None self.currentCard = None
self.lastCard = 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): def _deckLoadingState(self, oldState):
"Run once, when deck is loaded." "Run once, when deck is loaded."
#self.editor.deck = self.deck
# on reset?
runHook("deckLoading", self.deck) runHook("deckLoading", self.deck)
self.enableDeckMenuItems() self.enableDeckMenuItems()
if False: #self.config['showStudyScreen']: self.moveToState("overview")
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.
def _deckClosingState(self, oldState): def _deckClosingState(self, oldState):
"Run once, before a deck is closed." "Run once, before a deck is closed."
runHook("deckClosing", self.deck) runHook("deckClosing", self.deck)
def _overviewState(self, oldState):
self.overview.show()
def _reviewState(self, oldState): def _reviewState(self, oldState):
self.reviewer.show() self.reviewer.show()
@ -176,6 +146,29 @@ hr { margin:5 0 5 0; border:0; height:1px; background-color:#ddd; }
runHook("guiReset") runHook("guiReset")
self.moveToState("initial") 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 '<a class=but title="%s" href="%s">%s</a>' % (
key, link, name)
# Signal handling # Signal handling
########################################################################## ##########################################################################
@ -649,7 +642,7 @@ counts are %d %d %d
aqt.utils.showCritical(_("""\ aqt.utils.showCritical(_("""\
File is corrupt or not an Anki database. Click help for more info.\n File is corrupt or not an Anki database. Click help for more info.\n
Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
self.moveToState("noDeck") self.moveToState("deckBrowser")
return 0 return 0
self.config.addRecentDeck(self.deck.path) self.config.addRecentDeck(self.deck.path)
self.setupMedia(self.deck) self.setupMedia(self.deck)
@ -724,13 +717,12 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
########################################################################## ##########################################################################
def onClose(self): def onClose(self):
# allow focusOut to save
if self.inMainWindow() or not self.app.activeWindow(): if self.inMainWindow() or not self.app.activeWindow():
self.saveAndClose() self.close()
else: else:
self.app.activeWindow().close() 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." "(Auto)save and close. Prompt if necessary. True if okay to proceed."
# allow any focusOut()s to run first # allow any focusOut()s to run first
self.setFocus() self.setFocus()
@ -744,7 +736,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
self.deck.close() self.deck.close()
self.deck = None self.deck = None
if not hideWelcome and not synced: if not hideWelcome and not synced:
self.moveToState("noDeck") self.moveToState("deckBrowser")
self.hideWelcome = False self.hideWelcome = False
return True return True
@ -758,7 +750,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onNew(self, path=None, prompt=None): def onNew(self, path=None, prompt=None):
if not self.inMainWindow() and not path: return if not self.inMainWindow() and not path: return
if not self.saveAndClose(hideWelcome=True): return self.close()
register = not path register = not path
bad = ":/\\" bad = ":/\\"
name = _("mydeck") name = _("mydeck")
@ -769,7 +761,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
name = aqt.utils.getOnlyText( name = aqt.utils.getOnlyText(
prompt, default=name, title=_("New Deck")) prompt, default=name, title=_("New Deck"))
if not name: if not name:
self.moveToState("noDeck") self.moveToState("deckBrowser")
return return
found = False found = False
for c in bad: for c in bad:
@ -789,7 +781,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
defaultno=True): defaultno=True):
os.unlink(path) os.unlink(path)
else: else:
self.moveToState("noDeck") self.moveToState("deckBrowser")
return return
self.deck = DeckStorage.Deck(path) self.deck = DeckStorage.Deck(path)
self.deck.initUndo() self.deck.initUndo()
@ -834,7 +826,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onOpenOnline(self): def onOpenOnline(self):
if not self.inMainWindow(): return if not self.inMainWindow(): return
self.ensureSyncParams() self.ensureSyncParams()
if not self.saveAndClose(hideWelcome=True): return self.close()
# we need a disk-backed file for syncing # we need a disk-backed file for syncing
dir = unicode(tempfile.mkdtemp(prefix="anki"), sys.getfilesystemencoding()) dir = unicode(tempfile.mkdtemp(prefix="anki"), sys.getfilesystemencoding())
path = os.path.join(dir, u"untitled.anki") path = os.path.join(dir, u"untitled.anki")
@ -980,15 +972,16 @@ your deck."""))
self.moveToState("initial") self.moveToState("initial")
return file return file
# Deck browser # Components
########################################################################## ##########################################################################
def setupDeckBrowser(self): def setupDeckBrowser(self):
from aqt.deckbrowser import DeckBrowser from aqt.deckbrowser import DeckBrowser
self.deckBrowser = DeckBrowser(self) self.deckBrowser = DeckBrowser(self)
# Reviewer def setupOverview(self):
########################################################################## from aqt.overview import Overview
self.overview = Overview(self)
def setupReviewer(self): def setupReviewer(self):
from aqt.reviewer import Reviewer from aqt.reviewer import Reviewer
@ -1014,9 +1007,7 @@ your deck."""))
if self.state == "editCurrentFact": if self.state == "editCurrentFact":
event.ignore() event.ignore()
return self.moveToState("saveEdit") return self.moveToState("saveEdit")
if not self.saveAndClose(hideWelcome=True): self.close()
event.ignore()
else:
if self.config['syncOnProgramOpen']: if self.config['syncOnProgramOpen']:
self.hideWelcome = True self.hideWelcome = True
self.syncDeck(interactive=False) self.syncDeck(interactive=False)
@ -1822,7 +1813,7 @@ Are you sure?""" % deckName),
if self.loadAfterSync == -1: if self.loadAfterSync == -1:
# after sync all, so refresh browser list # after sync all, so refresh browser list
self.browserLastRefreshed = 0 self.browserLastRefreshed = 0
self.moveToState("noDeck") self.moveToState("deckBrowser")
elif self.loadAfterSync and self.deckPath: elif self.loadAfterSync and self.deckPath:
if self.loadAfterSync == 2: if self.loadAfterSync == 2:
name = re.sub("[<>]", "", self.syncName) name = re.sub("[<>]", "", self.syncName)
@ -1849,9 +1840,9 @@ Are you sure?""" % deckName),
c.close() c.close()
self.loadDeck(self.deckPath) self.loadDeck(self.deckPath)
else: else:
self.moveToState("noDeck") self.moveToState("deckBrowser")
except: except:
self.moveToState("noDeck") self.moveToState("deckBrowser")
raise raise
finally: finally:
self.deckPath = None 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." "Unload a new deck if an initial sync failed."
self.deck = None self.deck = None
self.deckPath = None self.deckPath = None
self.moveToState("noDeck") self.moveToState("deckBrowser")
self.syncFinished = True self.syncFinished = True
def setSyncStatus(self, text, *args): def setSyncStatus(self, text, *args):

168
aqt/overview.py Normal file
View file

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net>
# 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 = """
<center>
<h1>%(title)s</h1>
%(table)s
<hr>
<div id="placeholder" style="width:350px; height:100px;"></div>
<span class=sub>%(fcsub)s</span>
<hr class=sub>
%(opts)s
</center>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var d = %(fcdata)s;
if (d) {
$.plot($("#placeholder"), [
{ data: d, bars: { show: true, barWidth: 0.8 } }
], {
xaxis: { ticks: [[0.4, "Today"]] }
});
} else {
$("#placeholder").hide();
$(".sub").hide();
}
});
</script>
"""
_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 = "<table cellspacing=0 cellpadding=3 width=400>"
buf += "<tr><th></th><th align=right>%s</th>" % _("Due")
buf += "<th align=right>%s</th><th></th></tr>" % _("New")
line = "<tr><td><b>%s</b></td><td class=due>%s</td>"
line += "<td class=new>%s</td><td align=right>%s</td></tr>"
buf += line % (
"<a href=chgrp>%s</a>" % _("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 += "</table>"
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 = """
<table width=400>
<tr><td><b>%s</b></td><td align=center>%s</td><td align=right rowspan=1>%s</td></tr>
<tr><td><b>%s</b></td><td align=center>%s</td><td align=right rowspan=1>%s</td></tr>
</table>""" % (
_("New cards per day"), self.mw.deck.qconf['newPerDay'],
but("opts", _("Study Options"), "o"),
_("New card order"), ord,
but("list", "&#x25C0;"+_("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()

View file

@ -83,7 +83,7 @@ class Preferences(QDialog):
self.origConfig.save() self.origConfig.save()
self.parent.setLang() self.parent.setLang()
if self.needDeckClose: if self.needDeckClose:
self.parent.saveAndClose(parent=self) self.parent.close(parent=self)
else: else:
self.parent.reset() self.parent.reset()
self.done(0) self.done(0)

View file

@ -31,116 +31,11 @@ class Reviewer(object):
self._setupToolbar() self._setupToolbar()
self._reset() self._reset()
# Overview state
##########################################################################
_overviewBody = """
<center>
<h1>%(title)s</h1>
%(table)s
<hr>
<div id="placeholder" style="width:350px; height:100px;"></div>
<span class=sub>%(fcsub)s</span>
<hr class=sub>
%(opts)s
</center>
<script id="source" language="javascript" type="text/javascript">
$(function () {
var d = %(fcdata)s;
if (d) {
$.plot($("#placeholder"), [
{ data: d, bars: { show: true, barWidth: 0.8 } }
], {
xaxis: { ticks: [[0.4, "Today"]] }
});
} else {
$("#placeholder").hide();
$(".sub").hide();
}
});
</script>
"""
_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 '<a class=but href="%s">%s</a>' % (link, name)
buf = "<table cellspacing=0 cellpadding=3 width=400>"
buf += "<tr><th></th><th align=right>%s</th>" % _("Due")
buf += "<th align=right>%s</th><th></th></tr>" % _("New")
line = "<tr><td><b>%s</b></td><td class=due>%s</td>"
line += "<td class=new>%s</td><td align=right>%s</td></tr>"
buf += line % (
"<a href=chgrp>%s</a>" % _("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 += "</table>"
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 = """
<table width=400>
<tr><td><b>%s</b></td><td align=center>%s</td><td align=right rowspan=2>%s</td></tr>
<tr><td><b>%s</b></td><td align=center>%s</td></tr>
</table>""" % (
_("New cards per day"), self.mw.deck.qconf['newPerDay'],
'<a href=opts class=but>%s</a>' % _("Study Options"),
_("New card order"), ord)
return buf
# State control # State control
########################################################################## ##########################################################################
def _reset(self): def _reset(self):
self._overview() pass
def setState(self, state): def setState(self, state):
"Change to STATE, and update the display." "Change to STATE, and update the display."
@ -148,7 +43,7 @@ $(function () {
self.state = state self.state = state
if self.state == "initial": if self.state == "initial":
return return
elif self.state == "noDeck": elif self.state == "deckBrowser":
self.clearWindow() self.clearWindow()
self.drawWelcomeMessage() self.drawWelcomeMessage()
self.flush() self.flush()
@ -157,7 +52,7 @@ $(function () {
def redisplay(self): def redisplay(self):
"Idempotently display the current state (prompt for question, etc)" "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 return
self.buffer = "" self.buffer = ""
self.haveTop = self.needFutureWarning() self.haveTop = self.needFutureWarning()

View file

@ -31,7 +31,7 @@ class StatusView(object):
self.statusbar = parent.mainWin.statusbar self.statusbar = parent.mainWin.statusbar
self.shown = [] self.shown = []
self.hideBorders() self.hideBorders()
self.setState("noDeck") self.setState("deckBrowser")
self.timer = None self.timer = None
self.timerFlashStart = 0 self.timerFlashStart = 0
self.thinkingTimer = QTimer(parent) self.thinkingTimer = QTimer(parent)
@ -53,7 +53,7 @@ class StatusView(object):
self.state = state self.state = state
if self.state == "initial": if self.state == "initial":
self.showDeckStatus() self.showDeckStatus()
elif self.state == "noDeck": elif self.state == "deckBrowser":
self.hideDeckStatus() self.hideDeckStatus()
elif self.state in ("showQuestion", elif self.state in ("showQuestion",
"deckFinished", "deckFinished",