Esempio n. 1
0
 def _renderQA(self, data):
     "Returns hash of id, question, answer."
     # data is [cid, nid, mid, did, ord, tags, flds]
     # unpack fields and create dict
     flist = splitFields(data[6])
     fields = {}
     model = self.models.get(data[2])
     for (name, (idx, conf)) in self.models.fieldMap(model).items():
         fields[name] = flist[idx]
     fields['Tags'] = data[5]
     fields['Type'] = model['name']
     fields['Deck'] = self.decks.name(data[3])
     template = model['tmpls'][data[4]]
     fields['Card'] = template['name']
     # render q & a
     d = dict(id=data[0])
     for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
         if type == "q":
             format = format.replace("cloze:", "cq:")
         else:
             format = format.replace("cloze:", "ca:")
         fields = runFilter("mungeFields", fields, model, data, self)
         html = anki.template.render(format, fields)
         d[type] = runFilter(
             "mungeQA", html, type, fields, model, data, self)
     return d
Esempio n. 2
0
File: deck.py Progetto: ChYi/libanki
 def _renderQA(self, model, gname, data):
     "Returns hash of id, question, answer."
     # data is [cid, fid, mid, gid, ord, tags, flds]
     # unpack fields and create dict
     flist = splitFields(data[6])
     fields = {}
     for (name, (idx, conf)) in model.fieldMap().items():
         fields[name] = flist[idx]
         if fields[name]:
             fields[name] = '<span class="fm%s-%s">%s</span>' % (
                 hexifyID(data[2]), hexifyID(idx), fields[name])
         else:
             fields[name] = ""
     fields['Tags'] = data[5]
     fields['Model'] = model.name
     fields['Group'] = gname
     template = model.templates[data[4]]
     fields['Template'] = template['name']
     # render q & a
     d = dict(id=data[0])
     for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
         if type == "q":
             format = format.replace("cloze:", "cq:")
         else:
             if model.conf['clozectx']:
                 name = "cactx:"
             else:
                 name = "ca:"
             format = format.replace("cloze:", name)
         fields = runFilter("mungeFields", fields, model, gname, data, self)
         html = anki.template.render(format, fields)
         d[type] = runFilter(
             "mungeQA", html, type, fields, model, gname, data, self)
     return d
Esempio n. 3
0
def formatQA(cid, mid, fact, tags, cm, deck,  build=False):
    "Return a dict of {id, question, answer}"
    d = {'id': cid}
    fields = {}
    for (k, v) in fact.items():
        fields["text:"+k] = stripHTML(v[1])
        if v[1]:
            fields[k] = '<span class="fm%s">%s</span>' % (
                hexifyID(v[0]), v[1])
        else:
            fields[k] = u""
    fields['tags'] = tags[0]
    fields['Tags'] = tags[0]
    fields['modelTags'] = tags[1]
    fields['cardModel'] = tags[2]
    # render q & a
    ret = []
    for (type, format) in (("question", cm.qformat),
                           ("answer", cm.aformat)):
        # convert old style
        format = re.sub("%\((.+?)\)s", "{{\\1}}", format)
        # allow custom rendering functions & info
        fields = runFilter("prepareFields", fields, cid, mid, fact, tags, cm, deck)
        html = render(format, fields)
        d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm, deck,  build)
    return d
Esempio n. 4
0
    def _renderPreview(self):
        self.cancelPreviewTimer()

        c = self.card
        ti = self.maybeTextInput

        bodyclass = bodyClass(self.mw.col, c)

        q = ti(mungeQA(self.mw.col, c.q(reload=True)))
        q = runFilter("prepareQA", q, c, "clayoutQuestion")

        a = ti(mungeQA(self.mw.col, c.a()), type='a')
        a = runFilter("prepareQA", a, c, "clayoutAnswer")

        # use _showAnswer to avoid the longer delay
        self.pform.frontWeb.eval("_showAnswer(%s,'%s');" % (json.dumps(q), bodyclass))
        self.pform.backWeb.eval("_showAnswer(%s, '%s');" % (json.dumps(a), bodyclass))

        clearAudioQueue()
        if c.id not in self.playedAudio:
            playFromText(c.q())
            playFromText(c.a())
            self.playedAudio[c.id] = True

        self.updateCardNames()
Esempio n. 5
0
 def addNote(self, deckName, modelName, fields, tags=list()):
     note = self.createNote(deckName, modelName, fields, tags)
     if note is not None:
         collection = self.collection()
         collection.addNote(note)
         collection.autosave()
         self.startEditing()
         srcIdx = 0
         for c, name in enumerate(mw.col.models.fieldNames(note.model())):
             if name == 'Expression':
                 srcIdx = c
         print "Source", srcIdx
         showInfo(str(srcIdx))
         runFilter("editFocusLost", False, note, srcIdx)#note.fields.index('Expression'))
         return note.id
Esempio n. 6
0
 def modSchema(self, check):
     "Mark schema modified. Call this first so user can abort if necessary."
     if not self.schemaChanged():
         if check and not runFilter("modSchema", True):
             raise AnkiError("abortSchemaMod")
     self.scm = intTime(1000)
     self.setMod()
Esempio n. 7
0
    def setupWeb(self):
        self.web = EditorWebView(self.widget, self)
        self.web.title = "editor"
        self.web.allowDrops = True
        self.web.onBridgeCmd = self.onBridgeCmd
        self.outerLayout.addWidget(self.web, 1)
        self.web.onLoadFinished = self._loadFinished

        topbuts = """
<div id="topbutsleft" style="float:left;">
<button onclick="pycmd('fields')">%(flds)s...</button>
<button onclick="pycmd('cards')">%(cards)s...</button>
</div>
<div id="topbutsright" style="float:right;">
<button tabindex=-1 class=linkb type="button" id=bold onclick="pycmd('bold');return false;"><img class=topbut src="qrc:/icons/text_bold.png"></button>
<button tabindex=-1 class=linkb type="button" id=italic  onclick="pycmd('italic');return false;"><img class=topbut src="qrc:/icons/text_italic.png"></button>
<button tabindex=-1 class=linkb type="button" id=underline  onclick="pycmd('underline');return false;"><img class=topbut src="qrc:/icons/text_under.png"></button>
<button tabindex=-1 class=linkb type="button" id=superscript  onclick="pycmd('super');return false;"><img class=topbut src="qrc:/icons/text_super.png"></button>
<button tabindex=-1 class=linkb type="button" id=subscript  onclick="pycmd('sub');return false;"><img class=topbut src="qrc:/icons/text_sub.png"></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('clear');return false;"><img class=topbut src="qrc:/icons/text_clear.png"></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('colour');return false;"><div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;" class=topbut></div></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('changeCol');return false;"><div style="display:inline-block; border-radius: 5px;" class="topbut rainbow"></div></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('cloze');return false;"><img class=topbut src="qrc:/icons/text_cloze.png"></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('attach');return false;"><img class=topbut src="qrc:/icons/paperclip.png"></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('record');return false;"><img class=topbut src="qrc:/icons/media-record.png"></button>
<button tabindex=-1 class=linkb type="button" onclick="pycmd('more');return false;"><img class=topbut src="qrc:/icons/more.png"></button>
</div>
        """ % dict(flds=_("Fields"), cards=_("Cards"))
        topbuts = runFilter("setupEditorButtons", topbuts)
        self.web.stdHtml(_html % (
            self.mw.baseHTML(), anki.js.jquery,
            topbuts,
            _("Show Duplicates")))
Esempio n. 8
0
def formatQA(cid, mid, fact, tags, cm):
    "Return a dict of {id, question, answer}"
    d = {'id': cid}
    fields = {}
    for (k, v) in fact.items():
        fields["text:"+k] = v[1]
        if v[1]:
            fields[k] = '<span class="fm%s">%s</span>' % (
                hexifyID(v[0]), v[1])
        else:
            fields[k] = u""
    fields['tags'] = tags[0]
    fields['Tags'] = tags[0]
    fields['modelTags'] = tags[1]
    fields['cardModel'] = tags[2]
    # render q & a
    ret = []
    for (type, format) in (("question", cm.qformat),
                           ("answer", cm.aformat)):
        try:
            html = format % fields
        except (KeyError, TypeError, ValueError):
            html = _("[invalid question/answer format]")
        d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm)
    return d
Esempio n. 9
0
    def _showQuestion(self):
        self._reps += 1
        self.state = "question"
        self.typedAnswer = None
        c = self.card
        # grab the question and play audio
        if c.isEmpty():
            q = _("""\
The front of this card is empty. Please run Tools>Empty Cards.""")
        else:
            q = c.q()
        if self.autoplay(c):
            playFromText(q)
        # render & update bottom
        q = self._mungeQA(q)
        q = runFilter("prepareQA", q, c, "reviewQuestion")

        bodyclass = bodyClass(self.mw.col, c)

        self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
        self._drawFlag()
        self._drawMark()
        self._showAnswerButton()
        # if we have a type answer field, focus main web
        if self.typeCorrect:
            self.mw.web.setFocus()
        # user hook
        runHook('showQuestion')
Esempio n. 10
0
    def setupStyle(self):
        buf = ""

        if isWin and platform.release() == '10':
            # add missing bottom border to menubar
            buf += """
QMenuBar {
  border-bottom: 1px solid #aaa;
  background: white;
}        
"""
            # qt bug? setting the above changes the browser sidebar
            # to white as well, so set it back
            buf += """
QTreeWidget {
  background: #eee;
}            
            """

        # allow addons to modify the styling
        buf = runFilter("setupStyle", buf)

        # allow users to extend styling
        p = os.path.join(aqt.mw.pm.base, "style.css")
        if os.path.exists(p):
            buf += open(p).read()

        self.app.setStyleSheet(buf)
Esempio n. 11
0
def myRenderQA(self, data, qfmt=None, afmt=None):
    "Returns hash of id, question, answer."
    # data is [cid, nid, mid, did, ord, tags, flds, flags]
    # unpack fields and create dict
    flist = splitFields(data[6])
    fields = {}
    model = self.models.get(data[2])
    for (name, (idx, conf)) in self.models.fieldMap(model).items():
        fields[name] = flist[idx]
    if len(data) > 7:
        shakyWarningFieldName = _getShakyWarningFieldName(data[7])
        if shakyWarningFieldName:
            fields[shakyWarningFieldName] = shakyWarningFieldName
    fields['Tags'] = data[5].strip()
    fields['Type'] = model['name']
    fields['Deck'] = self.decks.name(data[3])
    if model['type'] == MODEL_STD:
        template = model['tmpls'][data[4]]
    else:
        template = model['tmpls'][0]
    fields['Card'] = template['name']
    fields['c%d' % (data[4]+1)] = "1"
    # render q & a
    d = dict(id=data[0])
    qfmt = qfmt or template['qfmt']
    afmt = afmt or template['afmt']
    for (type, format) in (("q", qfmt), ("a", afmt)):
        if type == "q":
            format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format)
            format = format.replace("<%cloze:", "<%%cq:%d:" % (
                data[4]+1))
        else:
            format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4]+1), format)
            format = format.replace("<%cloze:", "<%%ca:%d:" % (
                data[4]+1))
            fields['FrontSide'] = stripSounds(d['q'])
        fields = runFilter("mungeFields", fields, model, data, self)
        html = anki.template.render(format, fields)
        d[type] = runFilter(
            "mungeQA", html, type, fields, model, data, self)
        # empty cloze?
        if type == 'q' and model['type'] == MODEL_CLOZE:
            if not self.models._availClozeOrds(model, data[6], False):
                d['q'] += ("<p>" + _(
            "Please edit this note and add some cloze deletions. (%s)") % (
            "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help"))))
    return d
Esempio n. 12
0
 def updateCard(self):
     c = self.cards[self.currentCard]
     styles = (self.deck.css +
               ("\nhtml { background: %s }" % c.cardModel.lastFontColour) +
               "\ndiv { white-space: pre-wrap; }")
     styles = runFilter("addStyles", styles, c)
     self.dialog.webView.setHtml(
         ('<html><head>%s</head><body>' % getBase(self.deck)) +
         "<style>" + styles + "</style>" +
         runFilter("drawQuestion", mungeQA(self.deck, c.htmlQuestion()),
                   c) +
         "<br><br><hr><br><br>" +
         runFilter("drawAnswer", mungeQA(self.deck, c.htmlAnswer()),
                   c)
         + "</body></html>")
     playFromText(c.question)
     playFromText(c.answer)
Esempio n. 13
0
 def addStyles(self):
     # card styles
     s = "<style>\n"
     if self.main.deck:
         s += self.main.deck.css
     s = runFilter("addStyles", s, self.main.currentCard)
     s += "</style>"
     return s
Esempio n. 14
0
File: deck.py Progetto: ChYi/libanki
 def modSchema(self, check=True):
     "Mark schema modified. Call this first so user can abort if necessary."
     if not self.schemaChanged():
         if check and not runFilter("modSchema", True):
             raise AnkiError("abortSchemaMod")
         # next sync will be full
         self.emptyTrash()
     self.scm = intTime()
Esempio n. 15
0
File: view.py Progetto: zw/ankiqt
 def addStyles(self):
     # card styles
     s = "<style>\n"
     if self.main.deck:
         s += self.main.deck.css
     s += "div { white-space: pre-wrap; }\n"
     s = runFilter("addStyles", s, self.main.currentCard)
     s += "</style>"
     return s
Esempio n. 16
0
 def renderPreview(self):
     c = self.card
     styles = self.deck.rebuildCSS() + ("\nhtml { background: %s }" % c.cardModel.lastFontColour)
     styles = runFilter("addStyles", styles, c)
     self.form.preview.setHtml(
         ("<html><head>%s</head><body>" % getBase(self.deck, c))
         + "<style>"
         + styles
         + "</style>"
         + runFilter("drawQuestion", mungeQA(self.deck, c.htmlQuestion()), c)
         + "<hr>"
         + runFilter("drawAnswer", mungeQA(self.deck, c.htmlAnswer()), c)
         + "</body></html>"
     )
     clearAudioQueue()
     if c.id not in self.playedAudio:
         playFromText(c.question)
         playFromText(c.answer)
         self.playedAudio[c.id] = True
Esempio n. 17
0
 def bridge(self, str):
     if not self.note or not runHook:
         # shutdown
         return
     # focus lost or key/button pressed?
     if str.startswith("blur") or str.startswith("key"):
         (type, txt) = str.split(":", 1)
         txt = self.mungeHTML(txt)
         # misbehaving apps may include a null byte in the text
         txt = txt.replace("\x00", "")
         # reverse the url quoting we added to get images to display
         txt = unicode(urllib2.unquote(
             txt.encode("utf8")), "utf8", "replace")
         # make sure a trailing <br /> is removed
         txt = re.sub("(<br />)*$", "", txt)
         self.note.fields[self.currentField] = txt
         if not self.addMode:
             self.note.flush()
             self.mw.requireReset()
         if type == "blur":
             self.disableButtons()
             # run any filters
             if runFilter(
                 "editFocusLost", False, self.note, self.currentField):
                 # something updated the note; schedule reload
                 def onUpdate():
                     self.stealFocus = True
                     self.loadNote()
                     self.stealFocus = False
                     self.checkValid()
                 self.mw.progress.timer(100, onUpdate, False)
             else:
                 self.checkValid()
         else:
             runHook("editTimer", self.note)
             self.checkValid()
     # focused into field?
     elif str.startswith("focus"):
         (type, num) = str.split(":", 1)
         self.enableButtons()
         self.currentField = int(num)
         runHook("editFocusGained", self.note, self.currentField)
     # state buttons changed?
     elif str.startswith("state"):
         (cmd, txt) = str.split(":", 1)
         r = json.loads(txt)
         self._buttons['text_bold'].setChecked(r['bold'])
         self._buttons['text_italic'].setChecked(r['italic'])
         self._buttons['text_under'].setChecked(r['under'])
         self._buttons['text_super'].setChecked(r['super'])
         self._buttons['text_sub'].setChecked(r['sub'])
     elif str.startswith("dupes"):
         self.showDupes()
     else:
         print str
Esempio n. 18
0
    def render_unescaped(self, tag_name=None, context=None):
        """Render a tag without escaping it."""
        txt = get_or_attr(context, tag_name)
        if txt is not None:
            # Some field names could have colons in them avoid
            # interpreting these as field modifiers better would
            # probably be to put some restrictions on field names
            return txt

        # field modifiers
        parts = tag_name.split(':')
        extra = None
        if len(parts) == 1 or parts[0] == '':
            return '{unknown field %s}' % tag_name
        else:
            mods, tag = parts[:-1], parts[-1]  # py3k has *mods, tag = parts

        txt = get_or_attr(context, tag)

        # Since 'text:' and other mods can affect html on which Anki
        # relies to process clozes, we need to make sure clozes are
        # always treated after all the other mods, regardless of how
        # they're specified in the template, so that {{cloze:text: ==
        # {{text:cloze:
        # For type:, we return directly since no other mod than cloze
        # (or other pre-defined mods) can be present and those are
        # treated separately
        mods.reverse()
        mods.sort(key=lambda s: not s == "type")

        for mod in mods:
            # built-in modifiers
            if mod == 'text':
                # strip html
                txt = stripHTML(txt) if txt else ""
            elif mod == 'type':
                # Type answer field; convert it to [[type:...]] for
                # the gui code to process
                return "[[%s]]" % tag_name
            elif mod.startswith('cq-') or mod.startswith('ca-'):
                # cloze deletion
                mod, extra = mod.split("-")
                txt = self.clozeText(
                    txt, extra, mod[1]) if txt and extra else ""
            else:
                # hook-based field modifier
                mod, extra = re.search("^(.*?)(?:\((.*)\))?$", mod).groups()
                txt = runFilter('fmod_' + mod, txt or '', extra or '', context,
                                tag, tag_name)
                if txt is None:
                    return '{unknown field %s}' % tag_name
        return txt
Esempio n. 19
0
 def drawQuestion(self, nosound=False):
     "Show the question."
     if not self.main.config['splitQA']:
         self.write("<br>")
     q = self.main.currentCard.htmlQuestion()
     if self.haveTop:
         height = 35
     else:
         height = 45
     q = runFilter("drawQuestion", q)
     self.write(self.center(self.mungeQA(self.main.deck, q), height))
     if self.state != self.oldState and not nosound:
         playFromText(q)
Esempio n. 20
0
 def bridge(self, str):
     if not self.note or not runHook:
         # shutdown
         return
     # focus lost or key/button pressed?
     if str.startswith("blur") or str.startswith("key"):
         (type, txt) = str.split(":", 1)
         txt = self.mungeHTML(txt)
         # misbehaving apps may include a null byte in the text
         txt = txt.replace("\x00", "")
         # reverse the url quoting we added to get images to display
         txt = unicode(urllib2.unquote(
             txt.encode("utf8")), "utf8", "replace")
         self.note.fields[self.currentField] = txt
         if type == "blur":
             self.disableButtons()
             # run any filters
             if runFilter(
                 "editFocusLost", False, self.note, self.currentField):
                 # something updated the note; schedule reload
                 def onUpdate():
                     self.loadNote()
                     self.checkValid()
             else:
                 self.checkValid()
         else:
             runHook("editTimer", self.note)
             self.checkValid()
     # focused into field?
     elif str.startswith("focus"):
         (type, num) = str.split(":", 1)
         self.enableButtons()
         self.currentField = int(num)
     # state buttons changed?
     elif str.startswith("state"):
         (cmd, txt) = str.split(":", 1)
         r = json.loads(txt)
         self._buttons['text_bold'].setChecked(r['bold'])
         self._buttons['text_italic'].setChecked(r['italic'])
         self._buttons['text_under'].setChecked(r['under'])
         self._buttons['text_super'].setChecked(r['super'])
         self._buttons['text_sub'].setChecked(r['sub'])
     elif str.startswith("dupes"):
         self.showDupes()
     # save current selection
     elif str.startswith("selection"):
         (type, start, end) = str.split(":", 2)
         self.currentSelection = (int(start), int(end))
     else:
         print str
Esempio n. 21
0
    def setupWeb(self):
        self.web = EditorWebView(self.widget, self)
        self.web.title = "editor"
        self.web.allowDrops = True
        self.web.onBridgeCmd = self.onBridgeCmd
        self.outerLayout.addWidget(self.web, 1)

        righttopbtns = list()
        righttopbtns.append(self._addButton('text_bold', 'bold', _("Bold text (Ctrl+B)"), id='bold'))
        righttopbtns.append(self._addButton('text_italic', 'italic', _("Italic text (Ctrl+I)"), id='italic'))
        righttopbtns.append(self._addButton('text_under', 'underline', _("Underline text (Ctrl+U)"), id='underline'))
        righttopbtns.append(self._addButton('text_super', 'super', _("Superscript (Ctrl++)"), id='superscript'))
        righttopbtns.append(self._addButton('text_sub', 'sub', _("Subscript (Ctrl+=)"), id='subscript'))
        righttopbtns.append(self._addButton('text_clear', 'clear', _("Remove formatting (Ctrl+R)")))
        # The color selection buttons do not use an icon so the HTML must be specified manually
        tip = _("Set foreground colour (F7)")
        righttopbtns.append('''<button tabindex=-1 class=linkb title="{}"
            type="button" onclick="pycmd('colour');return false;">
            <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;"
            class=topbut></div></button>'''.format(tip))
        tip = _("Change colour (F8)")
        righttopbtns.append('''<button tabindex=-1 class=linkb title="{}"
            type="button" onclick="pycmd('changeCol');return false;">
            <div style="display:inline-block; border-radius: 5px;"
            class="topbut rainbow"></div></button>'''.format(tip))
        righttopbtns.append(self._addButton('text_cloze', 'cloze', _("Cloze deletion (Ctrl+Shift+C)")))
        righttopbtns.append(self._addButton('paperclip', 'attach', _("Attach pictures/audio/video (F3)")))
        righttopbtns.append(self._addButton('media-record', 'record', _("Record audio (F5)")))
        righttopbtns.append(self._addButton('more', 'more'))
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)
        topbuts = """
            <div id="topbutsleft" style="float:left;">
                <button title='%(fldsTitle)s' onclick="pycmd('fields')">%(flds)s...</button>
                <button title='%(cardsTitle)s' onclick="pycmd('cards')">%(cards)s...</button>
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns),
                   fldsTitle=_("Customize Fields"),
                   cardsTitle=shortcut(_("Customize Card Templates (Ctrl+L)")))
        bgcol = self.mw.app.palette().window().color().name()
        # then load page
        self.web.stdHtml(_html % (
            bgcol, bgcol,
            topbuts,
            _("Show Duplicates")),
                         css=["editor.css"],
                         js=["jquery.js", "editor.js"])
Esempio n. 22
0
File: editor.py Progetto: hans/anki
 def setupShortcuts(self):
     cuts = [
         ("Ctrl+L", self.onCardLayout),
         ("Ctrl+B", self.toggleBold),
         ("Ctrl+I", self.toggleItalic),
         ("Ctrl+U", self.toggleUnderline),
         ("Ctrl+Shift+=", self.toggleSuper),
         ("Ctrl+=", self.toggleSub),
         ("Ctrl+R", self.removeFormat),
         ("F7", self.onForeground),
         ("F8", self.onChangeCol),
         ("Ctrl+Shift+C", self.onCloze),
         ("Ctrl+Shift+Alt+C", self.onCloze),
         ("F3", self.onAddMedia),
         ("F5", self.onRecSound),
         ("Ctrl+T, T", self.insertLatex),
         ("Ctrl+T, E", self.insertLatexEqn),
         ("Ctrl+T, M", self.insertLatexMathEnv),
         ("Ctrl+Shift+X", self.onHtmlEdit),
         ("Ctrl+Shift+T", lambda: self.tags.setFocus),
     ]
     runFilter("setupEditorShortcuts", cuts)
     for keys, fn in cuts:
         QShortcut(QKeySequence(keys), self.widget, activated=fn)
Esempio n. 23
0
 def onBridgeCmd(self, cmd):
     if not self.note or not runHook:
         # shutdown
         return
     # focus lost or key/button pressed?
     if cmd.startswith("blur") or cmd.startswith("key"):
         (type, ord, nid, txt) = cmd.split(":", 3)
         ord = int(ord)
         try:
             nid = int(nid)
         except ValueError:
             nid = 0
         if nid != self.note.id:
             print("ignored late blur")
             return
         txt = urllib.parse.unquote(txt)
         txt = unicodedata.normalize("NFC", txt)
         txt = self.mungeHTML(txt)
         # misbehaving apps may include a null byte in the text
         txt = txt.replace("\x00", "")
         # reverse the url quoting we added to get images to display
         txt = self.mw.col.media.escapeImages(txt, unescape=True)
         self.note.fields[ord] = txt
         if not self.addMode:
             self.note.flush()
             self.mw.requireReset()
         if type == "blur":
             self.currentField = None
             # run any filters
             if runFilter(
                 "editFocusLost", False, self.note, ord):
                 # something updated the note; update it after a subsequent focus
                 # event has had time to fire
                 self.mw.progress.timer(100, self.loadNoteKeepingFocus, False)
             else:
                 self.checkValid()
         else:
             runHook("editTimer", self.note)
             self.checkValid()
     # focused into field?
     elif cmd.startswith("focus"):
         (type, num) = cmd.split(":", 1)
         self.currentField = int(num)
         runHook("editFocusGained", self.note, self.currentField)
     elif cmd in self._links:
         self._links[cmd](self)
     else:
         print("uncaught cmd", cmd)
Esempio n. 24
0
 def _showAnswer(self):
     if self.mw.state != "review":
         # showing resetRequired screen; ignore space
         return
     self.state = "answer"
     c = self.card
     a = c.a()
     # play audio?
     if self.autoplay(c):
         playFromText(a)
     a = self._mungeQA(a)
     a = runFilter("prepareQA", a, c, "reviewAnswer")
     # render and update bottom
     self.web.eval("_showAnswer(%s);" % json.dumps(a))
     self._showEaseButtons()
     # user hook
     runHook('showAnswer')
Esempio n. 25
0
 def drawAnswer(self):
     "Show the answer."
     a = self.main.currentCard.htmlAnswer()
     a = runFilter("drawAnswer", a, self.main.currentCard)
     if self.main.currentCard.cardModel.typeAnswer:
         try:
             cor = stripMedia(stripHTML(self.main.currentCard.fact[self.main.currentCard.cardModel.typeAnswer]))
         except KeyError:
             self.main.currentCard.cardModel.typeAnswer = ""
             cor = ""
         if cor:
             given = unicode(self.main.typeAnswerField.text())
             res = self.correct(cor, given)
             a = res + "<br>" + a
     self.write(self.center("<span id=answer />" + self.mungeQA(self.main.deck, a)))
     if self.state != self.oldState:
         playFromText(a)
Esempio n. 26
0
 def onBridgeCmd(self, cmd):
     if not self.note or not runHook:
         # shutdown
         return
     # focus lost or key/button pressed?
     if cmd.startswith("blur") or cmd.startswith("key"):
         (type, txt) = cmd.split(":", 1)
         txt = urllib.parse.unquote(txt)
         txt = unicodedata.normalize("NFC", txt)
         txt = self.mungeHTML(txt)
         # misbehaving apps may include a null byte in the text
         txt = txt.replace("\x00", "")
         # reverse the url quoting we added to get images to display
         txt = self.mw.col.media.escapeImages(txt, unescape=True)
         self.note.fields[self.currentField] = txt
         if not self.addMode:
             self.note.flush()
             self.mw.requireReset()
         if type == "blur":
             # run any filters
             if runFilter(
                 "editFocusLost", False, self.note, self.currentField):
                 # something updated the note; schedule reload
                 def onUpdate():
                     if not self.note:
                         return
                     self.stealFocus = True
                     self.loadNote()
                     self.checkValid()
                 self.mw.progress.timer(100, onUpdate, False)
             else:
                 self.checkValid()
         else:
             runHook("editTimer", self.note)
             self.checkValid()
     # focused into field?
     elif cmd.startswith("focus"):
         (type, num) = cmd.split(":", 1)
         self.currentField = int(num)
         runHook("editFocusGained", self.note, self.currentField)
     elif cmd in self._links:
         self._links[cmd](self)
     else:
         print("uncaught cmd", cmd)
Esempio n. 27
0
 def onBridgeCmd(self, cmd):
     if not self.note or not runHook:
         # shutdown
         return
     # focus lost or key/button pressed?
     if cmd.startswith("blur") or cmd.startswith("key"):
         (type, txt) = cmd.split(":", 1)
         txt = urllib.parse.unquote(txt)
         txt = unicodedata.normalize("NFC", txt)
         txt = self.mungeHTML(txt)
         # misbehaving apps may include a null byte in the text
         txt = txt.replace("\x00", "")
         # reverse the url quoting we added to get images to display
         txt = self.mw.col.media.escapeImages(txt, unescape=True)
         self.note.fields[self.currentField] = txt
         if not self.addMode:
             self.note.flush()
             self.mw.requireReset()
         if type == "blur":
             # run any filters
             if runFilter(
                 "editFocusLost", False, self.note, self.currentField):
                 # something updated the note; schedule reload
                 def onUpdate():
                     if not self.note:
                         return
                     self.stealFocus = True
                     self.loadNote()
                     self.checkValid()
                 self.mw.progress.timer(100, onUpdate, False)
             else:
                 self.checkValid()
         else:
             runHook("editTimer", self.note)
             self.checkValid()
     # focused into field?
     elif cmd.startswith("focus"):
         (type, num) = cmd.split(":", 1)
         self.currentField = int(num)
         runHook("editFocusGained", self.note, self.currentField)
     elif cmd in self._links:
         self._links[cmd](self)
     else:
         print("uncaught cmd", cmd)
Esempio n. 28
0
    def render_unescaped(self, tag_name=None, context=None):
        """Render a tag without escaping it."""
        txt = get_or_attr(context, tag_name)
        if txt is not None:
            # some field names could have colons in them
            # avoid interpreting these as field modifiers
            # better would probably be to put some restrictions on field names
            return txt

        # field modifiers
        parts = tag_name.split(':', 2)
        extra = None
        if len(parts) == 1 or parts[0] == '':
            return '{unknown field %s}' % tag_name
        elif len(parts) == 2:
            (mod, tag) = parts
        elif len(parts) == 3:
            (mod, extra, tag) = parts

        txt = get_or_attr(context, tag)

        # built-in modifiers
        if mod == 'text':
            # strip html
            if txt:
                return stripHTML(txt)
            return ""
        elif mod == 'type':
            # type answer field; convert it to [[type:...]] for the gui code
            # to process
            return "[[%s]]" % tag_name
        elif mod == 'cq' or mod == 'ca':
            # cloze deletion
            if txt and extra:
                return self.clozeText(txt, extra, mod[1])
            else:
                return ""
        else:
            # hook-based field modifier
            txt = runFilter('fmod_' + mod, txt or '', extra, context, tag,
                            tag_name)
            if txt is None:
                return '{unknown field %s}' % tag_name
            return txt
Esempio n. 29
0
    def setupWeb(self):
        self.web = EditorWebView(self.widget, self)
        self.web.title = "editor"
        self.web.allowDrops = True
        self.web.onBridgeCmd = self.onBridgeCmd
        self.outerLayout.addWidget(self.web, 1)
        self.web.onLoadFinished = self._loadFinished

        righttopbtns = list()
        righttopbtns.append(self._addButton('text_bold', 'bold', "Bold text (Ctrl+B)", id='bold'))
        righttopbtns.append(self._addButton('text_italic', 'italic', "Italic text (Ctrl+I)", id='italic'))
        righttopbtns.append(self._addButton('text_under', 'underline', "Underline text (Ctrl+U)", id='underline'))
        righttopbtns.append(self._addButton('text_super', 'super', "Superscript (Ctrl+Shift+=)", id='superscript'))
        righttopbtns.append(self._addButton('text_sub', 'sub', "Subscript (Ctrl+=)", id='subscript'))
        righttopbtns.append(self._addButton('text_clear', 'clear', "Remove formatting (Ctrl+R)"))
        # The color selection buttons do not use an icon so the HTML must be specified manually
        righttopbtns.append('''<button tabindex=-1 class=linkb title="Set foreground colour (F7)"
            type="button" onclick="pycmd('colour');return false;">
            <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;"
            class=topbut></div></button>''')
        righttopbtns.append('''<button tabindex=-1 class=linkb title="Change colour (F8)"
            type="button" onclick="pycmd('changeCol');return false;">
            <div style="display:inline-block; border-radius: 5px;"
            class="topbut rainbow"></div></button>''')
        righttopbtns.append(self._addButton('text_cloze', 'cloze', "Cloze deletion (Ctrl+Shift+C)"))
        righttopbtns.append(self._addButton('paperclip', 'attach', "Attach pictures/audio/video (F3)"))
        righttopbtns.append(self._addButton('media-record', 'record', "Record audio (F5)"))
        righttopbtns.append(self._addButton('more', 'more'))
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)
        topbuts = """
            <div id="topbutsleft" style="float:left;">
                <button onclick="pycmd('fields')">%(flds)s...</button>
                <button onclick="pycmd('cards')">%(cards)s...</button>
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns))
        bgcol = self.mw.app.palette().window().color().name()
        # then load page
        self.web.stdHtml(_html % (
            bgcol,
            topbuts,
            _("Show Duplicates")), js=anki.js.jquery, head=self.mw.baseHTML())
Esempio n. 30
0
    def render_unescaped(self, tag_name=None, context=None):
        """Render a tag without escaping it."""
        txt = get_or_attr(context, tag_name)
        if txt is not None:
            # some field names could have colons in them
            # avoid interpreting these as field modifiers
            # better would probably be to put some restrictions on field names
            return txt

        # field modifiers
        parts = tag_name.split(':',2)
        extra = None
        if len(parts) == 1 or parts[0] == '':
            return '{unknown field %s}' % tag_name
        elif len(parts) == 2:
            (mod, tag) = parts
        elif len(parts) == 3:
            (mod, extra, tag) = parts

        txt = get_or_attr(context, tag)

        # built-in modifiers
        if mod == 'text':
            # strip html
            if txt:
                return stripHTML(txt)
            return ""
        elif mod == 'type':
            # type answer field; convert it to [[type:...]] for the gui code
            # to process
            return "[[%s]]" % tag_name
        elif mod == 'cq' or mod == 'ca':
            # cloze deletion
            if txt and extra:
                return self.clozeText(txt, extra, mod[1])
            else:
                return ""
        else:
            # hook-based field modifier
            txt = runFilter('fmod_' + mod, txt or '', extra, context,
                            tag, tag_name);
            if txt is None:
                return '{unknown field %s}' % tag_name
            return txt
Esempio n. 31
0
    def setupWeb(self):
        self.web = EditorWebView(self.widget, self)
        self.web.title = "editor"
        self.web.allowDrops = True
        self.web.onBridgeCmd = self.onBridgeCmd
        self.outerLayout.addWidget(self.web, 1)
        self.web.onLoadFinished = self._loadFinished

        righttopbtns = list()
        righttopbtns.append(self._addButton('text_bold', 'bold', "Bold text (Ctrl+B)", id='bold'))
        righttopbtns.append(self._addButton('text_italic', 'italic', "Italic text (Ctrl+I)", id='italic'))
        righttopbtns.append(self._addButton('text_under', 'underline', "Underline text (Ctrl+U)", id='underline'))
        righttopbtns.append(self._addButton('text_super', 'super', "Superscript (Ctrl+Shift+=)", id='superscript'))
        righttopbtns.append(self._addButton('text_sub', 'sub', "Subscript (Ctrl+=)", id='subscript'))
        righttopbtns.append(self._addButton('text_clear', 'clear', "Remove formatting (Ctrl+R)"))
        # The color selection buttons do not use an icon so the HTML must be specified manually
        righttopbtns.append('''<button tabindex=-1 class=linkb title="Set foreground colour (F7)"
            type="button" onclick="pycmd('colour');return false;">
            <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;"
            class=topbut></div></button>''')
        righttopbtns.append('''<button tabindex=-1 class=linkb title="Change colour (F8)"
            type="button" onclick="pycmd('changeCol');return false;">
            <div style="display:inline-block; border-radius: 5px;"
            class="topbut rainbow"></div></button>''')
        righttopbtns.append(self._addButton('text_cloze', 'cloze', "Cloze deletion (Ctrl+Shift+C)"))
        righttopbtns.append(self._addButton('paperclip', 'attach', "Attach pictures/audio/video (F3)"))
        righttopbtns.append(self._addButton('media-record', 'record', "Record audio (F5)"))
        righttopbtns.append(self._addButton('more', 'more'))
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)
        topbuts = """
            <div id="topbutsleft" style="float:left;">
                <button onclick="pycmd('fields')">%(flds)s...</button>
                <button onclick="pycmd('cards')">%(cards)s...</button>
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(flds=_("Fields"), cards=_("Cards"), rightbts="".join(righttopbtns))
        self.web.stdHtml(_html % (
            self.mw.baseHTML(), anki.js.jquery,
            topbuts,
            _("Show Duplicates")))
Esempio n. 32
0
 def drawAnswer(self):
     "Show the answer."
     a = self.main.currentCard.htmlAnswer()
     a = runFilter("drawAnswer", a, self.main.currentCard)
     if self.main.currentCard.cardModel.typeAnswer:
         try:
             cor = stripMedia(
                 stripHTML(self.main.currentCard.fact[
                     self.main.currentCard.cardModel.typeAnswer]))
         except KeyError:
             self.main.currentCard.cardModel.typeAnswer = ""
             cor = ""
         if cor:
             given = unicode(self.main.typeAnswerField.text())
             res = self.correct(cor, given)
             a = res + "<br>" + a
     self.write(
         self.center('<span id=answer />' +
                     self.mungeQA(self.main.deck, a)))
     if self.state != self.oldState:
         playFromText(a)
Esempio n. 33
0
    def setupWeb(self) -> None:
        self.web = EditorWebView(self.widget, self)
        self.web.set_bridge_command(self.onBridgeCmd, self)
        self.outerLayout.addWidget(self.web, 1)

        # then load page
        self.web.stdHtml(
            "",
            css=["css/editor.css"],
            js=["js/editor.js"],
            context=self,
            default_css=False,
        )

        lefttopbtns: list[str] = []
        gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)

        lefttopbtns_defs = [
            f"noteEditorPromise.then((noteEditor) => noteEditor.toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1));"
            for button in lefttopbtns
        ]
        lefttopbtns_js = "\n".join(lefttopbtns_defs)

        righttopbtns: list[str] = []
        gui_hooks.editor_did_init_buttons(righttopbtns, self)
        # legacy filter
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)

        righttopbtns_defs = ", ".join(
            [json.dumps(button) for button in righttopbtns])
        righttopbtns_js = (f"""
noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{
    component: editorToolbar.AddonButtons,
    id: "addons",
    props: {{ buttons: [ {righttopbtns_defs} ] }},
}}));
""" if len(righttopbtns) > 0 else "")

        self.web.eval(f"{lefttopbtns_js} {righttopbtns_js}")
Esempio n. 34
0
def formatQA(cid, mid, fact, tags, cm):
    "Return a dict of {id, question, answer}"
    d = {'id': cid}
    fields = {}
    for (k, v) in fact.items():
        fields["text:" + k] = stripHTML(v[1])
        if v[1]:
            fields[k] = '<span class="fm%s">%s</span>' % (hexifyID(v[0]), v[1])
        else:
            fields[k] = u""
    fields['tags'] = tags[0]
    fields['Tags'] = tags[0]
    fields['modelTags'] = tags[1]
    fields['cardModel'] = tags[2]
    # render q & a
    ret = []
    for (type, format) in (("question", cm.qformat), ("answer", cm.aformat)):
        try:
            html = format % fields
        except (KeyError, TypeError, ValueError):
            html = _("[invalid question/answer format]")
        d[type] = runFilter("formatQA", html, type, cid, mid, fact, tags, cm)
    return d
Esempio n. 35
0
def apply_custom_filters(
    rendered: TemplateReplacementList,
    ctx: TemplateRenderContext,
    front_side: str | None,
) -> str:
    "Complete rendering by applying any pending custom filters."
    # template already fully rendered?
    if len(rendered) == 1 and isinstance(rendered[0], str):
        return rendered[0]

    res = ""
    for node in rendered:
        if isinstance(node, str):
            res += node
        else:
            # do we need to inject in FrontSide?
            if node.field_name == "FrontSide" and front_side is not None:
                node.current_text = front_side

            field_text = node.current_text
            for filter_name in node.filters:
                field_text = hooks.field_filter(
                    field_text, node.field_name, filter_name, ctx
                )
                # legacy hook - the second and fifth argument are no longer used.
                field_text = hooks.runFilter(
                    f"fmod_{filter_name}",
                    field_text,
                    "",
                    ctx.note().items(),
                    node.field_name,
                    "",
                )

            res += field_text
    return res
Esempio n. 36
0
def showQuestionWithoutAudio(self):
    self._reps += 1
    self.state = "question"
    self.typedAnswer = None
    c = self.card
    # grab the question and play audio
    if c.isEmpty():
        q = _("""\
The front of this card is empty. Please run Tools>Empty Cards.""")
    else:
        q = c.q()
    # render & update bottom
    q = self._mungeQA(q)
    q = runFilter("prepareQA", q, c, "reviewQuestion")

    bodyclass = bodyClass(self.mw.col, c)

    self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
    self._drawFlag()
    self._drawMark()
    self._showAnswerButton()
    # if we have a type answer field, focus main web
    if self.typeCorrect:
        self.mw.web.setFocus()
Esempio n. 37
0
 def fonts(self):
     return [(runFilter("mungeEditingFontName",
                        f["font"]), f["size"], f["rtl"])
             for f in self.note.model()["flds"]]
Esempio n. 38
0
    def setupWeb(self):
        self.web = EditorWebView(self.widget, self)
        self.web.title = "editor"
        self.web.allowDrops = True
        self.web.onBridgeCmd = self.onBridgeCmd
        self.outerLayout.addWidget(self.web, 1)

        righttopbtns: List[str] = [
            self._addButton("text_bold",
                            "bold",
                            _("Bold text (Ctrl+B)"),
                            id="bold"),
            self._addButton("text_italic",
                            "italic",
                            _("Italic text (Ctrl+I)"),
                            id="italic"),
            self._addButton("text_under",
                            "underline",
                            _("Underline text (Ctrl+U)"),
                            id="underline"),
            self._addButton("text_super",
                            "super",
                            _("Superscript (Ctrl++)"),
                            id="superscript"),
            self._addButton("text_sub",
                            "sub",
                            _("Subscript (Ctrl+=)"),
                            id="subscript"),
            self._addButton("text_clear", "clear",
                            _("Remove formatting (Ctrl+R)")),
        ]
        # The color selection buttons do not use an icon so the HTML must be specified manually
        tip = _("Set foreground colour (F7)")
        righttopbtns.append("""<button tabindex=-1 class=linkb title="{}"
            type="button" onclick="pycmd('colour');return false;">
            <div id=forecolor style="display:inline-block; background: #000;border-radius: 5px;"
            class=topbut></div></button>""".format(tip))
        tip = _("Change colour (F8)")
        righttopbtns.extend([
            """<button tabindex=-1 class=linkb title="{}"
                type="button" onclick="pycmd('changeCol');return false;">
                <div style="display:inline-block; border-radius: 5px;"
                class="topbut rainbow"></div></button>""".format(tip),
            self._addButton("text_cloze", "cloze",
                            _("Cloze deletion (Ctrl+Shift+C)")),
            self._addButton("paperclip", "attach",
                            _("Attach pictures/audio/video (F3)")),
            self._addButton("media-record", "record", _("Record audio (F5)")),
            self._addButton("more", "more"),
        ])
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)
        topbuts = """
            <div id="topbutsleft" style="float:left;">
                <button title='%(fldsTitle)s' onclick="pycmd('fields')">%(flds)s...</button>
                <button title='%(cardsTitle)s' onclick="pycmd('cards')">%(cards)s...</button>
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(
            flds=_("Fields"),
            cards=_("Cards"),
            rightbts="".join(righttopbtns),
            fldsTitle=_("Customize Fields"),
            cardsTitle=shortcut(_("Customize Card Templates (Ctrl+L)")),
        )
        bgcol = self.mw.app.palette().window().color().name()
        # then load page
        self.web.stdHtml(
            _html % (bgcol, bgcol, topbuts, _("Show Duplicates")),
            css=["editor.css"],
            js=["jquery.js", "editor.js"],
        )
Esempio n. 39
0
def _renderQA(self, data, qfmt=None, afmt=None):
    """Returns dict with id, question, answer, and whether a field is shown in question.

    Keyword arguments:
    data -- [cid, nid, mid, did, ord, tags, flds, cardFlags] (see db
    documentation for more information about those values)
    flds is a list of fields, not a dict.
    This corresponds to the information you can obtain in templates, using {{Tags}}, {{Type}}, etc..

    qfmt -- question format string (as in template)
    afmt -- answer format string (as in template)

    unpack fields and create dict
    TODO comment better

    """
    cid = data[0]
    nid = data[1]
    mid = data[2]
    did = data[3]
    ord = data[4]
    tags = data[5]
    flds = data[6]
    if len(
            data
    ) >= 8:  #this is a recent addition, some older version of anki does not have data[7]
        cardFlags = data[7]
    flist = splitFields(flds)  #the list of fields
    fields = {}  #
    #name -> ord for each field, tags
    # Type: the name of the model,
    # Deck, Subdeck: their name
    # Card: the template name
    # cn: 1 for n being the ord+1
    # FrontSide :
    model = self.models.get(mid)
    assert model is not None  #new (and fieldMap and items were not variables, but directly used
    fieldMap = self.models.fieldMap(model)
    items = list(fieldMap.items())
    for (name, (idx, conf)) in list(items):  #conf is not used
        fields[name] = flist[idx]
    fields['Tags'] = tags.strip()
    fields['Type'] = model['name']
    fields['Deck'] = self.decks.name(did)
    fields['Subdeck'] = fields['Deck'].split('::')[-1]
    if len(data) >= 8:
        fields['CardFlag'] = self._flagNameFromCardFlags(cardFlags)
    if model[
            'type'] == MODEL_STD:  #Note that model['type'] has not the same meaning as fields['Type']
        template = model['tmpls'][ord]
    else:  #for cloze deletions
        template = model['tmpls'][0]
    fields['Card'] = template['name']
    fields['c%d' % (ord + 1)] = "1"
    # render q & a
    d = dict(id=cid)
    # id: card id
    qfmt = qfmt or template['qfmt']
    afmt = afmt or template['afmt']
    for (type, format) in (("q", qfmt), ("a", afmt)):
        if type == "q":  #if/else is in the loop in order for d['q'] to be defined below
            format = re.sub("{{(?!type:)(.*?)cloze:",
                            r"{{\1cq-%d:" % (ord + 1), format)
            #Replace {{'foo'cloze: by {{'foo'cq-(ord+1), where 'foo' does not begins with "type:"
            format = format.replace("<%cloze:", "<%%cq:%d:" % (ord + 1))
            #Replace <%cloze: by <%%cq:(ord+1)
        else:
            format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (ord + 1), format)
            #Replace {{'foo'cloze: by {{'foo'ca-(ord+1)
            format = format.replace("<%cloze:", "<%%ca:%d:" % (ord + 1))
            #Replace <%cloze: by <%%ca:(ord+1)
            fields['FrontSide'] = stripSounds(d['q'])
            #d['q'] is defined during loop's first iteration
        fields = runFilter("mungeFields", fields, model, data,
                           self)  # TODO check
        html, showAField = anki.template.render(
            format,
            fields)  #replace everything of the form {{ by its value #MODIFIED
        d["showAField"] = showAField  #MODIFIED
        d[type] = runFilter("mungeQA", html, type, fields, model, data,
                            self)  # TODO check
        # empty cloze?
        if type == 'q' and model['type'] == MODEL_CLOZE:
            if not self.models._availClozeOrds(model, flds, False):
                d['q'] += ("<p>" + _(
                    "Please edit this note and add some cloze deletions. (%s)")
                           % ("<a href=%s#cloze>%s</a>" %
                              (HELP_SITE, _("help"))))
                #in the case where there is a cloze note type
                #without {{cn in fields indicated by
                #{{cloze:fieldName; an error message should be
                #shown
    return d
Esempio n. 40
0
 def fonts(self):
     return [(runFilter("mungeEditingFontName", f['font']),
              f['size'], f['rtl'])
             for f in self.note.model()['flds']]
Esempio n. 41
0
def _renderQA(self, data, qfmt=None, afmt=None):
    """Returns hash of id, question, answer.

    Keyword arguments:
    data -- [cid, nid, mid, did, ord, tags, flds] (see db
    documentation for more information about those values)
    This corresponds to the information you can obtain in templates, using {{Tags}}, {{Type}}, etc..
    qfmt -- question format string (as in template)
    afmt -- answer format string (as in template)

    unpack fields and create dict
    TODO comment better

    """
    cid, nid, mid, did, ord, tags, flds, cardFlags = data
    flist = splitFields(flds)#the list of fields
    fields = {} #
    #name -> ord for each field, tags
    # Type: the name of the model,
    # Deck, Subdeck: their name
    # Card: the template name
    # cn: 1 for n being the ord+1
    # FrontSide :
    model = self.models.get(mid)
    for (name, (idx, conf)) in list(self.models.fieldMap(model).items()):#conf is not used
        fields[name] = flist[idx]
    fields['Tags'] = tags.strip()
    fields['Type'] = model['name']
    fields['Deck'] = self.decks.name(did)
    fields['Subdeck'] = fields['Deck'].split('::')[-1]
    if model['type'] == MODEL_STD:#Note that model['type'] has not the same meaning as fields['Type']
        template = model['tmpls'][ord]
    else:#for cloze deletions
        template = model['tmpls'][0]
    fields['Card'] = template['name']
    fields['c%d' % (ord+1)] = "1"
    # render q & a
    d = dict(id=cid)
    # id: card id
    qfmt = qfmt or template['qfmt']
    afmt = afmt or template['afmt']
    start = time.clock()
    for (type, format) in (("q", qfmt), ("a", afmt)):
        if type == "q":
            format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (ord+1), format)
            #Replace {{'foo'cloze: by {{'foo'cq-(ord+1), where 'foo' does not begins with "type:"
            format = format.replace("<%cloze:", "<%%cq:%d:" % (
                ord+1))
            #Replace <%cloze: by <%%cq:(ord+1)
        else:
            format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (ord+1), format)
            #Replace {{'foo'cloze: by {{'foo'ca-(ord+1)
            format = format.replace("<%cloze:", "<%%ca:%d:" % (
                ord+1))
            #Replace <%cloze: by <%%ca:(ord+1)
            fields['FrontSide'] = stripSounds(d['q'])
            #d['q'] is defined during loop's first iteration
        fields = runFilter("mungeFields", fields, model, data, self) # TODO check
        html = anki.template.render(format, fields) #replace everything of the form {{ by its value TODO check
        d[type] = runFilter(
            "mungeQA", html, type, fields, model, data, self) # TODO check
        # empty cloze?
        if type == 'q' and model['type'] == MODEL_CLOZE:
            if not self.models._availClozeOrds(model, flds, False):
                d['q'] += ("<p>" + _(
            "Please edit this note and add some cloze deletions. (%s)") % (
            "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help"))))
                #in the case where there is a cloze note type
                #without {{cn in fields indicated by
                #{{cloze:fieldName; an error message should be
                #shown
    end = time.clock()
    dif = end-start
    print(f"Renders took {dif} seconds")
    return d
Esempio n. 42
0
 def remEmptyCards(self, ids):
     if not ids:
         return
     if runFilter("remEmptyCards", len(ids), True):
         self.remCards(ids)
Esempio n. 43
0
    def report(self):
        "Return an HTML string with a report."
        fmtPerc = anki.utils.fmtPercentage
        fmtFloat = anki.utils.fmtFloat
        if self.deck.isEmpty():
            return _("Please add some cards first.") + "<p/>"
        d = self.deck
        html = "<h1>" + _("Deck Statistics") + "</h1>"
        html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr()
        total = d.cardCount
        new = d.newCountAll()
        young = d.youngCardCount()
        old = d.matureCardCount()
        newP = new / float(total) * 100
        youngP = young / float(total) * 100
        oldP = old / float(total) * 100
        stats = d.getStats()
        (stats["new"], stats["newP"]) = (new, newP)
        (stats["old"], stats["oldP"]) = (old, oldP)
        (stats["young"], stats["youngP"]) = (young, youngP)
        html += _("Total number of cards:") + " <b>%d</b><br>" % total
        html += _(
            "Total number of facts:") + " <b>%d</b><br><br>" % d.factCount

        html += "<b>" + _("Card Maturity") + "</b><br>"
        html += _("Mature cards: <!--card count-->"
                  ) + " <b>%(old)d</b> (%(oldP)s)<br>" % {
                      'old': stats['old'],
                      'oldP': fmtPerc(stats['oldP'])
                  }
        html += _("Young cards: <!--card count-->"
                  ) + " <b>%(young)d</b> (%(youngP)s)<br>" % {
                      'young': stats['young'],
                      'youngP': fmtPerc(stats['youngP'])
                  }
        html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % {
            'new': stats['new'],
            'newP': fmtPerc(stats['newP'])
        }
        avgInt = self.getAverageInterval()
        if avgInt:
            html += _("Average interval: ") + (
                "<b>%s</b> ") % fmtFloat(avgInt) + _("days")
            html += "<br>"
        html += "<br>"
        inactive = d.inactiveCardCount()
        suspended = d.suspendedCardCount()
        active = total - inactive - suspended
        inactiveP = inactive / float(total) * 100
        suspendedP = suspended / float(total) * 100
        activeP = active / float(total) * 100
        html += "<b>" + _("Card State") + "</b><br>"
        html += _("Active cards:") + " <b>%(a)d</b> (%(b)s)<br>" % {
            'a': active,
            'b': fmtPerc(activeP)
        }
        html += _("Inactive cards:") + " <b>%(a)d</b> (%(b)s)<br>" % {
            'a': inactive,
            'b': fmtPerc(inactiveP)
        }
        html += _("Suspended cards:") + " <b>%(a)d</b> (%(b)s)<br><br>" % {
            'a': suspended,
            'b': fmtPerc(suspendedP)
        }
        html += "<b>" + _("Correct Answers") + "</b><br>"
        html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc(
            stats['gMatureYes%']) + ("</b> " +
                                     _("(%(partOf)d of %(totalSum)d)") % {
                                         'partOf': stats['gMatureYes'],
                                         'totalSum': stats['gMatureTotal']
                                     } + "<br>")
        html += _("Young cards: <!--correct answers-->") + " <b>" + fmtPerc(
            stats['gYoungYes%']) + ("</b> " +
                                    _("(%(partOf)d of %(totalSum)d)") % {
                                        'partOf': stats['gYoungYes'],
                                        'totalSum': stats['gYoungTotal']
                                    } + "<br>")
        html += _("First-seen cards:") + " <b>" + fmtPerc(
            stats['gNewYes%']) + ("</b> " +
                                  _("(%(partOf)d of %(totalSum)d)") % {
                                      'partOf': stats['gNewYes'],
                                      'totalSum': stats['gNewTotal']
                                  } + "<br><br>")

        # average pending time
        existing = d.cardCount - d.newCountToday

        def tr(a, b):
            return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b)

        def repsPerDay(reps, days):
            retval = ("<b>%d</b> " % reps) + ngettext("rep", "reps", reps)
            retval += ("/<b>%d</b> " % days) + ngettext("day", "days", days)
            return retval

        if existing and avgInt:
            html += "<b>" + _("Recent Work") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(
                _("In last week"),
                repsPerDay(self.getRepsDone(-7, 0),
                           self.getDaysReviewed(-7, 0)))
            html += tr(
                _("In last month"),
                repsPerDay(self.getRepsDone(-30, 0),
                           self.getDaysReviewed(-30, 0)))
            html += tr(
                _("In last 3 months"),
                repsPerDay(self.getRepsDone(-92, 0),
                           self.getDaysReviewed(-92, 0)))
            html += tr(
                _("In last 6 months"),
                repsPerDay(self.getRepsDone(-182, 0),
                           self.getDaysReviewed(-182, 0)))
            html += tr(
                _("In last year"),
                repsPerDay(self.getRepsDone(-365, 0),
                           self.getDaysReviewed(-365, 0)))
            html += tr(
                _("Deck life"),
                repsPerDay(self.getRepsDone(-13000, 0),
                           self.getDaysReviewed(-13000, 0)))
            html += "</table>"

            html += "<br><br><b>" + _("Average Daily Reviews") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(_("Deck life"), ("<b>%s</b> ") %
                       (fmtFloat(self.getSumInverseRoundInterval())) +
                       _("cards/day"))
            html += tr(_("In next week"), ("<b>%s</b> ") %
                       (fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day"))
            html += tr(_("In next month"), ("<b>%s</b> ") %
                       (fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day"))
            html += tr(_("In last week"), ("<b>%s</b> ") %
                       (fmtFloat(self.getPastWorkloadPeriod(7))) +
                       _("cards/day"))
            html += tr(_("In last month"), ("<b>%s</b> ") %
                       (fmtFloat(self.getPastWorkloadPeriod(30))) +
                       _("cards/day"))
            html += tr(_("In last 3 months"), ("<b>%s</b> ") %
                       (fmtFloat(self.getPastWorkloadPeriod(92))) +
                       _("cards/day"))
            html += tr(_("In last 6 months"), ("<b>%s</b> ") %
                       (fmtFloat(self.getPastWorkloadPeriod(182))) +
                       _("cards/day"))
            html += tr(_("In last year"), ("<b>%s</b> ") %
                       (fmtFloat(self.getPastWorkloadPeriod(365))) +
                       _("cards/day"))
            html += "</table>"

            html += "<br><br><b>" + _("Average Added") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(
                _("Deck life"),
                _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % {
                    'a': fmtFloat(self.newAverage()),
                    'b': fmtFloat(self.newAverage() * 30)
                })
            np = self.getNewPeriod(7)
            html += tr(
                _("In last week"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(7))
                }))
            np = self.getNewPeriod(30)
            html += tr(
                _("In last month"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(30))
                }))
            np = self.getNewPeriod(92)
            html += tr(
                _("In last 3 months"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(92))
                }))
            np = self.getNewPeriod(182)
            html += tr(
                _("In last 6 months"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(182))
                }))
            np = self.getNewPeriod(365)
            html += tr(
                _("In last year"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(365))
                }))
            html += "</table>"

            html += "<br><br><b>" + _("Average New Seen") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            np = self.getFirstPeriod(7)
            html += tr(
                _("In last week"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(7))
                }))
            np = self.getFirstPeriod(30)
            html += tr(
                _("In last month"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(30))
                }))
            np = self.getFirstPeriod(92)
            html += tr(
                _("In last 3 months"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(92))
                }))
            np = self.getFirstPeriod(182)
            html += tr(
                _("In last 6 months"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(182))
                }))
            np = self.getFirstPeriod(365)
            html += tr(
                _("In last year"),
                _("<b>%(a)d</b> (<b>%(b)s</b>/day)") %
                ({
                    'a': np,
                    'b': fmtFloat(np / float(365))
                }))
            html += "</table>"

            html += "<br><br><b>" + _("Card Ease") + "</b><br>"
            html += _("Lowest factor: %.2f") % d.s.scalar(
                "select min(factor) from cards") + "<br>"
            html += _("Average factor: %.2f") % d.s.scalar(
                "select avg(factor) from cards") + "<br>"
            html += _("Highest factor: %.2f") % d.s.scalar(
                "select max(factor) from cards") + "<br>"

            html = runFilter("deckStats", html)
        return html
Esempio n. 44
0
def _renderQA(self, data, qfmt=None, afmt=None):
    "Returns hash of id, question, answer."
    # data is [cid, nid, mid, did, ord, tags, flds]
    # unpack fields and create dict
    flist = splitFields(data[6])
    fields = {}
    model = self.models.get(data[2])
    for (name, (idx, conf)) in self.models.fieldMap(model).items():
        fields[name] = flist[idx]
    fields['Tags'] = data[5].strip()
    fields['Type'] = model['name']
    fields['Deck'] = self.decks.name(data[3])
    fields['Subdeck'] = fields['Deck'].split('::')[-1]
    if model['type'] == MODEL_STD:
        template = model['tmpls'][data[4]]
    else:
        template = model['tmpls'][0]
    fields['Card'] = template['name']
    fields['c%d' % (data[4]+1)] = "1"

    # render q & a
    d = dict(id=data[0])
    qfmt = qfmt or template['qfmt']
    afmt = afmt or template['afmt']
    for (type, format) in (("q", qfmt), ("a", afmt)):
        if type == "q":
            format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format)
            format = format.replace("<%cloze:", "<%%cq:%d:" % (
                data[4]+1))
        else:
            format = re.sub("{{(.*?)cloze:", r"{{\1ca-%d:" % (data[4]+1), format)
            format = format.replace("<%cloze:", "<%%ca:%d:" % (
                data[4]+1))
            fields['FrontSide'] = stripSounds(d['q'])
        fields = runFilter("mungeFields", fields, model, data, self)
        html = anki.template.render(format, fields)
        d[type] = html
    ############################
    tags = getTagMatches(d["a"])
    cid = data[0]
    if tags:
        if hasattr(self, "_spLast") and self._spLast == cid and tags == self._spTags:
            # preserve last replacements across card reloading
            # (required for: previewer, card templates editor)
            replacements = self._spRepl
        else: 
            seq_parser = Parser()
            replacements = seq_parser.parse(tags)
            self._spLast = cid
            self._spTags = tags
            self._spRepl = replacements
    else:
        if hasattr(self, "_spTags"):
            del self._spTags
        if hasattr(self, "_spRepl"):
            del self._spRepl
        if hasattr(self, "_spLast"):
            del self._spLast
    for type in ("q", "a"):
        html = d[type]
        if tags:
            format_str = getFormatString(html)
            html = formatTagString(format_str, replacements)
        d[type] = runFilter(
            "mungeQA", html, type, fields, model, data, self)
        # empty cloze?
        if type == 'q' and model['type'] == MODEL_CLOZE:
            if not self.models._availClozeOrds(model, data[6], False):
                d['q'] += ("<p>" + _(
            "Please edit this note and add some cloze deletions. (%s)") % (
            "<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help"))))
    #############################
    return d
Esempio n. 45
0
File: editor.py Progetto: qwcbw/anki
    def setupWeb(self) -> None:
        self.web = EditorWebView(self.widget, self)
        self.web.allowDrops = True
        self.web.set_bridge_command(self.onBridgeCmd, self)
        self.outerLayout.addWidget(self.web, 1)

        lefttopbtns: List[str] = [
            self._addButton(
                None,
                "fields",
                tr.editing_customize_fields(),
                f"{tr.editing_fields()}...",
                disables=False,
                rightside=False,
            ),
            self._addButton(
                None,
                "cards",
                tr.editing_customize_card_templates_ctrlandl(),
                f"{tr.editing_cards()}...",
                disables=False,
                rightside=False,
            ),
        ]

        gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)

        righttopbtns: List[str] = [
            self._addButton("text_bold",
                            "bold",
                            tr.editing_bold_text_ctrlandb(),
                            id="bold"),
            self._addButton(
                "text_italic",
                "italic",
                tr.editing_italic_text_ctrlandi(),
                id="italic",
            ),
            self._addButton(
                "text_under",
                "underline",
                tr.editing_underline_text_ctrlandu(),
                id="underline",
            ),
            self._addButton(
                "text_super",
                "super",
                tr.editing_superscript_ctrlandand(),
                id="superscript",
            ),
            self._addButton("text_sub",
                            "sub",
                            tr.editing_subscript_ctrland(),
                            id="subscript"),
            self._addButton("text_clear", "clear",
                            tr.editing_remove_formatting_ctrlandr()),
            self._addButton(
                None,
                "colour",
                tr.editing_set_foreground_colour_f7(),
                """
<span id="forecolor" class="topbut rounded" style="background: #000"></span>
""",
            ),
            self._addButton(
                None,
                "changeCol",
                tr.editing_change_colour_f8(),
                """
<span class="topbut rounded rainbow"></span>
""",
            ),
            self._addButton("text_cloze", "cloze",
                            tr.editing_cloze_deletion_ctrlandshiftandc()),
            self._addButton("paperclip", "attach",
                            tr.editing_attach_picturesaudiovideo_f3()),
            self._addButton("media-record", "record",
                            tr.editing_record_audio_f5()),
            self._addButton("more", "more"),
        ]

        gui_hooks.editor_did_init_buttons(righttopbtns, self)
        # legacy filter
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)

        topbuts = """
            <div id="topbutsleft" class="topbuts">
                %(leftbts)s
            </div>
            <div id="topbutsright" class="topbuts">
                %(rightbts)s
            </div>
        """ % dict(
            leftbts="".join(lefttopbtns),
            rightbts="".join(righttopbtns),
        )
        bgcol = self.mw.app.palette().window().color().name()  # type: ignore
        # then load page
        self.web.stdHtml(
            _html % (bgcol, topbuts, tr.editing_show_duplicates()),
            css=[
                "css/vendor/bootstrap.min.css",
                "css/editor.css",
            ],
            js=[
                "js/vendor/jquery.min.js",
                "js/vendor/bootstrap.bundle.min.js",
                "js/editor.js",
            ],
            context=self,
        )
        self.web.eval("preventButtonFocus();")
Esempio n. 46
0
    def setupWeb(self) -> None:
        self.web = EditorWebView(self.widget, self)
        self.web.allowDrops = True
        self.web.set_bridge_command(self.onBridgeCmd, self)
        self.outerLayout.addWidget(self.web, 1)

        lefttopbtns: List[str] = [
            self._addButton(
                None,
                "fields",
                tr(TR.EDITING_CUSTOMIZE_FIELDS),
                tr(TR.EDITING_FIELDS) + "...",
                disables=False,
                rightside=False,
            ),
            self._addButton(
                None,
                "cards",
                tr(TR.EDITING_CUSTOMIZE_CARD_TEMPLATES_CTRLANDL),
                tr(TR.EDITING_CARDS) + "...",
                disables=False,
                rightside=False,
            ),
        ]

        gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)

        righttopbtns: List[str] = [
            self._addButton("text_bold",
                            "bold",
                            tr(TR.EDITING_BOLD_TEXT_CTRLANDB),
                            id="bold"),
            self._addButton(
                "text_italic",
                "italic",
                tr(TR.EDITING_ITALIC_TEXT_CTRLANDI),
                id="italic",
            ),
            self._addButton(
                "text_under",
                "underline",
                tr(TR.EDITING_UNDERLINE_TEXT_CTRLANDU),
                id="underline",
            ),
            self._addButton(
                "text_super",
                "super",
                tr(TR.EDITING_SUPERSCRIPT_CTRLANDAND),
                id="superscript",
            ),
            self._addButton("text_sub",
                            "sub",
                            tr(TR.EDITING_SUBSCRIPT_CTRLAND),
                            id="subscript"),
            self._addButton("text_clear", "clear",
                            tr(TR.EDITING_REMOVE_FORMATTING_CTRLANDR)),
            self._addButton(
                None,
                "colour",
                tr(TR.EDITING_SET_FOREGROUND_COLOUR_F7),
                """
<div id="forecolor"
     style="display: inline-block; background: #000; border-radius: 5px;"
     class="topbut"
>""",
            ),
            self._addButton(
                None,
                "changeCol",
                tr(TR.EDITING_CHANGE_COLOUR_F8),
                """
<div style="display: inline-block; border-radius: 5px;"
     class="topbut rainbow"
>""",
            ),
            self._addButton("text_cloze", "cloze",
                            tr(TR.EDITING_CLOZE_DELETION_CTRLANDSHIFTANDC)),
            self._addButton("paperclip", "attach",
                            tr(TR.EDITING_ATTACH_PICTURESAUDIOVIDEO_F3)),
            self._addButton("media-record", "record",
                            tr(TR.EDITING_RECORD_AUDIO_F5)),
            self._addButton("more", "more"),
        ]

        gui_hooks.editor_did_init_buttons(righttopbtns, self)
        # legacy filter
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)

        topbuts = """
            <div id="topbutsleft" style="float:left;">
                %(leftbts)s
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(
            leftbts="".join(lefttopbtns),
            rightbts="".join(righttopbtns),
        )
        bgcol = self.mw.app.palette().window().color().name()  # type: ignore
        # then load page
        self.web.stdHtml(
            _html % (bgcol, bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)),
            css=["css/editor.css"],
            js=["js/vendor/jquery.min.js", "js/editor.js"],
            context=self,
        )
Esempio n. 47
0
    def report(self):
        "Return an HTML string with a report."
        fmtPerc = anki.utils.fmtPercentage
        fmtFloat = anki.utils.fmtFloat
        if self.deck.isEmpty():
            return _("Please add some cards first.") + "<p/>"
        d = self.deck
        html="<h1>" + _("Deck Statistics") + "</h1>"
        html += _("Deck created: <b>%s</b> ago<br>") % self.createdTimeStr()
        total = d.cardCount
        new = d.newCountAll()
        young = d.youngCardCount()
        old = d.matureCardCount()
        newP = new / float(total) * 100
        youngP = young / float(total) * 100
        oldP = old / float(total) * 100
        stats = d.getStats()
        (stats["new"], stats["newP"]) = (new, newP)
        (stats["old"], stats["oldP"]) = (old, oldP)
        (stats["young"], stats["youngP"]) = (young, youngP)
        html += _("Total number of cards:") + " <b>%d</b><br>" % total
        html += _("Total number of facts:") + " <b>%d</b><br><br>" % d.factCount

        html += "<b>" + _("Card Maturity") + "</b><br>"
        html += _("Mature cards: <!--card count-->") + " <b>%(old)d</b> (%(oldP)s)<br>" % {
                'old': stats['old'], 'oldP' : fmtPerc(stats['oldP'])}
        html += _("Young cards: <!--card count-->") + " <b>%(young)d</b> (%(youngP)s)<br>" % {
                'young': stats['young'], 'youngP' : fmtPerc(stats['youngP'])}
        html += _("Unseen cards:") + " <b>%(new)d</b> (%(newP)s)<br>" % {
                'new': stats['new'], 'newP' : fmtPerc(stats['newP'])}
        avgInt = self.getAverageInterval()
        if avgInt:
            html += _("Average interval: ") + ("<b>%s</b> ") % fmtFloat(avgInt) + _("days")
            html += "<br>"
        html += "<br>"
        inactive = d.inactiveCardCount()
        suspended = d.suspendedCardCount()
        active = total - inactive - suspended
        inactiveP = inactive / float(total) * 100
        suspendedP = suspended / float(total) * 100
        activeP = active / float(total) * 100
        html += "<b>" + _("Card State") + "</b><br>"
        html += _("Active cards:") + " <b>%(a)d</b> (%(b)s)<br>" % {
            'a': active, 'b' : fmtPerc(activeP)}
        html += _("Inactive cards:") + " <b>%(a)d</b> (%(b)s)<br>" % {
            'a': inactive, 'b' : fmtPerc(inactiveP)}
        html += _("Suspended cards:") + " <b>%(a)d</b> (%(b)s)<br><br>" % {
            'a': suspended, 'b' : fmtPerc(suspendedP)}
        html += "<b>" + _("Correct Answers") + "</b><br>"
        html += _("Mature cards: <!--correct answers-->") + " <b>" + fmtPerc(stats['gMatureYes%']) + (
                "</b> " + _("(%(partOf)d of %(totalSum)d)") % {
                'partOf' : stats['gMatureYes'],
                'totalSum' : stats['gMatureTotal'] } + "<br>")
        html += _("Young cards: <!--correct answers-->")  + " <b>" + fmtPerc(stats['gYoungYes%']) + (
                "</b> " + _("(%(partOf)d of %(totalSum)d)") % {
                'partOf' : stats['gYoungYes'],
                'totalSum' : stats['gYoungTotal'] } + "<br>")
        html += _("First-seen cards:") + " <b>" + fmtPerc(stats['gNewYes%']) + (
                "</b> " + _("(%(partOf)d of %(totalSum)d)") % {
                'partOf' : stats['gNewYes'],
                'totalSum' : stats['gNewTotal'] } + "<br><br>")

        # average pending time
        existing = d.cardCount - d.newCountToday
        def tr(a, b):
            return "<tr><td>%s</td><td align=right>%s</td></tr>" % (a, b)
        if existing and avgInt:
            html += "<b>" + _("Recent Work") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(_("In last week"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-7, 0),
                self.getDaysReviewed(-7, 0)))
            html += tr(_("In last month"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-30, 0),
                self.getDaysReviewed(-30, 0)))
            html += tr(_("In last 3 months"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-92, 0),
                self.getDaysReviewed(-92, 0)))
            html += tr(_("In last 6 months"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-182, 0),
                self.getDaysReviewed(-182, 0)))
            html += tr(_("In last year"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-365, 0),
                self.getDaysReviewed(-365, 0)))
            html += tr(_("Deck life"),
                       ("<b>%d</b> reps/<b>%d</b> days") % (
                self.getRepsDone(-13000, 0),
                self.getDaysReviewed(-13000, 0)))
            html += "</table>"

            html += "<br><br><b>" + _("Average Daily Reviews") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(_("Deck life"), ("<b>%s</b> ") % (
                fmtFloat(self.getSumInverseRoundInterval())) + _("cards/day"))
            html += tr(_("In next week"), ("<b>%s</b> ") % (
                fmtFloat(self.getWorkloadPeriod(7))) + _("cards/day"))
            html += tr(_("In next month"), ("<b>%s</b> ") % (
                fmtFloat(self.getWorkloadPeriod(30))) + _("cards/day"))
            html += tr(_("In last week"), ("<b>%s</b> ") % (
                fmtFloat(self.getPastWorkloadPeriod(7))) + _("cards/day"))
            html += tr(_("In last month"), ("<b>%s</b> ") % (
                fmtFloat(self.getPastWorkloadPeriod(30))) + _("cards/day"))
            html += tr(_("In last 3 months"), ("<b>%s</b> ") % (
                fmtFloat(self.getPastWorkloadPeriod(92))) + _("cards/day"))
            html += tr(_("In last 6 months"), ("<b>%s</b> ") % (
                fmtFloat(self.getPastWorkloadPeriod(182))) + _("cards/day"))
            html += tr(_("In last year"), ("<b>%s</b> ") % (
                fmtFloat(self.getPastWorkloadPeriod(365))) + _("cards/day"))
            html += "</table>"

            html += "<br><br><b>" + _("Average Added") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            html += tr(_("Deck life"), _("<b>%(a)s</b>/day, <b>%(b)s</b>/mon") % {
                'a': fmtFloat(self.newAverage()), 'b': fmtFloat(self.newAverage()*30)})
            np = self.getNewPeriod(7)
            html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(7))}))
            np = self.getNewPeriod(30)
            html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getNewPeriod(92)
            html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getNewPeriod(182)
            html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getNewPeriod(365)
            html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            html += "</table>"

            html += "<br><br><b>" + _("Average New Seen") + "</b>"
            if sys.platform.startswith("darwin"):
                html += "<table width=250>"
            else:
                html += "<table width=200>"
            np = self.getFirstPeriod(7)
            html += tr(_("In last week"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(7))}))
            np = self.getFirstPeriod(30)
            html += tr(_("In last month"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getFirstPeriod(92)
            html += tr(_("In last 3 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getFirstPeriod(182)
            html += tr(_("In last 6 months"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            np = self.getFirstPeriod(365)
            html += tr(_("In last year"), _("<b>%(a)d</b> (<b>%(b)s</b>/day)") % (
                {'a': np, 'b': fmtFloat(np / float(30))}))
            html += "</table>"

            html += "<br><br><b>" + _("Card Ease") + "</b><br>"
            html += _("Lowest factor: %.2f") % d.s.scalar(
                "select min(factor) from cards") + "<br>"
            html += _("Average factor: %.2f") % d.s.scalar(
                "select avg(factor) from cards") + "<br>"
            html += _("Highest factor: %.2f") % d.s.scalar(
                "select max(factor) from cards") + "<br>"

            html = runFilter("deckStats", html)
        return html
Esempio n. 48
0
    def setupWeb(self) -> None:
        self.web = EditorWebView(self.widget, self)
        self.web.allowDrops = True
        self.web.set_bridge_command(self.onBridgeCmd, self)
        self.outerLayout.addWidget(self.web, 1)

        lefttopbtns: List[str] = [
            self._addButton(
                None,
                "fields",
                _("Customize Fields"),
                _("Fields") + "...",
                disables=False,
                rightside=False,
            ),
            self._addButton(
                None,
                "cards",
                _("Customize Card Templates (Ctrl+L)"),
                _("Cards") + "...",
                disables=False,
                rightside=False,
            ),
        ]

        gui_hooks.editor_did_init_left_buttons(lefttopbtns, self)

        righttopbtns: List[str] = [
            self._addButton("text_bold",
                            "bold",
                            _("Bold text (Ctrl+B)"),
                            id="bold"),
            self._addButton("text_italic",
                            "italic",
                            _("Italic text (Ctrl+I)"),
                            id="italic"),
            self._addButton("text_under",
                            "underline",
                            _("Underline text (Ctrl+U)"),
                            id="underline"),
            self._addButton("text_super",
                            "super",
                            _("Superscript (Ctrl++)"),
                            id="superscript"),
            self._addButton("text_sub",
                            "sub",
                            _("Subscript (Ctrl+=)"),
                            id="subscript"),
            self._addButton("text_clear", "clear",
                            _("Remove formatting (Ctrl+R)")),
            self._addButton(
                None,
                "colour",
                _("Set foreground colour (F7)"),
                """
<div id="forecolor"
     style="display: inline-block; background: #000; border-radius: 5px;"
     class="topbut"
>""",
            ),
            self._addButton(
                None,
                "changeCol",
                _("Change colour (F8)"),
                """
<div style="display: inline-block; border-radius: 5px;"
     class="topbut rainbow"
>""",
            ),
            self._addButton("text_cloze", "cloze",
                            _("Cloze deletion (Ctrl+Shift+C)")),
            self._addButton("paperclip", "attach",
                            _("Attach pictures/audio/video (F3)")),
            self._addButton("media-record", "record", _("Record audio (F5)")),
            self._addButton("more", "more"),
        ]

        gui_hooks.editor_did_init_buttons(righttopbtns, self)
        # legacy filter
        righttopbtns = runFilter("setupEditorButtons", righttopbtns, self)

        topbuts = """
            <div id="topbutsleft" style="float:left;">
                %(leftbts)s
            </div>
            <div id="topbutsright" style="float:right;">
                %(rightbts)s
            </div>
        """ % dict(
            leftbts="".join(lefttopbtns),
            rightbts="".join(righttopbtns),
        )
        bgcol = self.mw.app.palette().window().color().name()  # type: ignore
        # then load page
        self.web.stdHtml(
            _html % (bgcol, bgcol, topbuts, _("Show Duplicates")),
            css=["css/editor.css"],
            js=["js/vendor/jquery.js", "js/editor.js"],
            context=self,
        )
Esempio n. 49
0
 def modSchema(self, check=True):
     "Mark schema modified. Call this first so user can abort if necessary."
     if not self.schemaChanged():
         if check and not runFilter("modSchema", True):
             raise AnkiError("abortSchemaMod")
     self.scm = intTime(1000)
Esempio n. 50
0
    def _renderQA(self,
                  data: QAData,
                  qfmt: Optional[str] = None,
                  afmt: Optional[str] = None) -> Dict[str, Union[str, int]]:
        # extract info from data
        split_fields = splitFields(data[6])
        card_ord = data[4]
        model = self.models.get(data[2])
        if model["type"] == MODEL_STD:
            template = model["tmpls"][data[4]]
        else:
            template = model["tmpls"][0]
        flag = data[7]
        deck_id = data[3]
        card_id = data[0]
        tags = data[5]
        qfmt = qfmt or template["qfmt"]
        afmt = afmt or template["afmt"]

        # create map of field names -> field content
        fields: Dict[str, str] = {}
        for (name, (idx, conf)) in list(self.models.fieldMap(model).items()):
            fields[name] = split_fields[idx]

        # add special fields
        fields["Tags"] = tags.strip()
        fields["Type"] = model["name"]
        fields["Deck"] = self.decks.name(deck_id)
        fields["Subdeck"] = fields["Deck"].split("::")[-1]
        fields["Card"] = template["name"]
        fields["CardFlag"] = self._flagNameFromCardFlags(flag)
        fields["c%d" % (card_ord + 1)] = "1"

        # legacy hook
        fields = runFilter("mungeFields", fields, model, data, self)

        ctx = TemplateRenderContext(self, data, fields)

        # render fields. if any custom filters are encountered,
        # the field_filter hook will be called.
        try:
            qtext, atext = render_card(self, qfmt, afmt, ctx)
        except anki.rsbackend.BackendException as e:
            errmsg = _("Card template has a problem:") + f"<br>{e}"
            qtext = errmsg
            atext = errmsg

        # avoid showing the user a confusing blank card if they've
        # forgotten to add a cloze deletion
        if model["type"] == MODEL_CLOZE:
            if not self.models._availClozeOrds(model, data[6], False):
                qtext = (qtext + "<p>" + _(
                    "Please edit this note and add some cloze deletions. (%s)")
                         % ("<a href=%s#cloze>%s</a>" %
                            (HELP_SITE, _("help"))))

        # allow add-ons to modify the generated result
        (qtext, atext) = hooks.card_did_render((qtext, atext), ctx)

        # legacy hook
        qtext = runFilter("mungeQA", qtext, "q", fields, model, data, self)
        atext = runFilter("mungeQA", atext, "a", fields, model, data, self)

        return dict(q=qtext, a=atext, id=card_id)
Esempio n. 51
0
 def remEmptyCards(self, ids):
     if not ids:
         return
     if runFilter("remEmptyCards", len(ids), True):
         self.remCards(ids)