rework sibling handling and change bury semantics

First, burying changes:

- unburying now happens on day rollover, or when manually unburying from
  overview screen

- burying is not performed when returning to deck list, or when closing
  collection, so burying now must mark cards as modified to ensure sync
  consistent

- because they're no longer temporary to a session, make sure we exclude them
  in filtered decks in -is:suspended

Sibling spacing changes:

- core behaviour now based on automatically burying related cards when we
  answer a card

- applies to reviews, optionally to new cards, and never to cards in the
  learning queue (partly because we can't suspend/bury cards in that queue at
  the moment)

- this means spacing works consistently in filtered decks now, works on
  reviews even when user is late to review, and provides better separation of
  new cards

- if burying new cards disabled, we just discard them from the current queue.
  an option to set due=ord*space+due would be nicer, but would require
  changing a lot of code and is more appropriate for a future major version
  change. discarding from queue suffers from the same issue as the new card
  cycling in that queue rebuilds may cause cards to be shown close together,
  so the default burying behaviour is preferable

- refer to them as 'related cards' rather than 'siblings'

These changes don't require any changes to the database format, so they
should hopefully coexist with older clients without issue.
This commit is contained in:
Damien Elmes 2013-08-10 15:54:33 +09:00
parent 8a4fbcc430
commit afde11671e
10 changed files with 147 additions and 250 deletions

View file

@ -57,8 +57,6 @@ class _Collection(object):
d += datetime.timedelta(hours=4) d += datetime.timedelta(hours=4)
self.crt = int(time.mktime(d.timetuple())) self.crt = int(time.mktime(d.timetuple()))
self.sched = Scheduler(self) self.sched = Scheduler(self)
# check for improper shutdown
self.cleanup()
def name(self): def name(self):
n = os.path.splitext(os.path.basename(self.path))[0] n = os.path.splitext(os.path.basename(self.path))[0]
@ -71,7 +69,7 @@ class _Collection(object):
(self.crt, (self.crt,
self.mod, self.mod,
self.scm, self.scm,
self.dty, self.dty, # no longer used
self._usn, self._usn,
self.ls, self.ls,
self.conf, self.conf,
@ -131,7 +129,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
def close(self, save=True): def close(self, save=True):
"Disconnect from DB." "Disconnect from DB."
if self.db: if self.db:
self.cleanup()
if save: if save:
self.save() self.save()
else: else:
@ -166,16 +163,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
"True if schema changed since last sync." "True if schema changed since last sync."
return self.scm > self.ls return self.scm > self.ls
def setDirty(self):
"Signal there are temp. suspended cards that need cleaning up on close."
self.dty = True
def cleanup(self):
"Unsuspend any temporarily suspended cards."
if self.dty:
self.sched.unburyCards()
self.dty = False
def usn(self): def usn(self):
return self._usn if self.server else -1 return self._usn if self.server else -1
@ -610,6 +597,10 @@ where c.nid == f.id
"select id from revlog where cid = ? " "select id from revlog where cid = ? "
"order by id desc limit 1", c.id) "order by id desc limit 1", c.id)
self.db.execute("delete from revlog where id = ?", last) self.db.execute("delete from revlog where id = ?", last)
# restore any siblings
self.db.execute(
"update cards set queue=type,mod=?,usn=? where queue=-2 and nid=?",
intTime(), self.usn(), c.nid)
# and finally, update daily counts # and finally, update daily counts
n = 1 if c.queue == 3 else c.queue n = 1 if c.queue == 3 else c.queue
type = ("new", "lrn", "rev")[n] type = ("new", "lrn", "rev")[n]

View file

@ -53,6 +53,8 @@ defaultConf = {
'separate': True, 'separate': True,
'order': NEW_CARDS_DUE, 'order': NEW_CARDS_DUE,
'perDay': 20, 'perDay': 20,
# may not be set on old decks
'bury': True,
}, },
'lapse': { 'lapse': {
'delays': [10], 'delays': [10],
@ -66,7 +68,7 @@ defaultConf = {
'perDay': 100, 'perDay': 100,
'ease4': 1.3, 'ease4': 1.3,
'fuzz': 0.05, 'fuzz': 0.05,
'minSpace': 1, 'minSpace': 1, # not currently used
'ivlFct': 1, 'ivlFct': 1,
'maxIvl': 36500, 'maxIvl': 36500,
}, },

View file

@ -257,7 +257,7 @@ select distinct(n.id) from cards c, notes n where c.nid=n.id and """+preds
return "queue in (1, 3)" return "queue in (1, 3)"
return "type = %d" % n return "type = %d" % n
elif val == "suspended": elif val == "suspended":
return "c.queue = -1" return "c.queue in (-1, -2)"
elif val == "due": elif val == "due":
return """ return """
(c.queue in (2,3) and c.due <= %d) or (c.queue in (2,3) and c.due <= %d) or

View file

@ -51,6 +51,7 @@ class Scheduler(object):
def answerCard(self, card, ease): def answerCard(self, card, ease):
assert ease >= 1 and ease <= 4 assert ease >= 1 and ease <= 4
self.col.markReview(card) self.col.markReview(card)
self._burySiblings(card)
card.reps += 1 card.reps += 1
# former is for logging new cards, latter also covers filt. decks # former is for logging new cards, latter also covers filt. decks
card.wasNew = card.type == 0 card.wasNew = card.type == 0
@ -132,11 +133,11 @@ order by due""" % self._deckLimit(),
return 3 return 3
def unburyCards(self): def unburyCards(self):
"Unbury cards when closing." "Unbury cards."
mod = self.col.db.mod self.col.conf['lastUnburied'] = self.today
self.col.setMod()
self.col.db.execute( self.col.db.execute(
"update cards set queue = type where queue = -2") "update cards set queue = type where queue = -2")
self.col.db.mod = mod
# Rev/lrn/time daily stats # Rev/lrn/time daily stats
########################################################################## ##########################################################################
@ -298,7 +299,9 @@ order by due""" % self._deckLimit(),
return c return c
# new first, or time for one? # new first, or time for one?
if self._timeForNewCard(): if self._timeForNewCard():
return self._getNewCard() c = self._getNewCard()
if c:
return c
# card due for review? # card due for review?
c = self._getRevCard() c = self._getRevCard()
if c: if c:
@ -339,30 +342,20 @@ did = ? and queue = 0 limit ?)""", did, lim)
lim = min(self.queueLimit, self._deckNewLimit(did)) lim = min(self.queueLimit, self._deckNewLimit(did))
if lim: if lim:
# fill the queue with the current did # fill the queue with the current did
self._newQueue = self.col.db.all(""" self._newQueue = self.col.db.list("""
select id, due from cards where did = ? and queue = 0 limit ?""", did, lim) select id from cards where did = ? and queue = 0 limit ?""", did, lim)
if self._newQueue: if self._newQueue:
self._newQueue.reverse() self._newQueue.reverse()
return True return True
# nothing left in the deck; move to next # nothing left in the deck; move to next
self._newDids.pop(0) self._newDids.pop(0)
# if count>0 but queue empty, the other cards were buried
self.newCount = 0
def _getNewCard(self): def _getNewCard(self):
if not self._fillNew(): if self._fillNew():
return self.newCount -= 1
(id, due) = self._newQueue.pop() return self.col.getCard(self._newQueue.pop())
# move any siblings to the end?
conf = self.col.decks.confForDid(self._newDids[0])
if conf['dyn'] or conf['new']['separate']:
n = len(self._newQueue)
while self._newQueue and self._newQueue[-1][1] == due:
self._newQueue.insert(0, self._newQueue.pop())
n -= 1
if not n:
# we only have one note in the queue; stop rotating
break
self.newCount -= 1
return self.col.getCard(id)
def _updateNewCardRatio(self): def _updateNewCardRatio(self):
if self.col.conf['newSpread'] == NEW_CARDS_DISTRIBUTE: if self.col.conf['newSpread'] == NEW_CARDS_DISTRIBUTE:
@ -761,6 +754,8 @@ did = ? and queue = 2 and due <= ? limit ?""",
return True return True
# nothing left in the deck; move to next # nothing left in the deck; move to next
self._revDids.pop(0) self._revDids.pop(0)
# if count>0 but queue empty, the other cards were buried
self.revCount = 0
def _getRevCard(self): def _getRevCard(self):
if self._fillRev(): if self._fillRev():
@ -904,37 +899,13 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
return max(0, self.today - due) return max(0, self.today - due)
def _updateRevIvl(self, card, ease): def _updateRevIvl(self, card, ease):
"Update CARD's interval, trying to avoid siblings."
idealIvl = self._nextRevIvl(card, ease) idealIvl = self._nextRevIvl(card, ease)
card.ivl = self._adjRevIvl(card, idealIvl) card.ivl = self._adjRevIvl(card, idealIvl)
def _adjRevIvl(self, card, idealIvl): def _adjRevIvl(self, card, idealIvl):
"Given IDEALIVL, return an IVL away from siblings."
if self._spreadRev: if self._spreadRev:
idealIvl = self._fuzzedIvl(idealIvl) idealIvl = self._fuzzedIvl(idealIvl)
idealDue = self.today + idealIvl return idealIvl
conf = self._revConf(card)
# find sibling positions
dues = self.col.db.list(
"select due from cards where nid = ? and type = 2"
" and id != ?", card.nid, card.id)
if not dues or idealDue not in dues:
return idealIvl
else:
leeway = max(conf['minSpace'], int(idealIvl * conf['fuzz']))
fudge = 0
# do we have any room to adjust the interval?
if leeway:
# loop through possible due dates for an empty one
for diff in range(1, leeway+1):
# ensure we're due at least tomorrow
if idealIvl - diff >= 1 and (idealDue - diff) not in dues:
fudge = -diff
break
elif (idealDue + diff) not in dues:
fudge = diff
break
return idealIvl + fudge
# Dynamic deck handling # Dynamic deck handling
########################################################################## ##########################################################################
@ -1136,6 +1107,10 @@ did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
g[key] = [self.today, 0] g[key] = [self.today, 0]
for deck in self.col.decks.all(): for deck in self.col.decks.all():
update(deck) update(deck)
# unbury if the day has rolled over
unburied = self.col.conf.get("lastUnburied", 0)
if unburied < self.today:
self.unburyCards()
def _checkDay(self): def _checkDay(self):
# check if the day has rolled over # check if the day has rolled over
@ -1152,17 +1127,22 @@ did = ?, queue = %s, due = ?, mod = ?, usn = ? where id = ?""" % queue, data)
def _nextDueMsg(self): def _nextDueMsg(self):
line = [] line = []
# the new line replacements are so we don't break translations
# in a point release
if self.revDue(): if self.revDue():
line.append(_("""\ line.append(_("""\
Today's review limit has been reached, but there are still cards Today's review limit has been reached, but there are still cards
waiting to be reviewed. For optimum memory, consider increasing waiting to be reviewed. For optimum memory, consider increasing
the daily limit in the options.""")) the daily limit in the options.""").replace("\n", " "))
if self.newDue(): if self.newDue():
line.append(_("""\ line.append(_("""\
There are more new cards available, but the daily limit has been There are more new cards available, but the daily limit has been
reached. You can increase the limit in the options, but please reached. You can increase the limit in the options, but please
bear in mind that the more new cards you introduce, the higher bear in mind that the more new cards you introduce, the higher
your short-term review workload will become.""")) your short-term review workload will become.""").replace("\n", " "))
if self.haveBuried():
line.append(_("""\
Some related or buried cards were delayed until tomorrow.""").replace("\n", " "))
if self.haveCustomStudy and not self.col.decks.current()['dyn']: if self.haveCustomStudy and not self.col.decks.current()['dyn']:
line.append(_("""\ line.append(_("""\
To study outside of the normal schedule, click the Custom Study button below.""")) To study outside of the normal schedule, click the Custom Study button below."""))
@ -1181,6 +1161,12 @@ To study outside of the normal schedule, click the Custom Study button below."""
("select 1 from cards where did in %s and queue = 0 " ("select 1 from cards where did in %s and queue = 0 "
"limit 1") % self._deckLimit()) "limit 1") % self._deckLimit())
def haveBuried(self):
sdids = ids2str(self.col.decks.active())
cnt = self.col.db.scalar(
"select 1 from cards where queue = -2 and did in %s limit 1" % sdids)
return not not cnt
# Next time reports # Next time reports
########################################################################## ##########################################################################
@ -1251,11 +1237,44 @@ To study outside of the normal schedule, click the Custom Study button below."""
def buryNote(self, nid): def buryNote(self, nid):
"Bury all cards for note until next session." "Bury all cards for note until next session."
self.col.setDirty()
cids = self.col.db.list( cids = self.col.db.list(
"select id from cards where nid = ? and queue >= 0", nid) "select id from cards where nid = ? and queue >= 0", nid)
self.removeLrn(cids) self.removeLrn(cids)
self.col.db.execute("update cards set queue = -2 where id in "+ids2str(cids)) self.col.db.execute("""
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
intTime(), self.col.usn())
# Sibling spacing
##########################################################################
def _burySiblings(self, card):
toBury = []
conf = self._newConf(card)
buryNew = conf.get("bury", True)
# loop through and remove from queues
for cid,queue in self.col.db.execute("""
select id, queue from cards where nid=? and id!=?
and (queue=0 or (queue=2 and due<=?))""",
card.nid, card.id, self.today):
if queue == 2:
toBury.append(cid)
try:
self._revQueue.remove(cid)
except ValueError:
pass
else:
# if bury disabled, we still discard to give same-day spacing
if buryNew:
toBury.append(cid)
try:
self._newQueue.remove(cid)
except ValueError:
pass
queue = "(queue = 0 or %s)" % queue
# then bury
self.col.db.execute(
"update cards set queue=-2,mod=?,usn=? where id in "+ids2str(toBury),
intTime(), self.col.usn())
# Resetting # Resetting
########################################################################## ##########################################################################

View file

@ -137,7 +137,6 @@ body { margin: 1em; -webkit-user-select: none; }
def _renderPage(self, reuse=False): def _renderPage(self, reuse=False):
css = self.mw.sharedCSS + self._css css = self.mw.sharedCSS + self._css
if not reuse: if not reuse:
self.mw.col.sched.unburyCards()
self._dueTree = self.mw.col.sched.deckDueTree() self._dueTree = self.mw.col.sched.deckDueTree()
tree = self._renderDeckTree(self._dueTree) tree = self._renderDeckTree(self._dueTree)
stats = self._renderStats() stats = self._renderStats()

View file

@ -180,13 +180,11 @@ class DeckConf(QDialog):
f.lrnFactor.setValue(c['initialFactor']/10.0) f.lrnFactor.setValue(c['initialFactor']/10.0)
f.newOrder.setCurrentIndex(c['order']) f.newOrder.setCurrentIndex(c['order'])
f.newPerDay.setValue(c['perDay']) f.newPerDay.setValue(c['perDay'])
f.separate.setChecked(c['separate']) f.bury.setChecked(c.get("bury", True))
f.newplim.setText(self.parentLimText('new')) f.newplim.setText(self.parentLimText('new'))
# rev # rev
c = self.conf['rev'] c = self.conf['rev']
f.revPerDay.setValue(c['perDay']) f.revPerDay.setValue(c['perDay'])
f.revSpace.setValue(c['fuzz']*100)
f.revMinSpace.setValue(c['minSpace'])
f.easyBonus.setValue(c['ease4']*100) f.easyBonus.setValue(c['ease4']*100)
f.fi1.setValue(c['ivlFct']*100) f.fi1.setValue(c['ivlFct']*100)
f.maxIvl.setValue(c['maxIvl']) f.maxIvl.setValue(c['maxIvl'])
@ -259,7 +257,7 @@ class DeckConf(QDialog):
c['initialFactor'] = f.lrnFactor.value()*10 c['initialFactor'] = f.lrnFactor.value()*10
c['order'] = f.newOrder.currentIndex() c['order'] = f.newOrder.currentIndex()
c['perDay'] = f.newPerDay.value() c['perDay'] = f.newPerDay.value()
c['separate'] = f.separate.isChecked() c['bury'] = f.bury.isChecked()
if self._origNewOrder != c['order']: if self._origNewOrder != c['order']:
# order of current deck has changed, so have to resort # order of current deck has changed, so have to resort
if c['order'] == NEW_CARDS_RANDOM: if c['order'] == NEW_CARDS_RANDOM:
@ -269,8 +267,6 @@ class DeckConf(QDialog):
# rev # rev
c = self.conf['rev'] c = self.conf['rev']
c['perDay'] = f.revPerDay.value() c['perDay'] = f.revPerDay.value()
c['fuzz'] = f.revSpace.value()/100.0
c['minSpace'] = f.revMinSpace.value()
c['ease4'] = f.easyBonus.value()/100.0 c['ease4'] = f.easyBonus.value()/100.0
c['ivlFct'] = f.fi1.value()/100.0 c['ivlFct'] = f.fi1.value()/100.0
c['maxIvl'] = f.maxIvl.value() c['maxIvl'] = f.maxIvl.value()

View file

@ -56,6 +56,9 @@ class Overview(object):
openLink(aqt.appShared+"info/%s?v=%s"%(self.sid, self.sidVer)) openLink(aqt.appShared+"info/%s?v=%s"%(self.sid, self.sidVer))
elif url == "studymore": elif url == "studymore":
self.onStudyMore() self.onStudyMore()
elif url == "unbury":
self.mw.col.sched.unburyCards()
self.mw.reset()
elif url.lower().startswith("http"): elif url.lower().startswith("http"):
openLink(url) openLink(url)
@ -72,6 +75,9 @@ class Overview(object):
self.mw.reset() self.mw.reset()
if key == "c" and not cram: if key == "c" and not cram:
self.onStudyMore() self.onStudyMore()
if key == "u":
self.mw.col.sched.unburyCards()
self.mw.reset()
# HTML # HTML
############################################################ ############################################################
@ -185,6 +191,8 @@ text-align: center;
else: else:
links.append(["C", "studymore", _("Custom Study")]) links.append(["C", "studymore", _("Custom Study")])
#links.append(["F", "cram", _("Filter/Cram")]) #links.append(["F", "cram", _("Filter/Cram")])
if self.mw.col.sched.haveBuried():
links.append(["U", "unbury", _("Unbury")])
buf = "" buf = ""
for b in links: for b in links:
if b[0]: if b[0]:

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>481</width> <width>494</width>
<height>419</height> <height>454</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -83,6 +83,51 @@
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="5" column="2">
<widget class="QLabel" name="label_27">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Starting ease</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="lrnFactor">
<property name="minimum">
<number>130</number>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Order</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="lrnEasyInt">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="lrnGradInt">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="2"> <item row="2" column="2">
<widget class="QLabel" name="newplim"> <widget class="QLabel" name="newplim">
<property name="text"> <property name="text">
@ -132,9 +177,9 @@
<widget class="QComboBox" name="newOrder"/> <widget class="QComboBox" name="newOrder"/>
</item> </item>
<item row="6" column="0" colspan="3"> <item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="separate"> <widget class="QCheckBox" name="bury">
<property name="text"> <property name="text">
<string>Try not to show sibling cards next to each other</string> <string>Bury related new cards until the next day</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -158,51 +203,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QSpinBox" name="lrnEasyInt">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="lrnGradInt">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Order</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="lrnFactor">
<property name="minimum">
<number>130</number>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Starting ease</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="label_27">
<property name="text">
<string>%</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -227,58 +227,14 @@
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_20"> <widget class="QLabel" name="label_20">
<property name="text"> <property name="text">
<string>Easy bonus</string> <string>Easy bonus</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2">
<widget class="QLabel" name="label_19">
<property name="text">
<string>days</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Minimum sibling range</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_18">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>%</string>
</property>
</widget>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="revSpace">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Space siblings by up to</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="easyBonus"> <widget class="QSpinBox" name="easyBonus">
<property name="minimum"> <property name="minimum">
<number>100</number> <number>100</number>
@ -291,23 +247,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2"> <item row="1" column="2">
<widget class="QLabel" name="label_21"> <widget class="QLabel" name="label_21">
<property name="text"> <property name="text">
<string>%</string> <string>%</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="2"> <item row="2" column="2">
<widget class="QLabel" name="label_34"> <widget class="QLabel" name="label_34">
<property name="text"> <property name="text">
<string>%</string> <string>%</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QSpinBox" name="revMinSpace"/>
</item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QSpinBox" name="revPerDay"> <widget class="QSpinBox" name="revPerDay">
<property name="minimum"> <property name="minimum">
@ -325,7 +278,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_33"> <widget class="QLabel" name="label_33">
<property name="text"> <property name="text">
<string>Interval modifier</string> <string>Interval modifier</string>
@ -339,7 +292,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="2" column="1">
<widget class="QDoubleSpinBox" name="fi1"> <widget class="QDoubleSpinBox" name="fi1">
<property name="decimals"> <property name="decimals">
<number>0</number> <number>0</number>
@ -358,14 +311,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Maximum interval</string> <string>Maximum interval</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="maxIvl"> <widget class="QSpinBox" name="maxIvl">
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
@ -375,7 +328,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="2"> <item row="3" column="2">
<widget class="QLabel" name="label_23"> <widget class="QLabel" name="label_23">
<property name="text"> <property name="text">
<string>days</string> <string>days</string>
@ -658,10 +611,8 @@
<tabstop>lrnGradInt</tabstop> <tabstop>lrnGradInt</tabstop>
<tabstop>lrnEasyInt</tabstop> <tabstop>lrnEasyInt</tabstop>
<tabstop>lrnFactor</tabstop> <tabstop>lrnFactor</tabstop>
<tabstop>separate</tabstop> <tabstop>bury</tabstop>
<tabstop>revPerDay</tabstop> <tabstop>revPerDay</tabstop>
<tabstop>revSpace</tabstop>
<tabstop>revMinSpace</tabstop>
<tabstop>easyBonus</tabstop> <tabstop>easyBonus</tabstop>
<tabstop>fi1</tabstop> <tabstop>fi1</tabstop>
<tabstop>maxIvl</tabstop> <tabstop>maxIvl</tabstop>

View file

@ -769,76 +769,6 @@ def test_cram_resched():
# d.sched.answerCard(c, 2) # d.sched.answerCard(c, 2)
# print c.__dict__ # print c.__dict__
def test_adjIvl():
d = getEmptyDeck()
d.sched._spreadRev = False
# add two more templates and set second active
m = d.models.current(); mm = d.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
t = d.models.newTemplate(m)
t['name'] = "f2"
t['qfmt'] = "{{Front}}"
t['afmt'] = "{{Back}}"
d.models.addTemplate(m, t)
t = d.models.newTemplate(m)
t['name'] = "f3"
t['qfmt'] = "{{Front}}"
t['afmt'] = "{{Back}}"
d.models.addTemplate(m, t)
d.models.save(m)
# create a new note; it should have 4 cards
f = d.newNote()
f['Front'] = "1"; f['Back'] = "1"
d.addNote(f)
assert d.cardCount() == 4
d.reset()
# immediately remove first; it should get ideal ivl
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 4
# with the default settings, second card should be -1
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 3
# and third +1
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 5
# fourth exceeds default settings, so gets ideal again
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 4
# try again with another note
f = d.newNote()
f['Front'] = "2"; f['Back'] = "2"
d.addNote(f)
d.reset()
# set a minSpacing of 0
conf = d.sched._cardConf(c)
conf['rev']['minSpace'] = 0
# first card gets ideal
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 4
# and second too, because it's below the threshold
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 4
# if we increase the ivl minSpace isn't needed
conf['new']['ints'][1] = 20
# ideal..
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 20
# adjusted
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 19
def test_ordcycle(): def test_ordcycle():
d = getEmptyDeck() d = getEmptyDeck()
# add two more templates and set second active # add two more templates and set second active

View file

@ -61,6 +61,7 @@ def test_review():
assert c.left != 1001 assert c.left != 1001
assert not d.undoName() assert not d.undoName()
# we should be able to undo multiple answers too # we should be able to undo multiple answers too
f = d.newNote()
f['Front'] = u"two" f['Front'] = u"two"
d.addNote(f) d.addNote(f)
d.reset() d.reset()