From 7604fa37972a5c65c20ac1ff42eb798ae775f6d9 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 22 Mar 2011 09:48:00 +0900 Subject: [PATCH] move the overview into a separate file; rename noDeck state to deckBrowser --- aqt/deckbrowser.py | 2 +- aqt/main.py | 119 +++++++++++++++----------------- aqt/overview.py | 168 +++++++++++++++++++++++++++++++++++++++++++++ aqt/preferences.py | 2 +- aqt/reviewer.py | 111 +----------------------------- aqt/status.py | 4 +- 6 files changed, 230 insertions(+), 176 deletions(-) create mode 100644 aqt/overview.py 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 += "" % _("Due") + buf += "" % _("New") + line = "" + line += "" + 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 += "
%s%s
%s%s%s%s
" + 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 = """ + + + +
%s%s%s
%s%s%s
""" % ( + _("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 += "" % _("Due") - buf += "" % _("New") - line = "" - line += "" - 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 += "
%s%s
%s%s%s%s
" - 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 = """ - - - -
%s%s%s
%s%s
""" % ( - _("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",