def _showCardValues(self): lapses = self.card.lapses # since we just failed the card, current ivl is no use interval = fmtTimeSpan(self.card.lastIvl * 86400) ease = self.card.factor // 10 statManager = CardStats(self.mw.col, self.card) secondsSpent = self.mw.col.db.first( "select sum(time)/1000 from revlog where cid = :id", id=self.card.id)[0] totalTime = statManager.time(secondsSpent) # watch on updates: calling a private method of sched for config info leechThreshold = self.mw.col.sched._lapseConf(self.card)['leechFails'] if leechThreshold == lapses: timesLeeched = "Leech for the first time." else: beyondOne = (lapses - leechThreshold) // (leechThreshold // 2) timesLeeched = "Leeched %i times." % (beyondOne + 1) txt = ("{leechtimes}<br>" "<b>Lapses</b>: {lapses}<br>" "<b>Last interval</b>: {lastivl}<br>" "<b>Ease</b>: {ease}%<br>" "<b>Total time</b>: {ttime}") self.form.label.setText(txt.format(leechtimes=timesLeeched, lapses=lapses, lastivl=interval, ease=ease, ttime=totalTime))
def _cardInfoData(self): from anki.stats import CardStats cs = CardStats(self.col, self.card) rep = cs.report() m = self.card.model() rep = """ <div style='width: 400px; margin: 0 auto 0; border: 1px solid #000; padding: 3px; '>%s</div>""" % rep return rep, cs
def setupCardInfo(self): self.currentCard = None self.cardStats = CardStats(self.deck, None)
class EditDeck(QMainWindow): def __init__(self, parent): if parent.config['standaloneWindows']: windParent = None else: windParent = parent QMainWindow.__init__(self, windParent) self.parent = parent self.deck = self.parent.deck self.config = parent.config self.forceClose = False self.origModTime = parent.deck.modified self.currentRow = None self.lastFilter = "" self.dialog = ankiqt.forms.cardlist.Ui_MainWindow() self.dialog.setupUi(self) restoreGeom(self, "editor") restoreSplitter(self.dialog.splitter, "editor") # flush all changes before we load self.deck.s.flush() self.model = DeckModel(self.parent, self.parent.deck) self.dialog.tableView.setSortingEnabled(False) self.dialog.tableView.setShowGrid(False) self.dialog.tableView.setModel(self.model) self.dialog.tableView.selectionModel() self.connect(self.dialog.tableView.selectionModel(), SIGNAL("selectionChanged(QItemSelection,QItemSelection)"), self.updateFilterLabel) self.dialog.tableView.setItemDelegate(StatusDelegate(self, self.model)) if self.deck.getInt("reverseOrder"): self.dialog.actionReverseOrder.setChecked(True) self.updateFont() self.setupMenus() self.setupFilter() self.setupSort() self.setupHeaders() self.setupHooks() self.setupEditor() self.setupCardInfo() self.dialog.filterEdit.setFocus() ui.dialogs.open("CardList", self) self.drawTags() self.updateFilterLabel() self.show() if self.parent.currentCard: self.currentCard = self.parent.currentCard self.updateSearch() if sys.platform.startswith("darwin"): self.macCloseShortcut = QShortcut(QKeySequence("Ctrl+w"), self) self.connect(self.macCloseShortcut, SIGNAL("activated()"), self.close) def findCardInDeckModel(self): for i, thisCard in enumerate(self.model.cards): if thisCard[0] == self.currentCard.id: return i return -1 def updateFont(self): self.dialog.tableView.setFont(QFont( self.config['editFontFamily'], self.config['editFontSize'])) self.dialog.tableView.verticalHeader().setDefaultSectionSize( self.parent.config['editLineSize']) self.model.reset() def setupFilter(self): self.filterTimer = None self.connect(self.dialog.filterEdit, SIGNAL("textChanged(QString)"), self.filterTextChanged) self.connect(self.dialog.filterEdit, SIGNAL("returnPressed()"), self.showFilterNow) self.setTabOrder(self.dialog.filterEdit, self.dialog.tableView) self.connect(self.dialog.tagList, SIGNAL("activated(int)"), self.tagChanged) def setupSort(self): self.dialog.sortBox.setMaxVisibleItems(30) self.sortIndex = self.deck.getInt("sortIndex") or 0 self.drawSort() self.connect(self.dialog.sortBox, SIGNAL("activated(int)"), self.sortChanged) self.sortChanged(self.sortIndex, refresh=False) def drawTags(self): self.dialog.tagList.view().setFixedWidth(200) self.dialog.tagList.setMaxVisibleItems(30) self.dialog.tagList.setFixedWidth(130) self.dialog.tagList.clear() alltags = [None, "Marked", "Suspended", None, None, None] # system tags self.dialog.tagList.addItem(_("<Filter>")) self.dialog.tagList.addItem(QIcon(":/icons/rating.png"), _('Marked')) self.dialog.tagList.addItem(QIcon(":/icons/media-playback-pause.png"), _('Suspended')) self.dialog.tagList.addItem(QIcon(":/icons/chronometer.png"), _('Due')) self.dialog.tagList.addItem(QIcon(":/icons/editclear.png"), _('No fact tags')) self.dialog.tagList.insertSeparator( self.dialog.tagList.count()) # model and card templates for (type, sql, icon) in ( ("models", "select tags from models", "contents.png"), ("cms", "select name from cardModels", "Anki_Card.png")): d = {} tagss = self.deck.s.column0(sql) for tags in tagss: for tag in parseTags(tags): d[tag] = 1 sortedtags = sorted(d.keys()) alltags.extend(sortedtags) icon = QIcon(":/icons/" + icon) for t in sortedtags: self.dialog.tagList.addItem(icon, t.replace("_", " ")) if sortedtags: self.dialog.tagList.insertSeparator( self.dialog.tagList.count()) alltags.append(None) # fact tags alluser = sorted(self.deck.allTags()) for tag in alltags: try: alluser.remove(tag) except: pass icon = QIcon(":/icons/Anki_Fact.png") for t in alluser: t = t.replace("_", " ") self.dialog.tagList.addItem(icon, t) alltags.extend(alluser) self.alltags = alltags def drawSort(self): self.sortList = [ _("Question"), _("Answer"), _("Created"), _("Modified"), _("Due"), _("Interval"), _("Reps"), _("Ease"), _("Fact Created"), ] self.sortFields = sorted(self.deck.allFields()) self.sortList.extend([_("'%s'") % f for f in self.sortFields]) self.dialog.sortBox.clear() self.dialog.sortBox.addItems(QStringList(self.sortList)) if self.sortIndex >= len(self.sortList): self.sortIndex = 0 self.dialog.sortBox.setCurrentIndex(self.sortIndex) def sortChanged(self, idx, refresh=True): if idx == 0: self.sortKey = "question" elif idx == 1: self.sortKey = "answer" elif idx == 2: self.sortKey = "created" elif idx == 3: self.sortKey = "modified" elif idx == 4: self.sortKey = "due" elif idx == 5: self.sortKey = "interval" elif idx == 6: self.sortKey = "reps" elif idx == 7: self.sortKey = "factor" elif idx == 8: self.sortKey = "fact" else: self.sortKey = ("field", self.sortFields[idx-9]) self.rebuildSortIndex(self.sortKey) self.sortIndex = idx self.deck.setVar('sortIndex', idx) self.model.sortKey = self.sortKey self.model.updateHeader() if refresh: self.model.showMatching() self.updateFilterLabel() self.onEvent() self.focusCurrentCard() def rebuildSortIndex(self, key): if key not in ( "question", "answer", "created", "modified", "due", "interval", "reps", "factor"): return old = self.deck.s.scalar("select sql from sqlite_master where name = :k", k="ix_cards_sort") if old and key in old: return self.parent.setProgressParent(self) self.deck.startProgress(2) self.deck.updateProgress(_("Building Index...")) self.deck.s.statement("drop index if exists ix_cards_sort") self.deck.updateProgress() if key in ("question", "answer"): key = key + " collate nocase" self.deck.s.statement( "create index ix_cards_sort on cards (%s)" % key) self.deck.s.statement("analyze") self.deck.finishProgress() self.parent.setProgressParent(None) def tagChanged(self, idx): if idx == 0: filter = "" elif idx == 1: filter = "tag:marked" elif idx == 2: filter = "tag:suspended" elif idx == 3: filter = "is:due" elif idx == 4: filter = "tag:none" else: filter = "tag:" + self.alltags[idx] self.lastFilter = filter self.dialog.filterEdit.setText(filter) self.showFilterNow() def updateFilterLabel(self): selected = len(self.dialog.tableView.selectionModel().selectedRows()) self.setWindowTitle(ngettext("Browser (%(cur)d " "of %(tot)d card shown; %(sel)s)", "Browser (%(cur)d " "of %(tot)d cards shown; %(sel)s)", self.deck.cardCount) % { "cur": len(self.model.cards), "tot": self.deck.cardCount, "sel": ngettext("%d selected", "%d selected", selected) % selected } + " - " + self.parent.deck.name()) def onEvent(self, type='field'): if self.deck.undoAvailable(): self.dialog.actionUndo.setText(_("Undo %s") % self.deck.undoName()) self.dialog.actionUndo.setEnabled(True) else: self.dialog.actionUndo.setEnabled(False) if self.deck.redoAvailable(): self.dialog.actionRedo.setText(_("Redo %s") % self.deck.redoName()) self.dialog.actionRedo.setEnabled(True) else: self.dialog.actionRedo.setEnabled(False) # update list if self.currentRow and self.model.cards: self.model.updateCard(self.currentRow) if type == "tag": self.drawTags() def filterTextChanged(self): interval = 300 # update filter dropdown if (self.lastFilter.lower() not in unicode(self.dialog.filterEdit.text()).lower()): self.dialog.tagList.setCurrentIndex(0) if self.filterTimer: self.filterTimer.setInterval(interval) else: self.filterTimer = QTimer(self) self.filterTimer.setSingleShot(True) self.filterTimer.start(interval) self.connect(self.filterTimer, SIGNAL("timeout()"), lambda: self.updateSearch(force=False)) def showFilterNow(self): if self.filterTimer: self.filterTimer.stop() self.updateSearch() def updateSearch(self, force=True): if self.parent.inDbHandler: return self.model.searchStr = unicode(self.dialog.filterEdit.text()) self.model.showMatching(force) self.updateFilterLabel() self.onEvent() self.filterTimer = None if self.model.cards: self.dialog.cardInfoGroup.show() self.dialog.fieldsArea.show() else: self.dialog.cardInfoGroup.hide() self.dialog.fieldsArea.hide() if not self.focusCurrentCard(): if self.model.cards: self.dialog.tableView.selectRow(0) if not self.model.cards: self.editor.setFact(None) def focusCurrentCard(self): if self.currentCard: try: self.currentCard.id except: return False currentCardIndex = self.findCardInDeckModel() if currentCardIndex >= 0: sm = self.dialog.tableView.selectionModel() sm.clear() self.dialog.tableView.selectRow(currentCardIndex) self.dialog.tableView.scrollTo( self.model.index(currentCardIndex,0), self.dialog.tableView.PositionAtCenter) return True return False def setupHeaders(self): if not sys.platform.startswith("win32"): self.dialog.tableView.verticalHeader().hide() self.dialog.tableView.horizontalHeader().show() restoreHeader(self.dialog.tableView.horizontalHeader(), "editor") for i in range(2): self.dialog.tableView.horizontalHeader().setResizeMode(i, QHeaderView.Stretch) self.dialog.tableView.horizontalHeader().setResizeMode(2, QHeaderView.Interactive) def setupMenus(self): # actions self.connect(self.dialog.actionDelete, SIGNAL("triggered()"), self.deleteCards) self.connect(self.dialog.actionAddTag, SIGNAL("triggered()"), self.addTags) self.connect(self.dialog.actionDeleteTag, SIGNAL("triggered()"), self.deleteTags) self.connect(self.dialog.actionReschedule, SIGNAL("triggered()"), self.reschedule) self.connect(self.dialog.actionCram, SIGNAL("triggered()"), self.cram) self.connect(self.dialog.actionAddCards, SIGNAL("triggered()"), self.addCards) self.connect(self.dialog.actionChangeModel, SIGNAL("triggered()"), self.onChangeModel) self.connect(self.dialog.actionSuspend, SIGNAL("triggered()"), self.onSuspend) self.connect(self.dialog.actionUnsuspend, SIGNAL("triggered()"), self.onUnsuspend) # edit self.connect(self.dialog.actionFont, SIGNAL("triggered()"), self.onFont) self.connect(self.dialog.actionUndo, SIGNAL("triggered()"), self.onUndo) self.connect(self.dialog.actionRedo, SIGNAL("triggered()"), self.onRedo) self.connect(self.dialog.actionInvertSelection, SIGNAL("triggered()"), self.invertSelection) self.connect(self.dialog.actionReverseOrder, SIGNAL("triggered()"), self.reverseOrder) self.connect(self.dialog.actionSelectFacts, SIGNAL("triggered()"), self.selectFacts) self.connect(self.dialog.actionFindReplace, SIGNAL("triggered()"), self.onFindReplace) # jumps self.connect(self.dialog.actionFirstCard, SIGNAL("triggered()"), self.onFirstCard) self.connect(self.dialog.actionLastCard, SIGNAL("triggered()"), self.onLastCard) self.connect(self.dialog.actionPreviousCard, SIGNAL("triggered()"), self.onPreviousCard) self.connect(self.dialog.actionNextCard, SIGNAL("triggered()"), self.onNextCard) self.connect(self.dialog.actionFind, SIGNAL("triggered()"), self.onFind) self.connect(self.dialog.actionFact, SIGNAL("triggered()"), self.onFact) self.connect(self.dialog.actionTags, SIGNAL("triggered()"), self.onTags) self.connect(self.dialog.actionSort, SIGNAL("triggered()"), self.onSort) # help self.connect(self.dialog.actionGuide, SIGNAL("triggered()"), self.onHelp) runHook('editor.setupMenus', self) def onClose(self): saveSplitter(self.dialog.splitter, "editor") self.editor.saveFieldsNow() if not self.forceClose: if not self.factValid: ui.utils.showInfo(_( "Some fields are missing or not unique."), parent=self, help="AddItems#AddError") return self.editor.setFact(None) self.editor.close() saveGeom(self, "editor") saveHeader(self.dialog.tableView.horizontalHeader(), "editor") self.hide() ui.dialogs.close("CardList") if self.parent.currentCard: self.parent.moveToState("showQuestion") else: self.parent.moveToState("auto") self.teardownHooks() return True def closeEvent(self, evt): if self.onClose(): evt.accept() else: evt.ignore() def keyPressEvent(self, evt): "Show answer on RET or register answer." if evt.key() in (Qt.Key_Escape,): self.close() # Editor ###################################################################### def setupEditor(self): self.editor = ui.facteditor.FactEditor(self, self.dialog.fieldsArea, self.deck) self.factValid = True self.editor.onFactValid = self.onFactValid self.editor.onFactInvalid = self.onFactInvalid self.editor.onChange = self.onEvent self.connect(self.dialog.tableView.selectionModel(), SIGNAL("currentRowChanged(QModelIndex, QModelIndex)"), self.rowChanged) def onFactValid(self, fact): self.factValid = True self.dialog.tableView.setEnabled(True) self.dialog.filterEdit.setEnabled(True) self.dialog.sortBox.setEnabled(True) self.dialog.tagList.setEnabled(True) self.dialog.menubar.setEnabled(True) self.dialog.cardInfoGroup.setEnabled(True) def onFactInvalid(self, fact): self.factValid = False self.dialog.tableView.setEnabled(False) self.dialog.filterEdit.setEnabled(False) self.dialog.sortBox.setEnabled(False) self.dialog.tagList.setEnabled(False) self.dialog.menubar.setEnabled(False) self.dialog.cardInfoGroup.setEnabled(False) def rowChanged(self, current, previous): self.currentRow = current self.currentCard = self.model.getCard(current) if not self.currentCard: self.editor.setFact(None, True) return fact = self.currentCard.fact self.editor.setFact(fact, True) self.showCardInfo(self.currentCard) self.onEvent() def setupCardInfo(self): self.currentCard = None self.cardStats = CardStats(self.deck, None) def showCardInfo(self, card): self.cardStats.card = self.currentCard self.dialog.cardLabel.setText( self.cardStats.report()) # Menu helpers ###################################################################### def selectedCards(self): return [self.model.cards[idx.row()][0] for idx in self.dialog.tableView.selectionModel().selectedRows()] def selectedFacts(self): return self.deck.s.column0(""" select distinct factId from cards where id in (%s)""" % ",".join([ str(self.model.cards[idx.row()][0]) for idx in self.dialog.tableView.selectionModel().selectedRows()])) def selectedFactsAsCards(self): return self.deck.s.column0( "select id from cards where factId in (%s)" % ",".join([str(s) for s in self.selectedFacts()])) def updateAfterCardChange(self, reset=False): "Refresh info like stats on current card" self.currentRow = self.dialog.tableView.currentIndex() self.rowChanged(self.currentRow, None) if reset: self.updateSearch() self.drawTags() self.parent.moveToState("auto") # Menu options ###################################################################### def deleteCards(self): cards = self.selectedCards() n = _("Delete Cards") new = self.findCardInDeckModel() + 1 self.dialog.tableView.setFocus() self.deck.setUndoStart(n) self.deck.deleteCards(cards) self.deck.setUndoEnd(n) new = min(max(0, new), len(self.model.cards) - 1) self.dialog.tableView.selectRow(new) self.updateSearch() self.updateAfterCardChange() def addTags(self): (tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to add:")) if tags: n = _("Add Tags") self.parent.setProgressParent(self) self.deck.setUndoStart(n) self.deck.addTags(self.selectedFacts(), tags) self.deck.setUndoEnd(n) self.parent.setProgressParent(None) self.updateAfterCardChange(reset=True) def deleteTags(self): (tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to delete:")) if tags: n = _("Delete Tags") self.parent.setProgressParent(self) self.deck.setUndoStart(n) self.deck.deleteTags(self.selectedFacts(), tags) self.deck.setUndoEnd(n) self.parent.setProgressParent(None) self.updateAfterCardChange(reset=True) def onSuspend(self): n = _("Suspend") self.parent.setProgressParent(self) self.deck.setUndoStart(n) self.deck.addTags(self.selectedFacts(), "Suspended") self.deck.setUndoEnd(n) self.parent.setProgressParent(None) self.updateAfterCardChange(reset=True) def onUnsuspend(self): n = _("Unsuspend") self.parent.setProgressParent(self) self.deck.setUndoStart(n) self.deck.deleteTags(self.selectedFacts(), "Suspended") self.deck.setUndoEnd(n) self.parent.setProgressParent(None) self.updateAfterCardChange(reset=True) def reschedule(self): n = _("Reschedule") d = QDialog(self) frm = ankiqt.forms.reschedule.Ui_Dialog() frm.setupUi(d) if not d.exec_(): return self.deck.setUndoStart(n) try: if frm.asNew.isChecked(): self.deck.resetCards(self.selectedCards()) else: try: min = float(str(frm.rangeMin.text())) max = float(str(frm.rangeMax.text())) except ValueError: ui.utils.showInfo( _("Please enter a valid start and end range."), parent=self) return self.deck.rescheduleCards(self.selectedCards(), min, max) finally: self.deck.rebuildCounts(full=False) self.deck.rebuildQueue() self.deck.setUndoEnd(n) self.updateAfterCardChange(reset=True) def addCards(self): sf = self.selectedFacts() if not sf: return mods = self.deck.s.column0(""" select distinct modelId from facts where id in %s""" % ids2str(sf)) if not len(mods) == 1: ui.utils.showInfo( _("Can only operate on one model at a time."), parent=self) return # get cards to enable cms = [x.id for x in self.deck.s.query(Fact).get(sf[0]).\ model.cardModels] d = AddCardChooser(self, cms) if not d.exec_(): return # for each fact id, generate n = _("Generate Cards") self.parent.setProgressParent(self) self.deck.startProgress() self.deck.setUndoStart(n) facts = self.deck.s.query(Fact).filter( text("id in %s" % ids2str(sf))).order_by(Fact.created).all() self.deck.updateProgress(_("Generating Cards...")) for c, fact in enumerate(facts): self.deck.addCards(fact, d.selectedCms) if c % 50 == 0: self.deck.updateProgress() self.deck.flushMod() self.deck.updateAllPriorities() self.deck.finishProgress() self.parent.setProgressParent(None) self.deck.setUndoEnd(n) self.updateSearch() self.updateAfterCardChange() def cram(self): if ui.utils.askUser( _("Cram selected cards in new deck?"), help="CramMode", parent=self): self.close() self.parent.onCram(self.selectedCards()) def onChangeModel(self): sf = self.selectedFacts() mods = self.deck.s.column0(""" select distinct modelId from facts where id in %s""" % ids2str(sf)) if not len(mods) == 1: ui.utils.showInfo( _("Can only change one model at a time."), parent=self) return d = ChangeModelDialog(self, self.currentCard.fact.model, self.currentCard.cardModel) d.exec_() if d.ret: n = _("Change Model") self.parent.setProgressParent(self) self.deck.setUndoStart(n) self.deck.changeModel(sf, *d.ret) self.deck.setUndoEnd(n) self.parent.setProgressParent(None) self.updateSearch() self.updateAfterCardChange() # Edit: selection ###################################################################### def selectFacts(self): sm = self.dialog.tableView.selectionModel() cardIds = dict([(x, 1) for x in self.selectedFactsAsCards()]) for i, card in enumerate(self.model.cards): if card.id in cardIds: sm.select(self.model.index(i, 0), QItemSelectionModel.Select | QItemSelectionModel.Rows) def invertSelection(self): sm = self.dialog.tableView.selectionModel() items = sm.selection() self.dialog.tableView.selectAll() sm.select(items, QItemSelectionModel.Deselect | QItemSelectionModel.Rows) def reverseOrder(self): self.deck.setVar("reverseOrder", not self.deck.getInt("reverseOrder")) self.model.cards.reverse() self.model.reset() self.focusCurrentCard() # Edit: undo/redo ###################################################################### def setupHooks(self): addHook("postUndoRedo", self.postUndoRedo) addHook("currentCardDeleted", self.updateSearch) def teardownHooks(self): removeHook("postUndoRedo", self.postUndoRedo) removeHook("currentCardDeleted", self.updateSearch) def postUndoRedo(self): self.updateFilterLabel() self.updateSearch() self.updateAfterCardChange() def onUndo(self): self.deck.undo() def onRedo(self): self.deck.redo() # Edit: font ###################################################################### def onFont(self): d = QDialog(self) frm = ankiqt.forms.editfont.Ui_Dialog() frm.setupUi(d) frm.fontCombo.setCurrentFont(QFont( self.parent.config['editFontFamily'])) frm.fontSize.setValue(self.parent.config['editFontSize']) frm.lineSize.setValue(self.parent.config['editLineSize']) if d.exec_(): self.parent.config['editFontFamily'] = ( unicode(frm.fontCombo.currentFont().family())) self.parent.config['editFontSize'] = ( int(frm.fontSize.value())) self.parent.config['editLineSize'] = ( int(frm.lineSize.value())) self.updateFont() # Edit: replacing ###################################################################### def onFindReplace(self): sf = self.selectedFacts() if not sf: return mods = self.deck.s.column0(""" select distinct modelId from facts where id in %s""" % ids2str(sf)) if not len(mods) == 1: ui.utils.showInfo( _("Can only operate on one model at a time."), parent=self) return d = QDialog(self) frm = ankiqt.forms.findreplace.Ui_Dialog() frm.setupUi(d) fields = sorted(self.currentCard.fact.model.fieldModels, key=attrgetter("name")) frm.field.addItems(QStringList( [_("All Fields")] + [f.name for f in fields])) self.connect(frm.buttonBox, SIGNAL("helpRequested()"), self.onFindReplaceHelp) if not d.exec_(): return n = _("Find and Replace") self.parent.setProgressParent(self) self.deck.startProgress(2) self.deck.updateProgress(_("Replacing...")) self.deck.setUndoStart(n) self.deck.updateProgress() changed = None try: if frm.field.currentIndex() == 0: field = None else: field = fields[frm.field.currentIndex()-1].id changed = self.deck.findReplace(sf, unicode(frm.find.text()), unicode(frm.replace.text()), frm.re.isChecked(), field) except sre_constants.error: ui.utils.showInfo(_("Invalid regular expression."), parent=self) self.deck.setUndoEnd(n) self.deck.finishProgress() self.parent.setProgressParent(None) self.parent.reset() self.updateSearch() self.updateAfterCardChange() if changed is not None: ui.utils.showInfo(ngettext("%(a)d of %(b)d fact updated", "%(a)d of %(b)d facts updated", len(sf)) % { 'a': changed, 'b': len(sf), }, parent=self) def onFindReplaceHelp(self): QDesktopServices.openUrl(QUrl(ankiqt.appWiki + "Browser#FindReplace")) # Jumping ###################################################################### def onFirstCard(self): if not self.model.cards: return self.editor.saveFieldsNow() self.dialog.tableView.selectionModel().clear() self.dialog.tableView.selectRow(0) def onLastCard(self): if not self.model.cards: return self.editor.saveFieldsNow() self.dialog.tableView.selectionModel().clear() self.dialog.tableView.selectRow(len(self.model.cards) - 1) def onPreviousCard(self): if not self.model.cards: return self.editor.saveFieldsNow() row = self.dialog.tableView.currentIndex().row() row = max(0, row - 1) self.dialog.tableView.selectionModel().clear() self.dialog.tableView.selectRow(row) def onNextCard(self): if not self.model.cards: return self.editor.saveFieldsNow() row = self.dialog.tableView.currentIndex().row() row = min(len(self.model.cards) - 1, row + 1) self.dialog.tableView.selectionModel().clear() self.dialog.tableView.selectRow(row) def onFind(self): self.dialog.filterEdit.setFocus() self.dialog.filterEdit.selectAll() def onFact(self): self.editor.focusFirst() def onTags(self): self.dialog.tagList.setFocus() def onSort(self): self.dialog.sortBox.setFocus() # Help ###################################################################### def onHelp(self): QDesktopServices.openUrl(QUrl(ankiqt.appWiki + "Browser"))
def cardStats(self, card): from anki.stats import CardStats return CardStats(self, card).report()
def onAdvBrowserLoad(self, advBrowser): """Called when the Advanced Browser add-on has finished loading. Create and add all custom columns owned by this module.""" # Store a list of CustomColumns managed by this module. We later # use this to build our part of the context menu. self.customColumns = [] # Convenience method to create lambdas without scope clobbering def getOnSort(f): return lambda: f # Dummy CardStats object so we can use the time() function without # creating the object every time. cs = CardStats(None, None) # -- Columns -- # # First review def cFirstOnData(c, n, t): first = mw.col.db.scalar( "select min(id) from revlog where cid = ?", c.id) if first: return time.strftime("%Y-%m-%d", time.localtime(first / 1000)) cc = advBrowser.newCustomColumn( type='cfirst', name='First Review', onData=cFirstOnData, onSort=lambda: "(select min(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Last review def cLastOnData(c, n, t): last = mw.col.db.scalar("select max(id) from revlog where cid = ?", c.id) if last: return time.strftime("%Y-%m-%d", time.localtime(last / 1000)) cc = advBrowser.newCustomColumn( type='clast', name='Last Review', onData=cLastOnData, onSort=lambda: "(select max(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Average time def cAvgtimeOnData(c, n, t): avgtime = mw.col.db.scalar( "select avg(time) from revlog where cid = ?", c.id) if avgtime: return str(round(avgtime / 1000, 1)) + "s" cc = advBrowser.newCustomColumn( type='cavgtime', name='Time (Average)', onData=cAvgtimeOnData, onSort=lambda: "(select avg(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Total time def cTottimeOnDAta(c, n, t): tottime = mw.col.db.scalar( "select sum(time) from revlog where cid = ?", c.id) if tottime: return str(round(tottime / 1000, 1)) + "s" cc = advBrowser.newCustomColumn( type='ctottime', name='Time (Total)', onData=cTottimeOnDAta, onSort=lambda: "(select sum(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Tags cc = advBrowser.newCustomColumn( type='ntags', name='Tags', onData=lambda c, n, t: " ".join(unicode(tag) for tag in n.tags), # Lazy shortcut. Treat the "Tags" column as if it were a note field # (it is!) so we get all the benefits of our custom work on those # fields. onSort=lambda: "(select valueForField(mid, flds, 'Tags') " "from notes where id = c.nid)", ) self.customColumns.append(cc) # Remove the built-in tags column. advBrowser.removeColumn("noteTags") # Overdue interval def cOverdueIvl(c, n, t): val = self.valueForOverdue(c.odid, c.queue, c.type, c.due) if val: return str(val) + " day" + ('s' if val > 1 else '') srt = ("(select valueForOverdue(odid, queue, type, due) " "from cards where id = c.id)") cc = advBrowser.newCustomColumn(type='coverdueivl', name="Overdue Interval", onData=cOverdueIvl, onSort=getOnSort(srt)) self.customColumns.append(cc) # Previous interval def cPrevIvl(c, n, t): ivl = mw.col.db.scalar( "select ivl from revlog where cid = ? " "order by id desc limit 1 offset 1", c.id) if ivl is None: return elif ivl == 0: return "0 days" elif ivl > 0: return fmtTimeSpan(ivl * 86400) else: return cs.time(-ivl) srt = ("(select ivl from revlog where cid = c.id " "order by id desc limit 1 offset 1)") cc = advBrowser.newCustomColumn(type='cprevivl', name="Previous Interval", onData=cPrevIvl, onSort=getOnSort(srt)) self.customColumns.append(cc)
def _cardInfoData(self) -> Tuple[str, CardStats]: cs = CardStats(self.col, self.card) rep = cs.report(include_revlog=True) return rep, cs