def nextCard(self): elapsed = self.mw.col.timeboxReached() if elapsed: part1 = ngettext("%d card studied in", "%d cards studied in", elapsed[1]) % elapsed[1] mins = int(round(elapsed[0]/60)) part2 = ngettext("%s minute.", "%s minutes.", mins) % mins fin = _("Finish") diag = askUserDialog("%s %s" % (part1, part2), [_("Continue"), fin]) diag.setIcon(QMessageBox.Information) if diag.run() == fin: return self.mw.moveToState("deckBrowser") self.mw.col.startTimebox() if self.cardQueue: # undone/edited cards to show c = self.cardQueue.pop() c.startTimer() self.hadCardQueue = True else: if self.hadCardQueue: # the undone/edited cards may be sitting in the regular queue; # need to reset self.mw.col.reset() self.hadCardQueue = False c = self.mw.col.sched.getCard() self.card = c clearAudioQueue() if not c: self.mw.moveToState("overview") return if self._reps is None or self._reps % 100 == 0: # we recycle the webview periodically so webkit can free memory self._initWeb() else: self._showQuestion()
def nextCard(self): if self.cardQueue: # undone/edited cards to show c = self.cardQueue.pop() c.startTimer() self.hadCardQueue = True else: if self.hadCardQueue: # the undone/edited cards may be sitting in the regular queue; # need to reset self.mw.col.reset() self.hadCardQueue = False c = self.mw.col.sched.getCard() self.card = c clearAudioQueue() if not c: self.mw.moveToState("overview") return if self._reps is None or self._reps % 100 == 0: # we recycle the webview periodically so webkit can free memory self._initWeb() else: self._showQuestion() elapsed = self.mw.col.timeboxReached() if elapsed: part1 = ngettext("%d card studied in", "%d cards studied in", elapsed[1]) % elapsed[1] part2 = ngettext("%s minute.", "%s minutes.", elapsed[0]/60) % (elapsed[0]/60) tooltip("%s %s" % (part1, part2), period=5000) self.mw.col.startTimebox()
def _dueInfo(self, tot, num): i = [] self._line(i, _("Total"), ngettext("%d review", "%d reviews", tot) % tot) self._line(i, _("Average"), self._avgDay( tot, num, _("reviews"))) tomorrow = self.col.db.scalar(""" select count() from cards where did in %s and queue in (2,3) and due = ?""" % self._limit(), self.col.sched.today+1) tomorrow = ngettext("%d card", "%d cards", tomorrow) % tomorrow self._line(i, _("Due tomorrow"), tomorrow) return self._lineTbl(i)
def fixIntegrity(self): "Fix possible problems and rebuild caches." problems = [] self.save() oldSize = os.stat(self.path)[stat.ST_SIZE] if self.db.scalar("pragma integrity_check") != "ok": return (_("Collection is corrupt. Please see the manual."), False) # note types with a missing model ids = self.db.list(""" select id from notes where mid not in """ + ids2str(self.models.ids())) if ids: print self.db.list("select distinct mid from notes where id in " + ids2str(ids)) problems.append( ngettext("Deleted %d note with missing note type.", "Deleted %d notes with missing note type.", len(ids)) % len(ids)) self.remNotes(ids) # delete any notes with missing cards ids = self.db.list(""" select id from notes where id not in (select distinct nid from cards)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d note with no cards.", "Deleted %d notes with no cards.", cnt) % cnt) self._remNotes(ids) # tags self.tags.registerNotes() # field cache for m in self.models.all(): self.updateFieldCache(self.models.nids(m)) # new card position self.conf['nextPos'] = self.db.scalar( "select max(due)+1 from cards where type = 0") or 0 # reviews should have a reasonable due # ids = self.db.list( "select id from cards where queue = 2 and due > 10000") if ids: problems.append("Reviews had incorrect due date.") self.db.execute( "update cards set due = 0, mod = ?, usn = ? where id in %s" % ids2str(ids), intTime(), self.usn()) # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] txt = _("Database rebuilt and optimized.") ok = not problems problems.append(txt) self.save() return ("\n".join(problems), ok)
def accept(self): file = getSaveFile( self, _("Export"), "export", self.exporter.key, self.exporter.ext) self.hide() if file: self.exporter.includeSched = ( self.frm.includeSched.isChecked()) self.exporter.includeMedia = ( self.frm.includeMedia.isChecked()) self.exporter.includeTags = ( self.frm.includeTags.isChecked()) if not self.frm.deck.currentIndex(): self.exporter.did = None else: name = self.decks[self.frm.deck.currentIndex()] self.exporter.did = self.col.decks.id(name) self.mw.progress.start(immediate=True) try: f = open(file, "wb") f.close() except (OSError, IOError), e: showWarning(_("Couldn't save file: %s") % unicode(e)) else: os.unlink(file) self.exporter.exportInto(file) tooltip(ngettext("%d card exported.", "%d cards exported.", \ self.exporter.count) % self.exporter.count) finally:
def onDelete(): saveGeom(diag, "emptyCards") QDialog.accept(diag) self.checkpoint(_("Delete Empty")) self.col.remCards(cids) tooltip(ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids)) self.reset()
def _introductionGraph(self, data, days, title): if not data: return "" d = data conf = dict( xaxis=dict(tickDecimals=0, max=0.5), yaxes=[dict(min=0), dict(position="right",min=0)]) if days is not None: conf['xaxis']['min'] = -days+0.5 def plot(id, data, ylabel, ylabel2): return self._graph( id, data=data, conf=conf, ylabel=ylabel, ylabel2=ylabel2) # graph (repdata, repsum) = self._splitRepData(d, ((1, colLearn, ""),)) txt = self._title( title, _("The number of new cards you have added.")) txt += plot("intro", repdata, ylabel=_("Cards"), ylabel2=_("Cumulative Cards")) # total and per day average tot = sum([i[1] for i in d]) period = self._periodDays() if not period: # base off date of earliest added card period = self._deckAge('add') i = [] self._line(i, _("Total"), ngettext("%d card", "%d cards", tot) % tot) self._line(i, _("Average"), self._avgDay(tot, period, _("cards"))) txt += self._lineTbl(i) return txt
def updateTopArea(self): cnt = self.mw.col.models.useCount(self.model) self.topAreaForm.changesLabel.setText(ngettext( "Changes below will affect the %(cnt)d note that uses this card type.", "Changes below will affect the %(cnt)d notes that use this card type.", cnt) % dict(cnt=cnt)) self.updateCardNames()
def replaceWithApkg(mw, file, backup): # unload collection, which will also trigger a backup mw.unloadCollection() # overwrite collection z = zipfile.ZipFile(file) try: z.extract("collection.anki2", mw.pm.profileFolder()) except: showWarning(_("The provided file is not a valid .apkg file.")) return # because users don't have a backup of media, it's safer to import new # data and rely on them running a media db check to get rid of any # unwanted media. in the future we might also want to deduplicate this # step d = os.path.join(mw.pm.profileFolder(), "collection.media") for n, (cStr, file) in enumerate( json.loads(z.read("media").decode("utf8")).items()): mw.progress.update(ngettext("Processed %d media file", "Processed %d media files", n) % n) size = z.getinfo(cStr).file_size dest = os.path.join(d, file) # if we have a matching file size if os.path.exists(dest) and size == os.stat(dest).st_size: continue data = z.read(cStr) open(dest, "wb").write(data) z.close() # reload mw.loadCollection() if backup: mw.col.modSchema(check=False) mw.progress.finish()
def introductionGraph(self): start, days, chunk = self.get_start_end_chunk() data = self._added(days, chunk) if not data: return "" conf = dict( xaxis=dict(tickDecimals=0, max=0.5), yaxes=[dict(min=0), dict(position="right", min=0)]) if days is not None: # pylint: disable=invalid-unary-operand-type conf['xaxis']['min'] = -days+0.5 def plot(id, data, ylabel, ylabel2): return self._graph( id, data=data, conf=conf, xunit=chunk, ylabel=ylabel, ylabel2=ylabel2) # graph repdata, repsum = self._splitRepData(data, ((1, colLearn, ""),)) txt = self._title( _("Added"), _("The number of new cards you have added.")) txt += plot("intro", repdata, ylabel=_("Cards"), ylabel2=_("Cumulative Cards")) # total and per day average tot = sum([i[1] for i in data]) period = self._periodDays() if not period: # base off date of earliest added card period = self._deckAge('add') i = [] self._line(i, _("Total"), ngettext("%d card", "%d cards", tot) % tot) self._line(i, _("Average"), self._avgDay(tot, period, _("cards"))) txt += self._lineTbl(i) return txt
def onDelete(self): self.mw.checkpoint(_("Delete")) cnt = len(self.card.note().cards()) self.mw.col.remNotes([self.card.note().id]) self.mw.reset() tooltip(ngettext( "Note and its %d card deleted.", "Note and its %d cards deleted.", cnt) % cnt)
def _renderStats(self): cards, thetime = self.mw.col.db.first(""" select count(), sum(time)/1000 from revlog where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) cards = cards or 0 thetime = thetime or 0 msgp1 = ngettext("%d card", "%d cards", cards) % cards buf = _("Studied %(a)s in %(b)s today.") % dict(a=msgp1, b=fmtTimeSpan(thetime)) return buf
def onResetTimes(browser): # Make sure user selected something. if not browser.form.tableView.selectionModel().hasSelection(): showWarning("Please select at least one card to reset creation date.", parent=browser) return # Preprocess cards, collecting note IDs. (card_cnt, nids) = identifyNotes(browser.selectedCards()) # debug #showInfo(("Processed %s cards leading to %s notes") % (card_cnt, len(nids))) # Prompt for date. todaystr = time.strftime('%Y/%m/%d', time.localtime()) (s, ret) = getText("Enter a date as YYYY/MM/DD to set as the creation time, or 'today' for current date (%s):" % todaystr, parent=browser) if (not s) or (not ret): return # Generate a random MM:HH:SS. This will help prevent the same timestamp from # being used if this addon is executed multiple times with the same date. random_time = ("%s:%s:%s") % (random.randint(0, 23), random.randint(0, 59), random.randint(0, 59)) # Don't want random? Uncomment the following line and specify any time you # want in the format HH:MM:SS where HH is 00-24: #random_time = "15:01:01" if s == 'today': desttime = time.mktime(time.strptime(("%s %s") % (todaystr, random_time), '%Y/%m/%d %H:%M:%S')) else: try: desttime = time.mktime(time.strptime(("%s %s") % (s, random_time), '%Y/%m/%d %H:%M:%S')) except ValueError: showWarning("Sorry, I didn't understand that date. Please enter 'today' or a date in YYYY/MM/DD format", parent=browser) return # This mimics anki/utils.py timestampID function (which calls intTime for # seconds since epoch and multiplies those seconds by 1000). desttime = desttime * 1000 # debug # showInfo(("desttime %s") % desttime) # Force a full sync if collection isn't already marked for one. This is # apparently because we are changing the key column of the table. # (Per Damien on 2013/01/07: http://groups.google.com/group/anki-users/msg/3c8910e10f6fd0ac?hl=en ) mw.col.modSchema(check=True) # Do it. resetCreationTimes(nids, desttime) # Done. mw.reset() tooltip(ngettext("Creation time reset for %d note.", "Creation time reset for %d notes.", len(nids)) % len(nids))
def _nextDueMsg(self): line = [] rev = self.revTomorrow() + self.lrnTomorrow() if rev: line.append( ngettext("There will be <b>%s review</b>.", "There will be <b>%s reviews</b>.", rev) % rev) new = self.newTomorrow() if new: line.append( ngettext("There will be <b>%d new</b> card.", "There will be <b>%d new</b> cards.", new) % new) if line: line.insert(0, _("At this time tomorrow:")) buf = "<br>".join(line) else: buf = _("No cards are due tomorrow.") buf = '<style>b { color: #00f; }</style>' + buf return buf
def _importNotes(self): # build guid -> (id,mod,mid) hash & map of existing note ids self._notes = {} existing = {} for id, guid, mod, mid in self.dst.db.execute( "select id, guid, mod, mid from notes"): self._notes[guid] = (id, mod, mid) existing[id] = True # we may need to rewrite the guid if the model schemas don't match, # so we need to keep track of the changes for the card import stage self._changedGuids = {} # iterate over source collection add = [] dirty = [] usn = self.dst.usn() dupes = 0 for note in self.src.db.execute( "select * from notes"): # turn the db result into a mutable list note = list(note) shouldAdd = self._uniquifyNote(note) if shouldAdd: # ensure id is unique while note[0] in existing: note[0] += 999 existing[note[0]] = True # bump usn note[4] = usn # update media references in case of dupes note[6] = self._mungeMedia(note[MID], note[6]) add.append(note) dirty.append(note[0]) # note we have the added the guid self._notes[note[GUID]] = (note[0], note[3], note[MID]) else: dupes += 1 # update existing note newer = note[3] > mod if self.allowUpdate and self._mid(mid) == mid and newer: localNid = self._notes[note[GUID]][0] note[0] = localNid note[4] = usn add.append(note) dirty.append(note[0]) if dupes: self.log.append(_("Already in collection: %s.") % (ngettext( "%d note", "%d notes", dupes) % dupes)) # add to col self.dst.db.executemany( "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", add) self.dst.updateFieldCache(dirty) self.dst.tags.registerNotes(dirty)
def todayStats(self): """ Copy and paste from CollectionStats.todayStats(self) Two changes made """ #DeckInformation ... changed formatting b = "" #DeckInformation ... changed to use our deck limit lim = "cid in (select id from cards where did in %s)" % self.deck_limit if lim: lim = " and " + lim cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first(""" select count(), sum(time)/1000, sum(case when ease = 1 then 1 else 0 end), /* failed */ sum(case when type = 0 then 1 else 0 end), /* learning */ sum(case when type = 1 then 1 else 0 end), /* review */ sum(case when type = 2 then 1 else 0 end), /* relearn */ sum(case when type = 3 then 1 else 0 end) /* filter */ from revlog where id > ? """ + lim, (self.col.sched.dayCutoff - 86400) * 1000) cards = cards or 0 thetime = thetime or 0 failed = failed or 0 lrn = lrn or 0 rev = rev or 0 relrn = relrn or 0 filt = filt or 0 # studied def bold(s): return "<b>" + unicode(s) + "</b>" msgp1 = ngettext("<!--studied-->%d card", "<!--studied-->%d cards", cards) % cards b += _("Studied %(a)s in %(b)s today.") % dict( a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1))) # again/pass count b += "<br>" + _("Again count: %s") % bold(failed) if cards: b += " " + _("(%s correct)") % bold( "%0.1f%%" % ((1 - failed / float(cards)) * 100)) # type breakdown b += "<br>" b += (_("Learn: %(a)s, Review: %(b)s, Relearn: %(c)s, Filtered: %(d)s") % dict(a=bold(lrn), b=bold(rev), c=bold(relrn), d=bold(filt))) # mature today mcnt, msum = self.col.db.first(""" select count(), sum(case when ease = 1 then 0 else 1 end) from revlog where lastIvl >= 21 and id > ?""" + lim, (self.col.sched.dayCutoff - 86400) * 1000) b += "<br>" if mcnt: b += _("Correct answers on mature cards: %(a)d/%(b)d (%(c).1f%%)") % dict( a=msum, b=mcnt, c=(msum / float(mcnt) * 100)) else: b += _("No mature cards were studied today.") return b
def setChildren(self): if not askUser( _("Set all decks below %s to this option group?") % self.deck['name']): return for did in self.childDids: deck = self.mw.col.decks.get(did) deck['conf'] = self.deck['conf'] self.mw.col.decks.save(deck) tooltip(ngettext("%d deck updated.", "%d decks updated.", \ len(self.childDids)) % len(self.childDids))
def todayStats_old(self): """We need to overwrite the entire method to change the mature ivl""" b = self._title(_("Today")) # studied today lim = self._revlogLimit() if lim: lim = " and " + lim cards, thetime, failed, lrn, rev, relrn, filt = self.col.db.first(""" select count(), sum(time)/1000, sum(case when ease = 1 then 1 else 0 end), /* failed */ sum(case when type = 0 then 1 else 0 end), /* learning */ sum(case when type = 1 then 1 else 0 end), /* review */ sum(case when type = 2 then 1 else 0 end), /* relearn */ sum(case when type = 3 then 1 else 0 end) /* filter */ from revlog where id > ? """+lim, (self.col.sched.dayCutoff-86400)*1000) cards = cards or 0 thetime = thetime or 0 failed = failed or 0 lrn = lrn or 0 rev = rev or 0 relrn = relrn or 0 filt = filt or 0 # studied if anki_version.startswith("2.0."): def bold(s): return "<b>"+unicode(s)+"</b>" else: def bold(s): return "<b>"+str(s)+"</b>" msgp1 = ngettext("<!--studied-->%d card", "<!--studied-->%d cards", cards) % cards b += _("Studied %(a)s in %(b)s today.") % dict( a=bold(msgp1), b=bold(fmtTimeSpan(thetime, unit=1))) # again/pass count b += "<br>" + _("Again count: %s") % bold(failed) if cards: b += " " + _("(%s correct)") % bold( "%0.1f%%" %((1-failed/float(cards))*100)) # type breakdown b += "<br>" b += (_("Learn: %(a)s, Review: %(b)s, Relearn: %(c)s, Filtered: %(d)s") % dict(a=bold(lrn), b=bold(rev), c=bold(relrn), d=bold(filt))) # mature today mcnt, msum = self.col.db.first(""" select count(), sum(case when ease = 1 then 0 else 1 end) from revlog where lastIvl >= %d and id > ?""" % MATURE_IVL +lim, (self.col.sched.dayCutoff-86400)*1000) b += "<br>" if mcnt: b += _("Correct answers on mature cards: %(a)d/%(b)d (%(c).1f%%)") % dict( a=msum, b=mcnt, c=(msum / float(mcnt) * 100)) else: b += _("No mature cards were studied today.") return b
def updateModelsList(self, notetypes): row = self.form.modelsList.currentRow() if row == -1: row = 0 self.form.modelsList.clear() self.models = notetypes for m in self.models: mUse = m.use_count mUse = ngettext("%d note", "%d notes", mUse) % mUse item = QListWidgetItem("%s [%s]" % (m.name, mUse)) self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row)
def setChildren(self): if not askUser( _("Set all decks below %s to this option group?") % self.deck['name']): return for did in self.childDids: deck = self.mw.col.decks.get(did) if deck['dyn']: continue deck['conf'] = self.deck['conf'] self.mw.col.decks.save(deck) tooltip(ngettext("%d deck updated.", "%d decks updated.", \ len(self.childDids)) % len(self.childDids))
def onDelete(self): if len(self.model["flds"]) < 2: return showWarning(_("Notes require at least one field.")) count = self.mm.useCount(self.model) c = ngettext("%d note", "%d notes", count) % count if not askUser(_("Delete field from %s?") % c): return if not self.change_tracker.mark_schema(): return f = self.model["flds"][self.form.fieldList.currentRow()] self.mm.remove_field(self.model, f) self.fillFields() self.form.fieldList.setCurrentRow(0)
def updateModelsList(self): row = self.form.modelsList.currentRow() if row == -1: row = 0 self.models = self.col.models.all() self.models.sort(key=itemgetter("name")) self.form.modelsList.clear() for m in self.models: mUse = self.mm.useCount(m) mUse = ngettext("%d note", "%d notes", mUse) % mUse item = QListWidgetItem("%s [%s]" % (m["name"], mUse)) self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row)
def onDelete(self): # need to check state because the shortcut is global to the main # window if self.mw.state != "review" or not self.card: return self.mw.checkpoint(_("Delete")) cnt = len(self.card.note().cards()) self.mw.col.remNotes([self.card.note().id]) self.mw.reset() tooltip(ngettext( "Note and its %d card deleted.", "Note and its %d cards deleted.", cnt) % cnt)
def onDelete(self): selected = self.selectedAddons() if not selected: return if not askUser( ngettext("Delete the %(num)d selected add-on?", "Delete the %(num)d selected add-ons?", len(selected)) % dict(num=len(selected))): return for dir in selected: self.mgr.deleteAddon(dir) self.form.addonList.clearSelection() self.redrawAddons()
def onDelete(self): selected = self.selectedAddons() if not selected: return if not askUser(ngettext("Delete the %(num)d selected add-on?", "Delete the %(num)d selected add-ons?", len(selected)) % dict(num=len(selected))): return for dir in selected: self.mgr.deleteAddon(dir) self.form.addonList.clearSelection() self.redrawAddons()
def onDelete(self): if len(self.model['flds']) < 2: return showWarning(_("Notes require at least one field.")) c = self.mm.useCount(self.model) c = ngettext("%d note", "%d notes", c) % c if not askUser(_("Delete field from %s?") % c): return f = self.model['flds'][self.form.fieldList.currentRow()] self.mw.progress.start() self.mm.remField(self.model, f) self.mw.progress.finish() self.fillFields() self.form.fieldList.setCurrentRow(0)
def updateModelsList(self): row = self.form.modelsList.currentRow() if row == -1: row = 0 self.models = self.col.models.all() self.models.sort(key=itemgetter("name")) self.form.modelsList.clear() for m in self.models: mUse = self.mm.useCount(m) mUse = ngettext("%d note", "%d notes", mUse) % mUse item = QListWidgetItem("%s [%s]" % (m['name'], mUse)) self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row)
def updateModelsList(self, notetypes: List[NoteTypeNameIDUseCount]) -> None: row = self.form.modelsList.currentRow() if row == -1: row = 0 self.form.modelsList.clear() self.models = notetypes for m in self.models: mUse = ngettext("%d note", "%d notes", m.use_count) % m.use_count item = QListWidgetItem("%s [%s]" % (m.name, mUse)) self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row)
def _ansInfo(self, totd, studied, first, unit, convHours=False, total=None): if not totd: return tot = totd[-1][1] period = self._periodDays() if not period: # base off earliest repetition date lim = self._revlogLimit() if lim: lim = " where " + lim t = self.col.db.scalar( "select id from revlog %s order by id limit 1" % lim) if not t: period = 1 else: period = max( 1, int(1 + ((self.col.sched.dayCutoff - (t / 1000)) / 86400))) i = [] self._line( i, _("Days studied"), _("<b>%(pct)d%%</b> (%(x)s of %(y)s)") % dict(x=studied, y=period, pct=studied / float(period) * 100), bold=False) if convHours: tunit = _("hours") else: tunit = unit self._line(i, _("Total"), _("%(tot)s %(unit)s") % dict(unit=tunit, tot=int(tot))) if convHours: # convert to minutes tot *= 60 self._line(i, _("Average for days studied"), self._avgDay(tot, studied, unit)) self._line(i, _("If you studied every day"), self._avgDay(tot, period, unit)) if total and tot: perMin = total / float(tot) perMin = ngettext("%d card/minute", "%d cards/minute", perMin) % perMin self._line(i, _("Average answer time"), "%0.1fs (%s)" % ((tot * 60) / total, perMin)) return self._lineTbl(i), int(tot)
def nextCard(self): elapsed = self.mw.col.timeboxReached() if elapsed: part1 = ngettext("%d card studied in", "%d cards studied in", elapsed[1]) % elapsed[1] mins = int(round(elapsed[0]/60)) part2 = ngettext("%s minute.", "%s minutes.", mins) % mins fin = _("Finish") diag = askUserDialog("%s %s" % (part1, part2), [_("Continue"), fin]) diag.setIcon(QMessageBox.Information) if diag.run() == fin: return self.mw.moveToState("deckBrowser") self.mw.col.startTimebox() if self.cardQueue: # undone/edited cards to show c = self.cardQueue.pop() c.startTimer() self.hadCardQueue = True else: if self.hadCardQueue: # the undone/edited cards may be sitting in the regular queue; # need to reset self.mw.col.reset() self.hadCardQueue = False c = self.mw.col.sched.getCard() if c and self.card and self.card.id == c.id: # if previously dropped card self.card=None return self.nextCard() self.card = c clearAudioQueue() if not c: return self.mw.moveToState("overview") if self._reps is None or self._reps % 100 == 0: # we recycle the webview periodically so webkit can free memory self._initWeb() else: self._showQuestion()
def onAddCard(self): cnt = self.mw.col.models.useCount(self.model) txt = ngettext("This will create %d card. Proceed?", "This will create %d cards. Proceed?", cnt) % cnt if not askUser(txt): return name = self._newCardName() t = self.mm.newTemplate(name) old = self.card.template() t['qfmt'] = old['qfmt'] t['afmt'] = old['afmt'] self.mm.addTemplate(self.model, t) self.ord = len(self.cards) self.redraw()
def on_done(fut): card_cnt = fut.result() template = self.current_template() cards = ngettext("%d card", "%d cards", card_cnt) % card_cnt msg = _("Delete the '%(a)s' card type, and its %(b)s?") % dict( a=template["name"], b=cards) if not askUser(msg): return if not self.change_tracker.mark_schema(): return self.onRemoveInner(template)
def onCheckMediaDB(self): self.progress.start(immediate=True) (nohave, unused, warnings) = self.col.media.check() self.progress.finish() # generate report report = "" if warnings: report += "\n".join(warnings) + "\n" if unused: numberOfUnusedFilesLabel = len(unused) if report: report += "\n\n\n" report += (ngettext( "%d file found in media folder not used by any cards:", "%d files found in media folder not used by any cards:", numberOfUnusedFilesLabel, ) % numberOfUnusedFilesLabel) report += "\n" + "\n".join(unused) if nohave: if report: report += "\n\n\n" report += _("Used on cards but missing from media folder:") report += "\n" + "\n".join(nohave) if not report: tooltip(_("No unused or missing files found.")) return # show report and offer to delete diag = QDialog(self) diag.setWindowTitle("Anki") layout = QVBoxLayout(diag) diag.setLayout(layout) text = QTextEdit() text.setReadOnly(True) text.setPlainText(report) layout.addWidget(text) box = QDialogButtonBox(QDialogButtonBox.Close) layout.addWidget(box) if unused: b = QPushButton(_("Delete Unused Files")) b.setAutoDefault(False) box.addButton(b, QDialogButtonBox.ActionRole) b.clicked.connect( lambda c, u=unused, d=diag: self.deleteUnused(u, d)) box.rejected.connect(diag.reject) diag.setMinimumHeight(400) diag.setMinimumWidth(500) restoreGeom(diag, "checkmediadb") diag.exec_() saveGeom(diag, "checkmediadb")
def _renderStats(self): cards, thetime = self.mw.col.db.first( """ select count(), sum(time)/1000 from revlog where id > ?""", (self.mw.col.sched.dayCutoff - 86400) * 1000, ) cards = cards or 0 thetime = thetime or 0 msgp1 = (ngettext("<!--studied-->%d card", "<!--studied-->%d cards", cards) % cards) buf = _("Studied %(a)s %(b)s today.") % dict( a=msgp1, b=fmtTimeSpan(thetime, unit=1, inTime=True)) return buf
def foreignNotes(self): # Load file and parse it by minidom self.loadSource(self.file) # Migrating content / time consuming part # addItemToCards is called for each sm element self.logger('Parsing started.') self.parse() self.logger('Parsing done.') # Return imported cards self.total = len(self.notes) self.log.append(ngettext("%d card imported.", "%d cards imported.", self.total) % self.total) return self.notes
def onDelete(self): note = self.card.note() numCards = len(note.cards()) if numCards > 1: r = askUser("Really delete this note? It has %i cards." % numCards, defaultno=True, title="Delete note?") if not r: return self.mw.checkpoint(_("Delete")) self.mw.col.remNotes([self.card.note().id]) tooltip(ngettext( "Note and its %d card deleted.", "Note and its %d cards deleted.", numCards) % numCards) self.accept()
def onDelete(self): note = self.card.note() numCards = len(note.cards()) if numCards > 1: r = askUser("Really delete this note? It has %i cards." % numCards, defaultno=True, title="Delete note?") if not r: return self.mw.checkpoint(_("Delete")) self.mw.col.remNotes([self.card.note().id]) tooltip( ngettext("Note and its %d card deleted.", "Note and its %d cards deleted.", numCards) % numCards) self.accept()
def onRemove(self): if len(self.model['tmpls']) < 2: return showInfo(_("At least one card type is required.")) idx = self.ord cards = self.mm.tmplUseCount(self.model, idx) cards = ngettext("%d card", "%d cards", cards) % cards msg = (_("Delete the '%(a)s' card type, and its %(b)s?") % dict(a=self.model['tmpls'][idx]['name'], b=cards)) if not askUser(msg): return if not self.mm.remTemplate(self.model, self.cards[idx].template()): return showWarning(_("""\ Removing this card type would cause one or more notes to be deleted. \ Please create a new card type first.""")) self.redraw()
def onAddCard(self): """Ask for confirmation and create a copy of current card as the last template""" cnt = self.mw.col.models.useCount(self.model) txt = ngettext("This will create %d card. Proceed?", "This will create %d cards. Proceed?", cnt) % cnt if not askUser(txt): return name = self._newCardName() t = self.mm.newTemplate(name) old = self.card.template() t['qfmt'] = old['qfmt'] t['afmt'] = old['afmt'] self.mm.addTemplate(self.model, t) self.ord = len(self.cards) self.redraw()
def onConfChange(self, idx): if self.ignoreConfChange: return if self.conf: self.saveConf() conf = self.confList[idx] self.deck['conf'] = conf['id'] self.loadConf() cnt = 0 for d in self.mw.col.decks.all(): if d['dyn']: continue if d['conf'] == conf['id']: cnt += 1 self.form.count.setText(ngettext("%d deck uses this options group", \ "%d decks use this options group", cnt) % cnt)
def _dueInfo(self, tot: int, num: int) -> str: i: List[str] = [] self._line( i, _("Total"), self.col.tr(TR.STATISTICS_REVIEWS, reviews=tot), ) self._line(i, _("Average"), self._avgDay(tot, num, _("reviews"))) tomorrow = self.col.db.scalar( f""" select count() from cards where did in %s and queue in ({QUEUE_TYPE_REV},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and due = ?""" % self._limit(), self.col.sched.today + 1, ) tomorrow = ngettext("%d card", "%d cards", tomorrow) % tomorrow self._line(i, _("Due tomorrow"), tomorrow) return self._lineTbl(i)
def _ansInfo(self, totd, studied, first, unit, convHours=False, total=None): if not totd: return tot = totd[-1][1] period = self._periodDays() if not period: # base off earliest repetition date lim = self._revlogLimit() if lim: lim = " where " + lim t = self.col.db.scalar("select id from revlog %s order by id limit 1" % lim) if not t: period = 1 else: period = max( 1, int(1+((self.col.sched.dayCutoff - (t/1000)) / 86400))) i = [] self._line(i, _("Days studied"), _("<b>%(pct)d%%</b> (%(x)s of %(y)s)") % dict( x=studied, y=period, pct=studied/float(period)*100), bold=False) if convHours: tunit = _("hours") else: tunit = unit self._line(i, _("Total"), _("%(tot)s %(unit)s") % dict( unit=tunit, tot=int(tot))) if convHours: # convert to minutes tot *= 60 self._line(i, _("Average for days studied"), self._avgDay( tot, studied, unit)) if studied != period: # don't display if you did study every day self._line(i, _("If you studied every day"), self._avgDay( tot, period, unit)) if total and tot: perMin = total / float(tot) perMin = round(perMin, 1) perMin = ngettext("%d card/minute", "%.01f cards/minute", perMin) % perMin # don't round down to zero if float(perMin.split(' ')[0]) < 0.1: perMin = ''.join(["<", _("%.01f cards/minute")]) % 0.1 self._line( i, _("Average answer time"), _("%(a)0.1fs (%(b)s)") % dict(a=(tot*60)/total, b=perMin)) return self._lineTbl(i), int(tot)
def _replaceWithApkg(mw, file, backup): mw.progress.start(immediate=True) z = zipfile.ZipFile(file) # v2 scheduler? colname = "collection.anki21" try: z.getinfo(colname) except KeyError: colname = "collection.anki2" try: with z.open(colname) as source, \ open(mw.pm.collectionPath(), "wb") as target: shutil.copyfileobj(source, target) except: mw.progress.finish() showWarning(_("The provided file is not a valid .apkg file.")) return # because users don't have a backup of media, it's safer to import new # data and rely on them running a media db check to get rid of any # unwanted media. in the future we might also want to deduplicate this # step d = os.path.join(mw.pm.profileFolder(), "collection.media") for n, (cStr, file) in enumerate( json.loads(z.read("media").decode("utf8")).items()): mw.progress.update( ngettext("Processed %d media file", "Processed %d media files", n) % n) size = z.getinfo(cStr).file_size dest = os.path.join(d, unicodedata.normalize("NFC", file)) # if we have a matching file size if os.path.exists(dest) and size == os.stat(dest).st_size: continue data = z.read(cStr) open(dest, "wb").write(data) z.close() # reload if not mw.loadCollection(): mw.progress.finish() return if backup: mw.col.modSchema(check=False) mw.progress.finish()
def onAddCard(self): """Ask for confirmation and create a copy of current card as the last template""" # only differenc, add a new element in newTemplatesData cnt = self.mw.col.models.useCount(self.model) txt = ngettext("This will create %d card. Proceed?", "This will create %d cards. Proceed?", cnt) % cnt if not askUser(txt): return name = self._newCardName() t = self.mm.newTemplate(name) old = self.card.template() t['qfmt'] = old['qfmt'] t['afmt'] = old['afmt'] self.mm.addTemplate(self.model, t) self.newTemplatesData.append({"old idx":self.newTemplatesData[self.ord]["old idx"], "is new": True})#new self.ord = len(self.cards) self.redraw()
def buildCard(self, text): config = mw.addonManager.getConfig(__name__) if config: MODEL = config['model'] target_fields = config['target_fields'] Deck = config['Deck'] else: msg = """ AnkiClips: The add-on missed the configuration file. If you would not get the right feeds, please reinstall this add-on.""" utils.showWarning(msg) # get deck and model deck = mw.col.decks.get(mw.col.decks.id(Deck)) model = mw.col.models.byName(MODEL) # assign model to deck mw.col.decks.select(deck['id']) mw.col.decks.get(deck)['mid'] = model['id'] mw.col.decks.save(deck) # assign deck to model mw.col.models.setCurrent(model) mw.col.models.current()['did'] = deck['id'] mw.col.models.save(model) # iterate notes adds = 0 for key in text.keys(): note = mw.col.newNote() note["audio"] = "[sound:" + key + ".mp3]" note["text"] = text[key] mw.col.addNote(note) adds += 1 mw.col.reset() mw.reset() # show result msg = ngettext("%d note added", "%d notes added", adds) % adds msg += "\n" return msg
def _replaceWithApkg(mw, file, backup): mw.progress.start(immediate=True) z = zipfile.ZipFile(file) # v2 scheduler? colname = "collection.anki21" try: z.getinfo(colname) except KeyError: colname = "collection.anki2" try: with z.open(colname) as source, \ open(mw.pm.collectionPath(), "wb") as target: shutil.copyfileobj(source, target) except: mw.progress.finish() showWarning(_("The provided file is not a valid .apkg file.")) return # because users don't have a backup of media, it's safer to import new # data and rely on them running a media db check to get rid of any # unwanted media. in the future we might also want to deduplicate this # step d = os.path.join(mw.pm.profileFolder(), "collection.media") for n, (cStr, file) in enumerate( json.loads(z.read("media").decode("utf8")).items()): mw.progress.update(ngettext("Processed %d media file", "Processed %d media files", n) % n) size = z.getinfo(cStr).file_size dest = os.path.join(d, unicodedata.normalize("NFC", file)) # if we have a matching file size if os.path.exists(dest) and size == os.stat(dest).st_size: continue data = z.read(cStr) open(dest, "wb").write(data) z.close() # reload if not mw.loadCollection(): mw.progress.finish() return if backup: mw.col.modSchema(check=False) mw.progress.finish()
def clockIsOff(self, diff): diffText = ngettext("%s second", "%s seconds", diff) % diff warn = _("""\ In order to ensure your collection works correctly when moved between \ devices, Anki requires your computer's internal clock to be set correctly. \ The internal clock can be wrong even if your system is showing the correct \ local time. Please go to the time settings on your computer and check the following: - AM/PM - Clock drift - Day, month and year - Timezone - Daylight savings Difference to correct time: %s.""") % diffText showWarning(warn) self.app.closeAllWindows()
def updateTemplates(self): if not self.handleCards: return # remove any shortcuts for s in self.cardShortcuts: s.setEnabled(False) self.cardShortcuts = [] m = self.deck.currentModel() ts = m.templates active = [t['name'] for t in ts if t['actv']] txt = ngettext("%d card", "%d cards", len(active)) % len(active) self.cards.setText(txt) n = 1 for t in ts: s = QShortcut(QKeySequence("Ctrl+%d" % n), self.widget) self.widget.connect(s, SIGNAL("activated()"), lambda t=t: self.toggleTemplate(t)) self.cardShortcuts.append(s) n += 1
def onRemove(self): """ Remove the current template, except if it would leave a note without card. Ask user for confirmation""" # only difference: remove current index from newTemplatesData. if len(self.model['tmpls']) < 2: return showInfo(_("At least one card type is required.")) idx = self.ord cards = self.mm.tmplUseCount(self.model, idx) cards = ngettext("%d card", "%d cards", cards) % cards msg = (_("Delete the '%(a)s' card type, and its %(b)s?") % dict(a=self.model['tmpls'][idx]['name'], b=cards)) if not askUser(msg): return if not self.mm.remTemplate(self.model, self.cards[idx].template()): return showWarning(_("""\ Removing this card type would cause one or more notes to be deleted. \ Please create a new card type first.""")) del self.newTemplatesData[idx] # Only new line self.redraw()
def onAddCard(self): cnt = self.mw.col.models.useCount(self.model) txt = (ngettext( "This will create %d card. Proceed?", "This will create %d cards. Proceed?", cnt, ) % cnt) if not askUser(txt): return if not self.change_tracker.mark_schema(): return name = self._newCardName() t = self.mm.newTemplate(name) old = self.current_template() t["qfmt"] = old["qfmt"] t["afmt"] = old["afmt"] self.mm.add_template(self.model, t) self.ord = len(self.templates) - 1 self.redraw_everything()
def _delete(self, did): self.mw.checkpoint(_("Delete Deck")) deck = self.mw.col.decks.get(did) if not deck["dyn"]: dids = [did] + [r[1] for r in self.mw.col.decks.children(did)] cnt = self.mw.col.db.scalar( "select count() from cards where did in {0} or " "odid in {0}".format(ids2str(dids))) if cnt: extra = ngettext(" It has %d card.", " It has %d cards.", cnt) % cnt else: extra = None if (deck["dyn"] or not extra or askUser((_("Are you sure you wish to delete %s?") % deck["name"]) + extra)): self.mw.progress.start() self.mw.col.decks.rem(did, True) self.mw.progress.finish() self.show()
def introductionGraph(self) -> str: start, days, chunk = self.get_start_end_chunk() data = self._added(days, chunk) if not data: return "" conf: Dict[str, Any] = dict( xaxis=dict(tickDecimals=0, max=0.5), yaxes=[dict(min=0), dict(position="right", min=0)], ) if days is not None: # pylint: disable=invalid-unary-operand-type conf["xaxis"]["min"] = -days + 0.5 def plot(id, data, ylabel, ylabel2): return self._graph(id, data=data, conf=conf, xunit=chunk, ylabel=ylabel, ylabel2=ylabel2) # graph repdata, repsum = self._splitRepData(data, ((1, colLearn, ""), )) txt = self._title(_("Added"), _("The number of new cards you have added.")) txt += plot("intro", repdata, ylabel=_("Cards"), ylabel2=_("Cumulative Cards")) # total and per day average tot = sum([i[1] for i in data]) period = self._periodDays() if not period: # base off date of earliest added card period = self._deckAge("add") i: List[str] = [] self._line(i, _("Total"), ngettext("%d card", "%d cards", tot) % tot) self._line(i, _("Average"), self._avgDay(tot, period, _("cards"))) txt += self._lineTbl(i) return txt
def _delete(self, did): if str(did) == '1': return showWarning(_("The default deck can't be deleted.")) self.mw.checkpoint(_("Delete Deck")) deck = self.mw.col.decks.get(did) if not deck['dyn']: dids = [did] + [r[1] for r in self.mw.col.decks.children(did)] cnt = self.mw.col.db.scalar( "select count() from cards where did in {0} or " "odid in {0}".format(ids2str(dids))) if cnt: extra = ngettext(" It has %d card.", " It has %d cards.", cnt) % cnt else: extra = None if deck['dyn'] or not extra or askUser( (_("Are you sure you wish to delete %s?") % deck['name']) + extra): self.mw.progress.start(immediate=True) self.mw.col.decks.rem(did, True) self.mw.progress.finish() self.show()