From a036b97a6860d2c2dd144600c0eceb996ed37cb4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 27 Mar 2011 23:07:15 +0900 Subject: [PATCH] add new study options --- aqt/groupconf.py | 39 +++ aqt/main.py | 7 +- aqt/overview.py | 2 +- aqt/studyopts.py | 343 +++++--------------------- designer/groupconf.ui | 554 ++++++++++++++++++++++++++++++++++++++++++ designer/studyopts.ui | 219 ++++++++++------- tools/build_ui.sh | 12 +- 7 files changed, 807 insertions(+), 369 deletions(-) create mode 100644 aqt/groupconf.py create mode 100644 designer/groupconf.ui diff --git a/aqt/groupconf.py b/aqt/groupconf.py new file mode 100644 index 000000000..dd84ddcbe --- /dev/null +++ b/aqt/groupconf.py @@ -0,0 +1,39 @@ +# Copyright: Damien Elmes +# -*- coding: utf-8 -*- +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +import aqt + +class GroupConf(QDialog): + def __init__(self, mw): + QDialog.__init__(self, mw) + self.mw = mw + self.form = aqt.forms.groupconf.Ui_Dialog() + self.form.setupUi(self) + self.setupNew() + self.setupLapse() + self.setupRev() + self.setupCram() + self.setupGeneral() + self.connect(self.form.optionsHelpButton, + SIGNAL("clicked()"), + lambda: QDesktopServices.openUrl(QUrl( + aqt.appWiki + "StudyOptions"))) + self.exec_() + + def setupNew(self): + pass + + def setupLapse(self): + pass + + def setupRev(self): + pass + + def setupCram(self): + pass + + def setupGeneral(self): + pass diff --git a/aqt/main.py b/aqt/main.py index 8eb9f452e..8b0c19920 100755 --- a/aqt/main.py +++ b/aqt/main.py @@ -17,7 +17,7 @@ from anki.utils import addTags, parseTags, canonifyTags, stripHTML, checksum from anki.hooks import runHook, addHook, removeHook import anki.consts -import aqt, aqt.facteditor, aqt.progress, aqt.webview, aqt.stats +import aqt, aqt.facteditor, aqt.progress, aqt.webview, aqt.stats, aqt.studyopts from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \ saveState, restoreState, getOnlyText, askUser, GetTextDialog, \ askUserDialog, applyStyles, getText, showText, showCritical @@ -647,6 +647,9 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") def setupCardStats(self): self.cardStats = aqt.stats.CardStats(self) + def onStudyOptions(self): + aqt.studyopts.StudyOptions(self) + def onCardStats(self): self.cardStats.show() @@ -783,7 +786,7 @@ Please give your deck a name:""")) self.connect(m.actionCheckMediaDatabase, s, self.onCheckMediaDB) self.connect(m.actionDownloadMissingMedia, s, self.onDownloadMissingMedia) self.connect(m.actionLocalizeMedia, s, self.onLocalizeMedia) - #self.connect(m.actionStudyOptions, s, self.onStudyOptions) + self.connect(m.actionStudyOptions, s, self.onStudyOptions) self.connect(m.actionDonate, s, self.onDonate) self.connect(m.actionBuryFact, s, self.onBuryFact) diff --git a/aqt/overview.py b/aqt/overview.py index 33a5407c9..ce8f46f6d 100644 --- a/aqt/overview.py +++ b/aqt/overview.py @@ -54,7 +54,7 @@ class Overview(object): self.mw.deck.cramGroups(self.mw.deck.qconf['revGroups']) self.mw.moveToState("review") elif url == "opts": - print "study options" + self.mw.onStudyOptions() elif url == "list": self.mw.close() elif url == "chgrp": diff --git a/aqt/studyopts.py b/aqt/studyopts.py index 0a46f1a8b..06749204a 100644 --- a/aqt/studyopts.py +++ b/aqt/studyopts.py @@ -1,284 +1,75 @@ +# Copyright: Damien Elmes +# -*- coding: utf-8 -*- +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html - def _studyScreenState(self, oldState): - self.currentCard = None - # if self.deck.finishScheduler: - # self.deck.finishScheduler() - self.disableCardMenuItems() - self.showStudyScreen() +from PyQt4.QtCore import * +from PyQt4.QtGui import * +import datetime, time, aqt - # Study screen - ########################################################################## - - def setupStudyScreen(self): - return - self.form.buttonStack.hide() - self.form.newCardOrder.insertItems( - 0, QStringList(anki.consts.newCardOrderLabels().values())) - self.form.newCardScheduling.insertItems( - 0, QStringList(anki.consts.newCardSchedulingLabels().values())) - self.form.revCardOrder.insertItems( - 0, QStringList(anki.consts.revCardOrderLabels().values())) - self.connect(self.form.optionsHelpButton, - SIGNAL("clicked()"), +class StudyOptions(QDialog): + def __init__(self, mw): + QDialog.__init__(self, mw) + self.mw = mw + self.form = aqt.forms.studyopts.Ui_Dialog() + self.form.setupUi(self) + self.setup() + self.load() + self.connect(self.form.buttonBox, + SIGNAL("helpRequested()"), lambda: QDesktopServices.openUrl(QUrl( - aqt.appWiki + "StudyOptions"))) - self.connect(self.form.minuteLimit, - SIGNAL("textChanged(QString)"), self.onMinuteLimitChanged) - self.connect(self.form.questionLimit, - SIGNAL("textChanged(QString)"), self.onQuestionLimitChanged) - self.connect(self.form.newPerDay, - SIGNAL("textChanged(QString)"), self.onNewLimitChanged) - self.connect(self.form.startReviewingButton, - SIGNAL("clicked()"), - self.onStartReview) - self.connect(self.form.newCardOrder, - SIGNAL("activated(int)"), self.onNewCardOrderChanged) - self.connect(self.form.failedCardMax, - SIGNAL("editingFinished()"), - self.onFailedMaxChanged) - self.connect(self.form.newCategories, - SIGNAL("clicked()"), self.onNewCategoriesClicked) - self.connect(self.form.revCategories, - SIGNAL("clicked()"), self.onRevCategoriesClicked) - self.form.tabWidget.setCurrentIndex(self.config['studyOptionsTab']) + aqt.appWiki + "StudyOptions"))) + self.exec_() - def onNewCategoriesClicked(self): - aqt.activetags.show(self, "new") + def setup(self): + import anki.consts as c + self.form.newOrder.insertItems( + 0, QStringList(c.newCardOrderLabels().values())) + self.form.newSpread.insertItems( + 0, QStringList(c.newCardSchedulingLabels().values())) + self.form.revOrder.insertItems( + 0, QStringList(c.revCardOrderLabels().values())) - def onRevCategoriesClicked(self): - aqt.activetags.show(self, "rev") + def load(self): + f = self.form + d = self.mw.deck + qc = d.qconf + f.newPerDay.setValue(qc['newPerDay']) + f.newOrder.setCurrentIndex(qc['newOrder']) + f.newSpread.setCurrentIndex(qc['newSpread']) + f.revOrder.setCurrentIndex(qc['revOrder']) + f.timeLimit.setValue(qc['timeLim']/60.0) + f.questionLimit.setValue(qc['repLim']) + self.startDate = datetime.datetime.fromtimestamp(d.crt) + f.dayOffset.setValue(self.startDate.hour) + f.lrnCutoff.setValue(qc['collapseTime']/60.0) - def onFailedMaxChanged(self): - try: - v = int(self.form.failedCardMax.text()) - if v == 1 or v < 0: - v = 2 - self.deck.failedCardMax = v - except ValueError: - pass - self.form.failedCardMax.setText(str(self.deck.failedCardMax)) - self.deck.flushMod() + def accept(self): + f = self.form + d = self.mw.deck + qc = d.qconf + old = qc['newOrder'] + qc['newOrder'] = f.newOrder.currentIndex() + self.updateNewOrder(old, qc['newOrder']) + qc['newSpread'] = f.newSpread.currentIndex() + qc['revOrder'] = f.revOrder.currentIndex() + qc['newPerDay'] = f.newPerDay.value() + qc['timeLim'] = f.timeLimit.value()*60 + qc['repLim'] = f.questionLimit.value() + qc['collapseTime'] = f.lrnCutoff.value()*60 + hrs = f.dayOffset.value() + old = self.startDate + date = datetime.datetime( + old.year, old.month, old.day, hrs) + d.crt = int(time.mktime(date.timetuple())) + self.mw.reset() + QDialog.accept(self) - def onMinuteLimitChanged(self, qstr): - try: - val = float(self.form.minuteLimit.text()) * 60 - if self.deck.sessionTimeLimit == val: - return - self.deck.sessionTimeLimit = val - except ValueError: - pass - self.deck.flushMod() - self.updateStudyStats() - - def onQuestionLimitChanged(self, qstr): - try: - val = int(self.form.questionLimit.text()) - if self.deck.sessionRepLimit == val: - return - self.deck.sessionRepLimit = val - except ValueError: - pass - self.deck.flushMod() - self.updateStudyStats() - - def onNewLimitChanged(self, qstr): - try: - val = int(self.form.newPerDay.text()) - if self.deck.newCardsPerDay == val: - return - self.deck.newCardsPerDay = val - except ValueError: - pass - self.deck.flushMod() - self.deck.reset() - self.statusView.redraw() - self.updateStudyStats() - - def onNewCardOrderChanged(self, ncOrd): - def uf(obj, field, value): - if getattr(obj, field) != value: - setattr(obj, field, value) - self.deck.flushMod() - if ncOrd != 0: - if self.deck.newCardOrder == 0: - # need to put back in order - self.mw.startProgress() - self.mw.updateProgress(_("Ordering...")) - self.deck.orderNewCards() - self.deck.finishProgress() - uf(self.deck, 'newCardOrder', ncOrd) - elif ncOrd == 0: - # (re-)randomize - self.deck.startProgress() - self.deck.updateProgress(_("Randomizing...")) + def updateNewOrder(self, old, new): + if old == new: + return + self.mw.progress.start() + if new == 1: + self.deck.orderNewCards() + else: self.deck.randomizeNewCards() - self.deck.finishProgress() - uf(self.deck, 'newCardOrder', ncOrd) - - def updateActives(self): - labels = [ - _("Show All Due Cards"), - _("Show Chosen Categories") - ] - if self.deck.getVar("newActive") or self.deck.getVar("newInactive"): - new = labels[1] - else: - new = labels[0] - self.form.newCategoryLabel.setText(new) - if self.deck.getVar("revActive") or self.deck.getVar("revInactive"): - rev = labels[1] - else: - rev = labels[0] - self.form.revCategoryLabel.setText(rev) - - def updateStudyStats(self): - self.form.buttonStack.hide() - self.deck.reset() - self.updateActives() - wasReached = self.deck.timeboxReached() - sessionColour = '%s' - cardColour = '%s' - # top label - h = {} - h['ret'] = cardColour % (self.deck.revCount+self.deck.failedSoonCount) - h['new'] = cardColour % self.deck.newCount - h['newof'] = str(self.deck.newCountAll()) - # counts & time for today - todayStart = self.deck.failedCutoff - 86400 - sql = "select count(), sum(userTime) from revlog" - (reps, time_) = self.deck.db.first( - sql + " where time > :start", start=todayStart) - h['timeToday'] = sessionColour % ( - anki.utils.fmtTimeSpan(time_ or 0, short=True, point=1)) - h['repsToday'] = sessionColour % reps - # and yesterday - yestStart = todayStart - 86400 - (reps, time_) = self.deck.db.first( - sql + " where time > :start and time <= :end", - start=yestStart, end=todayStart) - h['timeTodayChg'] = str( - anki.utils.fmtTimeSpan(time_ or 0, short=True, point=1)) - h['repsTodayChg'] = str(reps) - # session counts - limit = self.deck.sessionTimeLimit - start = self.deck.sessionStartTime or time.time() - limit - start2 = self.deck.lastSessionStart or start - limit - last10 = self.deck.db.scalar( - "select count(*) from revlog where time >= :t", - t=start) - last20 = self.deck.db.scalar( - "select count(*) from revlog where " - "time >= :t and time < :t2", - t=start2, t2=start) - h['repsInSes'] = sessionColour % last10 - h['repsInSesChg'] = str(last20) - h['cs_header'] = "" + _("Cards/session:") + "" - h['cd_header'] = "" + _("Cards/day:") + "" - h['td_header'] = "" + _("Time/day:") + "" - h['rd_header'] = "" + _("Reviews due:") + "" - h['ntod_header'] = "" + _("New today:") + "" - h['ntot_header'] = "" + _("New total:") + "" - stats1 = ("""\ - - -
%(cs_header)s%(repsInSesChg)s%(repsInSes)s
-
- - - - - -
-%(cd_header)s%(repsTodayChg)s%(repsToday)s
%(td_header)s%(timeTodayChg)s%(timeToday)s
""") % h - - stats2 = ("""\ - - - - -
%(rd_header)s%(ret)s
%(ntod_header)s%(new)s
%(ntot_header)s%(newof)s
""") % h - self.form.optionsLabel.setText("""\ -

-%s - -

%s
""" % (stats1, stats2)) - h['tt_header'] = _("Session Statistics") - h['cs_tip'] = _("The number of cards you studied in the current \ -session (blue) and previous session (black)") - h['cd_tip'] = _("The number of cards you studied today (blue) and \ -yesterday (black)") - h['td_tip'] = _("The number of minutes you studied today (blue) and \ -yesterday (black)") - h['rd_tip'] = _("The number of cards that are waiting to be reviewed \ -today") - h['ntod_tip'] = _("The number of new cards that are waiting to be \ -learnt today") - h['ntot_tip'] = _("The total number of new cards in the deck") - statToolTip = ("""

%(tt_header)s

-
%(cs_header)s
%(cs_tip)s
-
%(cd_header)s
%(cd_tip)s
-
%(td_header)s
%(td_tip)s
-
%(rd_header)s
%(rd_tip)s
-
%(ntod_header)s
%(ntod_tip)s
-
%(ntot_header)s
%(ntot_tip)s<
""") % h - - self.form.optionsLabel.setToolTip(statToolTip) - - def showStudyScreen(self): - # forget last card - self.lastCard = None - self.switchToStudyScreen() - self.updateStudyStats() - self.form.startReviewingButton.setFocus() - self.setupStudyOptions() - self.form.studyOptionsFrame.setMaximumWidth(500) - self.form.studyOptionsFrame.show() - - def setupStudyOptions(self): - self.form.newPerDay.setText(str(self.deck.newCardsPerDay)) - lim = self.deck.sessionTimeLimit/60 - if int(lim) == lim: - lim = int(lim) - self.form.minuteLimit.setText(str(lim)) - self.form.questionLimit.setText(str(self.deck.sessionRepLimit)) - self.form.newCardOrder.setCurrentIndex(self.deck.newCardOrder) - self.form.newCardScheduling.setCurrentIndex(self.deck.newCardSpacing) - self.form.revCardOrder.setCurrentIndex(self.deck.revCardOrder) - self.form.failedCardsOption.clear() - if self.deck.getFailedCardPolicy() == 5: - labels = failedCardOptionLabels().values() - else: - labels = failedCardOptionLabels().values()[0:-1] - self.form.failedCardsOption.insertItems(0, labels) - self.form.failedCardsOption.setCurrentIndex(self.deck.getFailedCardPolicy()) - self.form.failedCardMax.setText(unicode(self.deck.failedCardMax)) - - def onStartReview(self): - def uf(obj, field, value): - if getattr(obj, field) != value: - setattr(obj, field, value) - self.deck.flushMod() - self.form.studyOptionsFrame.hide() - # make sure the size is updated before button stack shown - self.app.processEvents() - uf(self.deck, 'newCardSpacing', - self.form.newCardScheduling.currentIndex()) - uf(self.deck, 'revCardOrder', - self.form.revCardOrder.currentIndex()) - pol = self.deck.getFailedCardPolicy() - if (pol != 5 and pol != - self.form.failedCardsOption.currentIndex()): - self.deck.setFailedCardPolicy( - self.form.failedCardsOption.currentIndex()) - self.deck.flushMod() - self.deck.reset() - if not self.deck.finishScheduler: - self.deck.startTimebox() - self.config['studyOptionsTab'] = self.form.tabWidget.currentIndex() - self.moveToState("getQuestion") - - def onStudyOptions(self): - if self.state == "studyScreen": - pass - else: - self.moveToState("studyScreen") + self.mw.progress.finish() diff --git a/designer/groupconf.ui b/designer/groupconf.ui new file mode 100644 index 000000000..d9981fcd2 --- /dev/null +++ b/designer/groupconf.ui @@ -0,0 +1,554 @@ + + + Dialog + + + + 0 + 0 + 453 + 325 + + + + + + + 0 + + + + New Cards + + + + + + + + Graduating interval + + + + + + + + + + + 0 + 0 + + + + days + + + + + + + Easy + + + + + + + + + + days + + + + + + + Easy on first sight + + + + + + + + + + days + + + + + + + Starting factor + + + + + + + + + + Steps (in minutes) + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Lapses + + + + + + + + Steps (in minutes) + + + + + + + + + + Interval multiplier + + + + + + + 1.000000000000000 + + + 0.050000000000000 + + + + + + + Leech threshold + + + + + + + + + + + 0 + 0 + + + + lapses + + + + + + + Leech action + + + + + + + Automatically relearn lapsed cards + + + + + + + 1 + + + 99 + + + + + + + Minimum interval + + + + + + + days + + + + + + + + + + Suspend Card + + + + + Tag Only + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 72 + + + + + + + + + Reviews + + + + + + + + Space siblings by up to + + + + + + + + + + + 0 + 0 + + + + % + + + + + + + Minimum sibling range + + + + + + + days + + + + + + + Easy bonus + + + + + + + x + + + + + + + 1 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 152 + + + + + + + + + Cramming + + + + + + + + Steps (in minutes) + + + + + + + + + + Lapse interval multiplier + + + + + + + 1.000000000000000 + + + 0.050000000000000 + + + + + + + Reset interval of cards failed during cram + + + + + + + 1 + + + + + + + Minimal interval + + + + + + + Boost regular interval + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 93 + + + + + + + + + General + + + + + + + + Ignore answer times longer than + + + + + + + + + + seconds + + + + + + + + + Qt::Vertical + + + + 20 + 199 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + tabWidget + lrnSteps + lrnGradInt + lrnEasyInt + lrnFirstInt + lrnFactor + lapSteps + lapMult + lapMinInt + leechThreshold + leechAction + lapRelearn + revSpace + revMinSpace + easyBonus + cramSteps + cramBoost + cramReset + cramMult + cramMinInt + maxTaken + buttonBox + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/studyopts.ui b/designer/studyopts.ui index 708208572..0b0c75e6f 100644 --- a/designer/studyopts.ui +++ b/designer/studyopts.ui @@ -6,117 +6,168 @@ 0 0 - 279 - 271 + 435 + 334 Dialog - + - - - - - Next day starts at - - + + + + + + + Next day starts at + + + + + + + + 60 + 16777215 + + + + 23 + + + + + + + Learn ahead limit + + + + + + + + 60 + 16777215 + + + + + + + + mins + + + + + + + New cards/day + + + + + + + 999 + + + + + + + + + + + + + + + + Timebox question limit + + + + + + + Timebox time limit + + + + + + + 9999 + + + + + + + 9999 + + + + + + + mins + + + + - - - + + + + Qt::Vertical + + - 60 - 16777215 + 20 + 40 - + - - + + - AM + Group Settings... - - - - Collapse learning by up to - - - - - - - - 60 - 16777215 - - - - - - - - mins - - - - - - - New cards/day - - - - - - - - - - - - - - - - - - Group Settings... - - - - - + Qt::Vertical - - - 20 - 40 - - - - - - - - Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + newPerDay + newOrder + newSpread + revOrder + dayOffset + lrnCutoff + questionLimit + timeLimit + groupSettings + buttonBox + diff --git a/tools/build_ui.sh b/tools/build_ui.sh index 98ba03cd8..753330f95 100755 --- a/tools/build_ui.sh +++ b/tools/build_ui.sh @@ -17,14 +17,14 @@ pyrcc=`which pyrcc4` if [ $? != 0 ]; then if [ xDarwin = x$(uname) ] then - if [ -e /Library/Frameworks/Python.framework/Versions/2.5/bin/pyuic4 ] + if [ -e /Library/Frameworks/Python.framework/Versions/2.7/bin/pyuic4 ] then - pyuic=/Library/Frameworks/Python.framework/Versions/2.5/bin/pyuic4 - pyrcc=/Library/Frameworks/Python.framework/Versions/2.5/bin/pyrcc4 - elif [ -e /opt/local/Library/Frameworks/Python.framework/Versions/2.5/bin/pyuic4 ] + pyuic=/Library/Frameworks/Python.framework/Versions/2.7/bin/pyuic4 + pyrcc=/Library/Frameworks/Python.framework/Versions/2.7/bin/pyrcc4 + elif [ -e /opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/pyuic4 ] then - pyuic=/opt/local/Library/Frameworks/Python.framework/Versions/2.5/bin/pyuic4 - pyrcc=/opt/local/Library/Frameworks/Python.framework/Versions/2.5/bin/pyrcc4 + pyuic=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/pyuic4 + pyrcc=/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/pyrcc4 elif [ -e /System/Library/Frameworks/Python.framework/Versions/2.6/bin/pyuic4 ] then pyuic=/System/Library/Frameworks/Python.framework/Versions/2.6/bin/pyuic4