mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
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:
parent
8a4fbcc430
commit
afde11671e
10 changed files with 147 additions and 250 deletions
|
@ -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]
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
115
anki/sched.py
115
anki/sched.py
|
@ -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
|
|
||||||
(id, due) = 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
|
self.newCount -= 1
|
||||||
return self.col.getCard(id)
|
return self.col.getCard(self._newQueue.pop())
|
||||||
|
|
||||||
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
|
|
||||||
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
|
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
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue