diff --git a/ankiqt/ui/view.py b/ankiqt/ui/view.py index 40e8a2759..d6b4019b3 100644 --- a/ankiqt/ui/view.py +++ b/ankiqt/ui/view.py @@ -10,6 +10,7 @@ from anki.utils import stripHTML from anki.hooks import runHook, runFilter from anki.media import stripMedia import types, time, re, os, urllib, sys, difflib +import unicodedata as ucd from ankiqt import ui from ankiqt.ui.utils import mungeQA, getBase from anki.utils import fmtTimeSpan @@ -141,13 +142,8 @@ class View(object): and self.main.config['autoplaySounds']): playFromText(q) - def correct(self, a, b): - if b == "": - return ""; - - ret = ""; - s = difflib.SequenceMatcher(None, b, a) - + def calculateOkBadStyle(self): + "Precalculates styles for correct and incorrect part of answer" sz = 20 fn = u"Arial" for fm in self.main.currentCard.fact.model.fieldModels: @@ -156,24 +152,61 @@ class View(object): fn = fm.quizFontFamily or fn break st = "background: %s; color: #000; font-size: %dpx; font-family: %s;" - ok = st % (passedCharColour, sz, fn) - bad = st % (failedCharColour, sz, fn) + self.styleOk = st % (passedCharColour, sz, fn) + self.styleBad = st % (failedCharColour, sz, fn) + + def ok(self, a): + "returns given sring in style correct (green)" + if len(a) == 0: + return "" + return "%s" % (self.styleOk, a) + + def bad(self, a): + "returns given sring in style incorrect (red)" + if len(a) == 0: + return "" + return "%s" % (self.styleBad, a) + + def head(self, a): + return a[:len(a) - 1] + + def tail(self, a): + return a[len(a) - 1:] + + def applyStyle(self, testChar, correct, wrong): + "Calculates answer fragment depending on testChar's unicode category" + ZERO_SIZE = 'Mn' + if ucd.category(testChar) == ZERO_SIZE: + return self.ok(self.head(correct)) + self.bad(self.tail(correct) + wrong) + return self.ok(correct) + self.bad(wrong) + + def correct(self, a, b): + "Diff-corrects the typed-in answer." + if b == "": + return ""; + + self.calculateOkBadStyle() + + ret = "" + lastEqual = "" + s = difflib.SequenceMatcher(None, b, a) for tag, i1, i2, j1, j2 in s.get_opcodes(): if tag == "equal": - ret += ("%s" % (ok, b[i1:i2])) + lastEqual = b[i1:i2] elif tag == "replace": - ret += ("%s" - % (bad, b[i1:i2] + (" " * ((j2 - j1) - (i2 - i1))))) + ret += self.applyStyle(b[i1], lastEqual, + b[i1:i2] + ("-" * ((j2 - j1) - (i2 - i1)))) + lastEqual = "" elif tag == "delete": - p = re.compile(r"^\s*$") - if p.match(b[i1:i2]): - ret += ("%s" % (ok, b[i1:i2])) - else: - ret += ("%s" % (bad, b[i1:i2])) + ret += self.applyStyle(b[i1], lastEqual, b[i1:i2]) + lastEqual = "" elif tag == "insert": - ret += ("%s" % (bad, " " * (j2 - j1))) - return ret + dashNum = (j2 - j1) if ucd.category(a[i1]) != 'Mn' else ((j2 - j1) - 1) + ret += self.applyStyle(a[i1], lastEqual, "-" * dashNum) + lastEqual = "" + + return ret + self.ok(lastEqual) def drawAnswer(self): "Show the answer." @@ -188,7 +221,8 @@ class View(object): cor = "" if cor: given = unicode(self.main.typeAnswerField.text()) - res = self.correct(given, cor) + res = self.correct(ucd.normalize('NFC', cor), + ucd.normalize('NFC', given)) a = res + "
" + a self.write(self.center('' + self.mungeQA(self.main.deck, a)))