Exemplo n.º 1
0
    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))
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
 def setupCardInfo(self):
     self.currentCard = None
     self.cardStats = CardStats(self.deck, None)
Exemplo n.º 4
0
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"))
Exemplo n.º 5
0
 def cardStats(self, card):
     from anki.stats import CardStats
     return CardStats(self, card).report()
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
 def _cardInfoData(self) -> Tuple[str, CardStats]:
     cs = CardStats(self.col, self.card)
     rep = cs.report(include_revlog=True)
     return rep, cs