diff --git a/anki/deck.py b/anki/deck.py index 17f9e99a8..32a080980 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -20,19 +20,12 @@ import anki.cards, anki.facts, anki.template, anki.cram, anki.find defaultConf = { # scheduling options - 'groups': [], - 'newPerDay': 20, - 'newToday': [0, 0], # currentDay, count - 'newTodayOrder': NEW_TODAY_ORD, - 'newOrder': NEW_CARDS_DUE, - 'newSpread': NEW_CARDS_DISTRIBUTE, + 'activeGroups': [], + 'topGroup': 1, + 'curGroup': None, 'revOrder': REV_CARDS_RANDOM, - 'collapseTime': 1200, - 'repLim': 0, - 'timeLim': 600, # other config 'nextPos': 1, - 'mediaURL': "", 'fontFamilies': [ [u'MS 明朝',u'ヒラギノ明朝 Pro W3',u'Kochi Mincho', u'東風明朝'] ], @@ -228,7 +221,7 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""", return 0 fact.flush() # randomize? - if self.randomNew(): + if self.models.randomNew(): due = self._randPos() else: due = self.nextID("pos") @@ -284,7 +277,7 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""", "Generate cards for templates if cards not empty. Return cards." cards = [] # if random mode, determine insertion point - if self.randomNew(): + if self.models.randomNew(): # if this fact has existing new cards, use their due time due = self.db.scalar( "select due from cards where fid = ? and queue = 0", fact.id) @@ -331,9 +324,6 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""", card.flush() return card - def randomNew(self): - return self.conf['newOrder'] == NEW_CARDS_RANDOM - # Cards ########################################################################## diff --git a/anki/groups.py b/anki/groups.py index d75e3e397..1a27bbe0e 100644 --- a/anki/groups.py +++ b/anki/groups.py @@ -4,7 +4,27 @@ import simplejson from anki.utils import intTime +from anki.consts import * +# fixmes: +# - make sure lists like new[delays] are not being shared by multiple groups +# - make sure all children have parents (create as necessary) +# - when renaming a group, top level properties should be added or removed as +# appropriate + +# configuration only available to top level groups +defaultTopConf = { + 'newPerDay': 20, + 'newToday': [0, 0], # currentDay, count + 'newTodayOrder': NEW_TODAY_ORD, + 'newSpread': NEW_CARDS_DISTRIBUTE, + 'collapseTime': 1200, + 'repLim': 0, + 'timeLim': 600, + 'curModel': None, +} + +# configuration available to all groups defaultConf = { 'new': { 'delays': [1, 10], @@ -33,11 +53,7 @@ defaultConf = { 'minSpace': 1, }, 'maxTaken': 60, -} - -defaultData = { - 'activeTags': None, - 'inactiveTags': None, + 'mod': 0, } class GroupManager(object): @@ -54,6 +70,7 @@ class GroupManager(object): self.changed = False def save(self, g): + "Can be called with either a group or a group configuration." g['mod'] = intTime() self.changed = True @@ -73,12 +90,20 @@ class GroupManager(object): return int(id) if not create: return None - g = dict(name=name, conf=1, mod=intTime()) + if "::" not in name: + # if it's a top level group, it gets the top level config + g = defaultTopConf.copy() + else: + # not top level. calling code should ensure parents already exist? + g = {} + g['name'] = name + g['conf'] = 1 while 1: - id = str(intTime(1000)) - if id in self.groups: + id = intTime(1000) + if str(id) in self.groups: continue - self.groups[id] = g + g['id'] = id + self.groups[str(id)] = g self.save(g) return int(id) @@ -89,11 +114,15 @@ class GroupManager(object): self.deck.db.execute("delete from groups where id = ?", gid) print "fixme: loop through models and update stale gid references" - def all(self): + def allNames(self): "An unsorted list of all group names." return [x['name'] for x in self.groups.values()] - # Utils + def all(self): + "A list of all groups." + return self.groups.values() + + # Group utils ############################################################# def name(self, gid): @@ -102,6 +131,46 @@ class GroupManager(object): def conf(self, gid): return self.gconf[str(self.groups[str(gid)]['conf'])] + def get(self, gid): + return self.groups[str(gid)] + def setGroup(self, cids, gid): self.db.execute( "update cards set gid = ? where id in "+ids2str(cids), gid) + + # Group selection + ############################################################# + + def top(self): + "The current top level group as an object, and marks as modified." + g = self.get(self.deck.conf['topGroup']) + self.save(g) + return g + + def active(self): + "The currrently active gids." + return self.deck.conf['activeGroups'] + + def selected(self): + "The currently selected gid, or None if whole collection." + return self.deck.conf['curGroup'] + + def select(self, gid): + "Select a new group. If gid is None, select whole collection." + if not gid: + self.deck.conf['topGroup'] = 1 + self.deck.conf['curGroup'] = None + self.deck.conf['activeGroups'] = [] + return + # save the top level group + name = self.groups[str(gid)]['name'] + path = name.split("::") + self.deck.conf['topGroup'] = self.id(path[0]) + # current group + self.deck.conf['curGroup'] = gid + # and active groups (current + all children) + actv = [gid] + for g in self.all(): + if g['name'].startswith(name + "::"): + actv.append(g['id']) + self.deck.conf['activeGroups'] = actv diff --git a/anki/models.py b/anki/models.py index d94815890..d59a533b3 100644 --- a/anki/models.py +++ b/anki/models.py @@ -6,6 +6,7 @@ import simplejson, copy from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \ timestampID from anki.lang import _ +from anki.consts import * # Models ########################################################################## @@ -16,6 +17,7 @@ defaultModel = { 'sortf': 0, 'gid': 1, 'clozectx': False, + 'newOrder': NEW_CARDS_DUE, 'latexPre': """\ \\documentclass[12pt]{article} \\special{papersize=3in,5in} @@ -87,7 +89,13 @@ class ModelManager(object): def current(self): "Get current model." - return self.get(self.deck.conf['currentModelId']) + try: + return self.get(self.deck.groups.top()['curModel']) + except: + return self.models.values()[0] + + def setCurrent(self, m): + self.deck.groups.top()['curModel'] = m['id'] def get(self, id): "Get model with ID." @@ -117,6 +125,7 @@ class ModelManager(object): def rem(self, m): "Delete model, and all its cards/facts." self.deck.modSchema() + current = self.current()['id'] == m['id'] # delete facts/cards self.deck.remCards(self.deck.db.list(""" select id from cards where fid in (select id from facts where mid = ?)""", @@ -125,14 +134,14 @@ select id from cards where fid in (select id from facts where mid = ?)""", del self.models[m['id']] self.save() # GUI should ensure last model is not deleted - if self.deck.conf['currentModelId'] == m['id']: - self.deck.conf['currentModelId'] = int(self.models.keys()[0]) + if current: + self.setCurrent(self.models.values()[0]) def _add(self, m): self._setID(m) self.models[m['id']] = m self.save(m) - self.deck.conf['currentModelId'] = m['id'] + self.setCurrent(m) return m def _setID(self, m): @@ -155,6 +164,9 @@ select id from cards where fid in (select id from facts where mid = ?)""", return self.deck.db.scalar( "select count() from facts where mid = ?", m['id']) + def randomNew(self): + return self.current()['newOrder'] == NEW_CARDS_RANDOM + # Copying ################################################## diff --git a/anki/sched.py b/anki/sched.py index a4ac45fb5..188f5c3d2 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -49,7 +49,7 @@ class Scheduler(object): # put it in the learn queue card.queue = 1 card.type = 1 - self.deck.conf['newToday'][1] += 1 + self.deck.groups.top()['newToday'][1] += 1 if card.queue == 1: self._answerLrnCard(card, ease) elif card.queue == 2: @@ -98,20 +98,6 @@ order by due""" % self._groupLimit(), # Counts ########################################################################## - def selCounts(self): - "Return counts for selected groups, without building queue." - self._resetCounts() - return self.counts() - - def allCounts(self): - "Return counts for all groups, without building queue." - conf = self.deck.conf['groups'] - if conf: - self.deck.conf['groups'] = [] - self._resetCounts() - self.deck.conf['groups'] = conf - return self.counts() - def _resetCounts(self): self._updateCutoff() self._resetLrnCount() @@ -212,7 +198,7 @@ from cards group by gid""", self.today): # FIXME: need to keep track of reps for timebox and new card introduction def _resetNewCount(self): - l = self.deck.conf + l = self.deck.groups.top() if l['newToday'][0] != self.today: # it's a new day; reset counts l['newToday'] = [self.today, 0] @@ -241,7 +227,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(), if self.newQueue: (id, due) = self.newQueue.pop() # move any siblings to the end? - if self.deck.conf['newTodayOrder'] == NEW_TODAY_ORD: + if self.deck.groups.top()['newTodayOrder'] == NEW_TODAY_ORD: n = len(self.newQueue) while self.newQueue and self.newQueue[-1][1] == due: self.newQueue.insert(0, self.newQueue.pop()) @@ -253,7 +239,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(), return id def _updateNewCardRatio(self): - if self.deck.conf['newSpread'] == NEW_CARDS_DISTRIBUTE: + if self.deck.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE: if self.newCount: self.newCardModulus = ( (self.newCount + self.revCount) / self.newCount) @@ -267,9 +253,9 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(), "True if it's time to display a new card when distributing." if not self.newCount: return False - if self.deck.conf['newSpread'] == NEW_CARDS_LAST: + if self.deck.groups.top()['newSpread'] == NEW_CARDS_LAST: return False - elif self.deck.conf['newSpread'] == NEW_CARDS_FIRST: + elif self.deck.groups.top()['newSpread'] == NEW_CARDS_FIRST: return True elif self.newCardModulus: return self.reps and self.reps % self.newCardModulus == 0 @@ -282,7 +268,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(), select count() from (select id from cards where queue = 1 %s and due < ? limit %d)""" % ( self._groupLimit(), self.reportLimit), - intTime() + self.deck.conf['collapseTime']) + intTime() + self.deck.groups.top()['collapseTime']) def _resetLrn(self): self.lrnQueue = self.deck.db.all(""" @@ -294,7 +280,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) if self.lrnQueue: cutoff = time.time() if collapse: - cutoff += self.deck.conf['collapseTime'] + cutoff += self.deck.groups.top()['collapseTime'] if self.lrnQueue[0][0] < cutoff: id = heappop(self.lrnQueue)[1] self.lrnCount -= 1 @@ -327,7 +313,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) card.due = int(time.time() + delay) heappush(self.lrnQueue, (card.due, card.id)) # if it's due within the cutoff, increment count - if delay <= self.deck.conf['collapseTime']: + if delay <= self.deck.groups.top()['collapseTime']: self.lrnCount += 1 self._logLrn(card, ease, conf, leaving, type) @@ -578,7 +564,7 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % ( return self.deck.groups.conf(card.gid) def _groupLimit(self): - l = self.deck.conf['groups'] + l = self.deck.groups.active() if not l: # everything return "" @@ -605,16 +591,10 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % ( def finishedMsg(self): return ( "