From f0525a76fb41938ccc71c79ca79562d2a6cd3fc4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 13 Dec 2013 20:24:39 +0900 Subject: [PATCH 001/109] if we renamed any files to nfc, need to rerun check --- anki/__init__.py | 2 +- anki/media.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/anki/__init__.py b/anki/__init__.py index b54587f97..530807a95 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.19" # build scripts grep this line, so preserve formatting +version="2.0.20" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] diff --git a/anki/media.py b/anki/media.py index 9826ae6f1..a96135315 100644 --- a/anki/media.py +++ b/anki/media.py @@ -217,6 +217,7 @@ class MediaManager(object): files = os.listdir(mdir) else: files = local + renamedFiles = False for file in files: if not local: if not os.path.isfile(file): @@ -236,14 +237,20 @@ class MediaManager(object): # delete if we already have the NFC form, otherwise rename if os.path.exists(nfcFile): os.unlink(file) + renamedFiles = True else: os.rename(file, nfcFile) + renamedFiles = True file = nfcFile # compare if nfcFile not in allRefs: unused.append(file) else: allRefs.discard(nfcFile) + # if we renamed any files to nfc format, we must rerun the check + # to make sure the renamed files are not marked as unused + if renamedFiles: + return self.check(local=local) nohave = [x for x in allRefs if not x.startswith("_")] return (nohave, unused, invalid) From 33984b1f605bcb3cc08a28868bbcdf24d3f1d7c7 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 17 Dec 2013 02:02:56 +0900 Subject: [PATCH 002/109] remove pyaudio try/accept, as we catch it in sound.py --- thirdparty/pyaudio.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/thirdparty/pyaudio.py b/thirdparty/pyaudio.py index af1147a29..f491f2baf 100644 --- a/thirdparty/pyaudio.py +++ b/thirdparty/pyaudio.py @@ -92,16 +92,7 @@ __author__ = "Hubert Pham" __version__ = "0.2.4" __docformat__ = "restructuredtext en" -import sys - -# attempt to import PortAudio -try: - import _portaudio as pa -except ImportError: - print "Please build and install the PortAudio Python " +\ - "bindings first." - sys.exit(-1) - +import _portaudio as pa # Try to use Python 2.4's built in `set' try: From cc98ef3763795e985aa7153a0d6ddf1999d2235c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 19 Dec 2013 07:41:29 +0900 Subject: [PATCH 003/109] tweak imports to work around tooling incorrectly finding module unused fixes 'quit' from the profile menu, etc --- aqt/main.py | 2 +- aqt/qt.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/aqt/main.py b/aqt/main.py index 4e89481f6..608cec7ee 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -4,7 +4,7 @@ import re import signal -import zipfile +import zipfile from send2trash import send2trash from aqt.qt import * diff --git a/aqt/qt.py b/aqt/qt.py index 168c75cc4..a46aec0b9 100644 --- a/aqt/qt.py +++ b/aqt/qt.py @@ -2,9 +2,13 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # imports are all in this file to make moving to pyside easier in the future +# fixme: make sure not to optimize imports on this file + +import sip +import os -import sip, os from anki.utils import isWin, isMac + sip.setapi('QString', 2) sip.setapi('QVariant', 2) sip.setapi('QUrl', 2) @@ -18,14 +22,16 @@ from PyQt4.QtGui import * from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings from PyQt4.QtNetwork import QLocalServer, QLocalSocket + def debug(): from PyQt4.QtCore import pyqtRemoveInputHook from pdb import set_trace pyqtRemoveInputHook() set_trace() +import sys, traceback + if os.environ.get("DEBUG"): - import sys, traceback def info(type, value, tb): from PyQt4.QtCore import pyqtRemoveInputHook for line in traceback.format_exception(type, value, tb): From c5df294a287f296f55c0cf646433b1c4e7dd2a33 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 13 Jan 2014 19:07:34 +0900 Subject: [PATCH 004/109] fix "local variable 'txt' referenced before assignment" --- aqt/browser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aqt/browser.py b/aqt/browser.py index 23025aa65..5bbd33ece 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -96,10 +96,14 @@ class DataModel(QAbstractTableModel): return elif role == Qt.DisplayRole and section < len(self.activeCols): type = self.columnType(section) + txt = None for stype, name in self.browser.columns: if type == stype: txt = name break + # handle case where extension has set an invalid column type + if not txt: + txt = self.browser.columns[0][1] return txt else: return From 8769a6daf09224de272260205ad65e200e296361 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 14:12:49 +0900 Subject: [PATCH 005/109] catch err 10054 --- aqt/sync.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aqt/sync.py b/aqt/sync.py index 2a38b158b..84ba7ce9f 100644 --- a/aqt/sync.py +++ b/aqt/sync.py @@ -170,6 +170,8 @@ AnkiWeb is too busy at the moment. Please try again in a few minutes.""") elif "10061" in err or "10013" in err or "10053" in err: return _( "Antivirus or firewall software is preventing Anki from connecting to the internet.") + elif "10054" in err: + return _("Connection timed out. Either your internet connection is experiencing problems, or you have a very large file in your media folder.") elif "Unable to find the server" in err: return _( "Server not found. Either your connection is down, or antivirus/firewall " From 44b83d9bd80a1966842698ea2546216d98db30df Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 14:18:42 +0900 Subject: [PATCH 006/109] switch over to per-day sibling burying --- anki/collection.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/anki/collection.py b/anki/collection.py index 66735af5d..1991ff895 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -73,9 +73,8 @@ class _Collection(object): self.crt = int(time.mktime(d.timetuple())) self.sched = Scheduler(self) if not self.conf.get("newBury", False): - mod = self.db.mod - self.sched.unburyCards() - self.db.mod = mod + self.conf['newBury'] = True + self.setMod() def name(self): n = os.path.splitext(os.path.basename(self.path))[0] @@ -148,10 +147,6 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", def close(self, save=True): "Disconnect from DB." if self.db: - if not self.conf.get("newBury", False): - mod = self.db.mod - self.sched.unburyCards() - self.db.mod = mod if save: self.save() else: From 4b217664aae620fa0abd8e0fbd283d59d42389f4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 14:40:45 +0900 Subject: [PATCH 007/109] confirm interface language selection on first startup --- aqt/profiles.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/aqt/profiles.py b/aqt/profiles.py index 0b5588447..ccf90fa47 100644 --- a/aqt/profiles.py +++ b/aqt/profiles.py @@ -284,7 +284,15 @@ please see: def _onLangSelected(self): f = self.langForm - code = langs[f.lang.currentRow()][1] + obj = langs[f.lang.currentRow()] + code = obj[1] + name = obj[0] + en = "Are you sure you wish to display Anki's interface in %s?" + r = QMessageBox.question( + None, "Anki", en%name, QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + if r != QMessageBox.Yes: + return self._setDefaultLang() self.meta['defaultLang'] = code sql = "update profiles set data = ? where name = ?" self.db.execute(sql, cPickle.dumps(self.meta), "_global") From 76ed611bc018ef7198e2711a88152ac8b9346a8a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 14:59:16 +0900 Subject: [PATCH 008/109] workaround for google images+safari --- aqt/editor.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/aqt/editor.py b/aqt/editor.py index e347d6d7e..5dd6d1dab 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -1117,13 +1117,18 @@ class EditorWebView(AnkiWebView): url = mime.urls()[0].toString() # chrome likes to give us the URL twice with a \n url = url.splitlines()[0] - mime = QMimeData() + newmime = QMimeData() link = self.editor.urlToLink(url) if link: - mime.setHtml(link) + newmime.setHtml(link) + elif mime.hasImage(): + # if we couldn't convert the url to a link and there's an + # image on the clipboard (such as copy&paste from + # google images in safari), use that instead + return self._processImage(mime) else: - mime.setText(url) - return mime + newmime.setText(url) + return newmime # if the user has used 'copy link location' in the browser, the clipboard # will contain the URL as text, and no URLs or HTML. the URL will already From 513b0ca8d7d33f29b7a2db2186661fc18432d6dc Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 15:06:22 +0900 Subject: [PATCH 009/109] ignore filtered cards when determining new card pos boundaries --- aqt/browser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aqt/browser.py b/aqt/browser.py index 5bbd33ece..7622fe64b 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -1197,7 +1197,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, frm = aqt.forms.reposition.Ui_Dialog() frm.setupUi(d) (pmin, pmax) = self.col.db.first( - "select min(due), max(due) from cards where type=0") + "select min(due), max(due) from cards where type=0 and odid=0") + pmin = pmin or 0 + pmax = pmax or 0 txt = _("Queue top: %d") % pmin txt += "\n" + _("Queue bottom: %d") % pmax frm.label.setText(txt) From 96eeacbf693c7a8d27f1de461afb24c579cdf89b Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 15:12:45 +0900 Subject: [PATCH 010/109] increase filtered deck size limit to 99999 --- anki/sched.py | 2 +- designer/dyndconf.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/anki/sched.py b/anki/sched.py index 7e5bcc7ca..d49507080 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -997,7 +997,7 @@ due = odue, odue = 0, odid = 0, usn = ?, mod = ? where %s""" % lim, elif o == DYN_DUE: t = "c.due" elif o == DYN_DUEPRIORITY: - t = "(case when queue=2 and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 10000+due end)" % ( + t = "(case when queue=2 and due <= %d then (ivl / cast(%d-due+0.001 as real)) else 100000+due end)" % ( self.today, self.today) else: # if we don't understand the term, default to due order diff --git a/designer/dyndconf.ui b/designer/dyndconf.ui index 41d9bf25e..39ca36f76 100644 --- a/designer/dyndconf.ui +++ b/designer/dyndconf.ui @@ -46,7 +46,7 @@ 1 - 9999 + 99999 From 227c5cbc9426dc6eee938212da6d85d42c7f5e6c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 15:24:42 +0900 Subject: [PATCH 011/109] rename corrupt collection (#999) if we don't do this, the user can never get back into the profile in order to overwrite it with a backup --- aqt/main.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/aqt/main.py b/aqt/main.py index 608cec7ee..5fe6bf3f5 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -264,15 +264,19 @@ To import into a password protected profile, please open the profile before atte def loadCollection(self): self.hideSchemaMsg = True + cpath = self.pm.collectionPath() try: - self.col = Collection(self.pm.collectionPath(), log=True) + self.col = Collection(cpath, log=True) except anki.db.Error: - # move back to profile manager + # warn user showWarning("""\ Your collection is corrupt. Please see the manual for \ how to restore from a backup.""") - self.unloadProfile() - raise + # move it out of the way so the profile can be used again + newpath = cpath+str(intTime()) + os.rename(cpath, newpath) + # then close + sys.exit(1) except Exception, e: # the custom exception handler won't catch this if we immediately # unload, so we have to manually handle it From 14a23214083fcf36b2cd55efcc151194a45018d0 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 14 Jan 2014 15:45:12 +0900 Subject: [PATCH 012/109] increase custom study size limit as well --- anki/consts.py | 2 ++ aqt/customstudy.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/anki/consts.py b/anki/consts.py index 1b219941b..0eabc0419 100644 --- a/anki/consts.py +++ b/anki/consts.py @@ -38,6 +38,8 @@ DYN_DUE = 6 DYN_REVADDED = 7 DYN_DUEPRIORITY = 8 +DYN_MAX_SIZE = 99999 + # model types MODEL_STD = 0 MODEL_CLOZE = 1 diff --git a/aqt/customstudy.py b/aqt/customstudy.py index 8da5b7a1a..a33f728fa 100644 --- a/aqt/customstudy.py +++ b/aqt/customstudy.py @@ -41,7 +41,7 @@ class CustomStudy(QDialog): def onRadioChange(self, idx): f = self.form; sp = f.spin - smin = 1; smax = 9999; sval = 1 + smin = 1; smax = DYN_MAX_SIZE; sval = 1 post = _("cards") tit = "" spShow = True @@ -127,15 +127,15 @@ class CustomStudy(QDialog): # and then set various options if i == RADIO_FORGOT: dyn['delays'] = [1] - dyn['terms'][0] = ['rated:%d:1' % spin, 9999, DYN_RANDOM] + dyn['terms'][0] = ['rated:%d:1' % spin, DYN_MAX_SIZE, DYN_RANDOM] dyn['resched'] = False elif i == RADIO_AHEAD: dyn['delays'] = None - dyn['terms'][0] = ['prop:due<=%d' % spin, 9999, DYN_DUE] + dyn['terms'][0] = ['prop:due<=%d' % spin, DYN_MAX_SIZE, DYN_DUE] dyn['resched'] = True elif i == RADIO_PREVIEW: dyn['delays'] = None - dyn['terms'][0] = ['is:new added:%s'%spin, 9999, DYN_OLDEST] + dyn['terms'][0] = ['is:new added:%s'%spin, DYN_MAX_SIZE, DYN_OLDEST] dyn['resched'] = False elif i == RADIO_CRAM: dyn['delays'] = None From 92eb4e71d46b0b578b11632ab51b97a8c8186578 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 17 Jan 2014 14:30:40 +0900 Subject: [PATCH 013/109] ignore duplicates in cloze+type answer --- aqt/reviewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 19fa2f55a..eb74ecb13 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -434,7 +434,7 @@ Please run Tools>Empty Cards""") return txt matches = [noHint(txt) for txt in matches] if len(matches) > 1: - txt = ", ".join(matches) + txt = ", ".join(list(set(matches))) else: txt = matches[0] return txt From 97a2b819974bc78d7982eebc06e94f4019047f0e Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 22 Jan 2014 02:57:44 +0900 Subject: [PATCH 014/109] fix 'add reverse' template for non-english languages --- anki/stdmodels.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/anki/stdmodels.py b/anki/stdmodels.py index ef59c718d..955afb990 100644 --- a/anki/stdmodels.py +++ b/anki/stdmodels.py @@ -48,10 +48,11 @@ def addForwardOptionalReverse(col): mm = col.models m = addBasicModel(col) m['name'] = _("Basic (optional reversed card)") - fm = mm.newField(_("Add Reverse")) + av = _("Add Reverse") + fm = mm.newField(av) mm.addField(m, fm) t = mm.newTemplate(_("Card 2")) - t['qfmt'] = "{{#Add Reverse}}{{"+_("Back")+"}}{{/Add Reverse}}" + t['qfmt'] = "{{#%s}}{{%s}}{{/%s}}" % (av, _("Back"), av) t['afmt'] = "{{FrontSide}}\n\n
\n\n"+"{{"+_("Front")+"}}" mm.addTemplate(m, t) return m From 5f9afe52961bc139a13315a8629a208363823259 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 22 Jan 2014 17:13:33 +0900 Subject: [PATCH 015/109] bump version --- anki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/__init__.py b/anki/__init__.py index 530807a95..fb180dbee 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.20" # build scripts grep this line, so preserve formatting +version="2.0.21" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] From 1e1e1f6f959e13a19f3fb176857039aafebfb24d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 24 Jan 2014 03:10:04 +0900 Subject: [PATCH 016/109] catch broken pipe error --- aqt/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/sync.py b/aqt/sync.py index 84ba7ce9f..fcadf86f7 100644 --- a/aqt/sync.py +++ b/aqt/sync.py @@ -170,7 +170,7 @@ AnkiWeb is too busy at the moment. Please try again in a few minutes.""") elif "10061" in err or "10013" in err or "10053" in err: return _( "Antivirus or firewall software is preventing Anki from connecting to the internet.") - elif "10054" in err: + elif "10054" in err or "Broken pipe" in err: return _("Connection timed out. Either your internet connection is experiencing problems, or you have a very large file in your media folder.") elif "Unable to find the server" in err: return _( From 6c6be1144b3e8d96e5a1e4c65c655d276a80017e Mon Sep 17 00:00:00 2001 From: Jussi Maatta Date: Sat, 25 Jan 2014 23:56:22 +0200 Subject: [PATCH 017/109] Remove unused function from SuperMemo XML importer. --- anki/importing/supermemo_xml.py | 58 --------------------------------- 1 file changed, 58 deletions(-) diff --git a/anki/importing/supermemo_xml.py b/anki/importing/supermemo_xml.py index 60c7fa0a4..94d30b80d 100644 --- a/anki/importing/supermemo_xml.py +++ b/anki/importing/supermemo_xml.py @@ -142,64 +142,6 @@ class SupermemoXmlImporter(NoteImporter): return unicode(btflsoup(s,convertEntities=btflsoup.HTML_ENTITIES )) - - def _unescape(self,s,initilize): - """Note: This method is not used, BeautifulSoup does better job. - """ - - if self._unescape_trtable == None: - self._unescape_trtable = ( - ('€',u'€'), (' ',u' '), ('!',u'!'), ('"',u'"'), ('#',u'#'), ('$',u'$'), ('%',u'%'), ('&',u'&'), (''',u"'"), - ('(',u'('), (')',u')'), ('*',u'*'), ('+',u'+'), (',',u','), ('-',u'-'), ('.',u'.'), ('/',u'/'), ('0',u'0'), - ('1',u'1'), ('2',u'2'), ('3',u'3'), ('4',u'4'), ('5',u'5'), ('6',u'6'), ('7',u'7'), ('8',u'8'), ('9',u'9'), - (':',u':'), (';',u';'), ('<',u'<'), ('=',u'='), ('>',u'>'), ('?',u'?'), ('@',u'@'), ('A',u'A'), ('B',u'B'), - ('C',u'C'), ('D',u'D'), ('E',u'E'), ('F',u'F'), ('G',u'G'), ('H',u'H'), ('I',u'I'), ('J',u'J'), ('K',u'K'), - ('L',u'L'), ('M',u'M'), ('N',u'N'), ('O',u'O'), ('P',u'P'), ('Q',u'Q'), ('R',u'R'), ('S',u'S'), ('T',u'T'), - ('U',u'U'), ('V',u'V'), ('W',u'W'), ('X',u'X'), ('Y',u'Y'), ('Z',u'Z'), ('[',u'['), ('\',u'\\'), (']',u']'), - ('^',u'^'), ('_',u'_'), ('`',u'`'), ('a',u'a'), ('b',u'b'), ('c',u'c'), ('d',u'd'), ('e',u'e'), ('f',u'f'), - ('g',u'g'), ('h',u'h'), ('i',u'i'), ('j',u'j'), ('k',u'k'), ('l',u'l'), ('m',u'm'), ('n',u'n'), - ('o',u'o'), ('p',u'p'), ('q',u'q'), ('r',u'r'), ('s',u's'), ('t',u't'), ('u',u'u'), ('v',u'v'), - ('w',u'w'), ('x',u'x'), ('y',u'y'), ('z',u'z'), ('{',u'{'), ('|',u'|'), ('}',u'}'), ('~',u'~'), - (' ',u' '), ('¡',u'¡'), ('¢',u'¢'), ('£',u'£'), ('¤',u'¤'), ('¥',u'¥'), ('¦',u'¦'), ('§',u'§'), - ('¨',u'¨'), ('©',u'©'), ('ª',u'ª'), ('«',u'«'), ('¬',u'¬'), ('­',u'­'), ('®',u'®'), ('¯',u'¯'), - ('°',u'°'), ('±',u'±'), ('²',u'²'), ('³',u'³'), ('´',u'´'), ('µ',u'µ'), ('¶',u'¶'), ('·',u'·'), - ('¸',u'¸'), ('¹',u'¹'), ('º',u'º'), ('»',u'»'), ('¼',u'¼'), ('½',u'½'), ('¾',u'¾'), ('¿',u'¿'), - ('À',u'À'), ('Á',u'Á'), ('Â',u'Â'), ('Ã',u'Ã'), ('Ä',u'Ä'), ('Å',u'Å'), ('Å',u'Å'), ('Æ',u'Æ'), - ('Ç',u'Ç'), ('È',u'È'), ('É',u'É'), ('Ê',u'Ê'), ('Ë',u'Ë'), ('Ì',u'Ì'), ('Í',u'Í'), ('Î',u'Î'), - ('Ï',u'Ï'), ('Ð',u'Ð'), ('Ñ',u'Ñ'), ('Ò',u'Ò'), ('Ó',u'Ó'), ('Ô',u'Ô'), ('Õ',u'Õ'), ('Ö',u'Ö'), - ('×',u'×'), ('Ø',u'Ø'), ('Ù',u'Ù'), ('Ú',u'Ú'), ('Û',u'Û'), ('Ü',u'Ü'), ('Ý',u'Ý'), ('Þ',u'Þ'), - ('ß',u'ß'), ('à',u'à'), ('á',u'á'), ('â',u'â'), ('ã',u'ã'), ('ä',u'ä'), ('å',u'å'), ('æ',u'æ'), - ('ç',u'ç'), ('è',u'è'), ('é',u'é'), ('ê',u'ê'), ('ë',u'ë'), ('ì',u'ì'), ('í',u'í'), ('í',u'í'), - ('î',u'î'), ('ï',u'ï'), ('ð',u'ð'), ('ñ',u'ñ'), ('ò',u'ò'), ('ó',u'ó'), ('ô',u'ô'), ('õ',u'õ'), - ('ö',u'ö'), ('÷',u'÷'), ('ø',u'ø'), ('ù',u'ù'), ('ú',u'ú'), ('û',u'û'), ('ü',u'ü'), ('ý',u'ý'), - ('þ',u'þ'), ('ÿ',u'ÿ'), ('Ā',u'Ā'), ('ā',u'ā'), ('Ă',u'Ă'), ('ă',u'ă'), ('Ą',u'Ą'), ('ą',u'ą'), - ('Ć',u'Ć'), ('ć',u'ć'), ('Ĉ',u'Ĉ'), ('ĉ',u'ĉ'), ('Ċ',u'Ċ'), ('ċ',u'ċ'), ('Č',u'Č'), ('č',u'č'), - ('Ď',u'Ď'), ('ď',u'ď'), ('Đ',u'Đ'), ('đ',u'đ'), ('Ē',u'Ē'), ('ē',u'ē'), ('Ĕ',u'Ĕ'), ('ĕ',u'ĕ'), - ('Ė',u'Ė'), ('ė',u'ė'), ('Ę',u'Ę'), ('ę',u'ę'), ('Ě',u'Ě'), ('ě',u'ě'), ('Ĝ',u'Ĝ'), ('ĝ',u'ĝ'), - ('Ğ',u'Ğ'), ('ğ',u'ğ'), ('Ġ',u'Ġ'), ('ġ',u'ġ'), ('Ģ',u'Ģ'), ('ģ',u'ģ'), ('Ĥ',u'Ĥ'), ('ĥ',u'ĥ'), - ('Ħ',u'Ħ'), ('ħ',u'ħ'), ('Ĩ',u'Ĩ'), ('ĩ',u'ĩ'), ('Ī',u'Ī'), ('ī',u'ī'), ('Ĭ',u'Ĭ'), ('ĭ',u'ĭ'), - ('Į',u'Į'), ('į',u'į'), ('İ',u'İ'), ('ı',u'ı'), ('IJ',u'IJ'), ('ij',u'ij'), ('Ĵ',u'Ĵ'), ('ĵ',u'ĵ'), - ('Ķ',u'Ķ'), ('ķ',u'ķ'), ('ĸ',u'ĸ'), ('Ĺ',u'Ĺ'), ('ĺ',u'ĺ'), ('Ļ',u'Ļ'), ('ļ',u'ļ'), ('Ľ',u'Ľ'), - ('ľ',u'ľ'), ('Ŀ',u'Ŀ'), ('ŀ',u'ŀ'), ('Ł',u'Ł'), ('ł',u'ł'), ('Ń',u'Ń'), ('ń',u'ń'), ('Ņ',u'Ņ'), - ('ņ',u'ņ'), ('Ň',u'Ň'), ('ň',u'ň'), ('ʼn',u'ʼn'), ('Ŋ',u'Ŋ'), ('ŋ',u'ŋ'), ('Ō',u'Ō'), ('ō',u'ō'), - ('Ŏ',u'Ŏ'), ('ŏ',u'ŏ'), ('Ő',u'Ő'), ('ő',u'ő'), ('Œ',u'Œ'), ('œ',u'œ'), ('Ŕ',u'Ŕ'), ('ŕ',u'ŕ'), - ('Ŗ',u'Ŗ'), ('ŗ',u'ŗ'), ('Ř',u'Ř'), ('ř',u'ř'), ('Ś',u'Ś'), ('ś',u'ś'), ('Ŝ',u'Ŝ'), ('ŝ',u'ŝ'), - ('Ş',u'Ş'), ('ş',u'ş'), ('Š',u'Š'), ('š',u'š'), ('Ţ',u'Ţ'), ('ţ',u'ţ'), ('Ť',u'Ť'), ('ť',u'ť'), - ('Ŧ',u'Ŧ'), ('ŧ',u'ŧ'), ('Ũ',u'Ũ'), ('ũ',u'ũ'), ('Ū',u'Ū'), ('ū',u'ū'), ('Ŭ',u'Ŭ'), ('ŭ',u'ŭ'), - ('Ů',u'Ů'), ('ů',u'ů'), ('Ű',u'Ű'), ('ű',u'ű'), ('Ų',u'Ų'), ('ų',u'ų'), ('Ŵ',u'Ŵ'), ('ŵ',u'ŵ'), - ('Ŷ',u'Ŷ'), ('ŷ',u'ŷ'), ('Ÿ',u'Ÿ'), ('Ź',u'Ź'), ('ź',u'ź'), ('Ż',u'Ż'), ('ż',u'ż'), ('Ž',u'Ž'), - ('ž',u'ž'), ('ſ',u'ſ'), ('Ŕ',u'Ŕ'), ('ŕ',u'ŕ'), ('Ŗ',u'Ŗ'), ('ŗ',u'ŗ'), ('Ř',u'Ř'), ('ř',u'ř'), - ('Ś',u'Ś'), ('ś',u'ś'), ('Ŝ',u'Ŝ'), ('ŝ',u'ŝ'), ('Ş',u'Ş'), ('ş',u'ş'), ('Š',u'Š'), ('š',u'š'), - ('Ţ',u'Ţ'), ('ţ',u'ţ'), ('Ť',u'Ť'), ('Ɂ',u'ť'), ('Ŧ',u'Ŧ'), ('ŧ',u'ŧ'), ('Ũ',u'Ũ'), ('ũ',u'ũ'), - ('Ū',u'Ū'), ('ū',u'ū'), ('Ŭ',u'Ŭ'), ('ŭ',u'ŭ'), ('Ů',u'Ů'), ('ů',u'ů'), ('Ű',u'Ű'), ('ű',u'ű'), - ('Ų',u'Ų'), ('ų',u'ų'), ('Ŵ',u'Ŵ'), ('ŵ',u'ŵ'), ('Ŷ',u'Ŷ'), ('ŷ',u'ŷ'), ('Ÿ',u'Ÿ'), ('Ź',u'Ź'), - ('ź',u'ź'), ('Ż',u'Ż'), ('ż',u'ż'), ('Ž',u'Ž'), ('ž',u'ž'), ('ſ',u'ſ'), - ) - - - #m = re.match() - #s = s.replace(code[0], code[1]) - ## DEFAULT IMPORTER METHODS def foreignNotes(self): From cdcd5eb6510fa482d238c3367d5ce909aebf5a19 Mon Sep 17 00:00:00 2001 From: Jussi Maatta Date: Sat, 25 Jan 2014 23:57:02 +0200 Subject: [PATCH 018/109] Fix double newlines in items imported from SuperMemo. When importing SuperMemo XML files, indicate to the BeautifulSoup XML parser that the
tag is self-closing. This prevents it from adding a matching
for every
tag. These extraneous tags resulted in extra newlines in items imported from SuperMemo. While we're at it, fix the same bug for a couple of other self-closing HTML tags. --- anki/importing/supermemo_xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/importing/supermemo_xml.py b/anki/importing/supermemo_xml.py index 94d30b80d..9a00438ce 100644 --- a/anki/importing/supermemo_xml.py +++ b/anki/importing/supermemo_xml.py @@ -140,7 +140,7 @@ class SupermemoXmlImporter(NoteImporter): #s = re.sub(u'>',u'>',s) #s = re.sub(u'<',u'<',s) - return unicode(btflsoup(s,convertEntities=btflsoup.HTML_ENTITIES )) + return unicode(btflsoup(s, selfClosingTags=['br','hr','img','wbr'], convertEntities=btflsoup.HTML_ENTITIES)) ## DEFAULT IMPORTER METHODS From 656698da8387b1c24f751e8ac4795605981ff636 Mon Sep 17 00:00:00 2001 From: Jussi Maatta Date: Sat, 25 Jan 2014 23:57:57 +0200 Subject: [PATCH 019/109] Add A-factor to E-factor conversion to SuperMemo import. The SuperMemo XML importer assumed that the A-factors used by SuperMemo map 1-1 with E-factors (which correspond to Ease in Anki). This resulted in too large E-factors for imported items. This change adds an A-factor to E-factor conversion to the importer. --- anki/importing/supermemo_xml.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/anki/importing/supermemo_xml.py b/anki/importing/supermemo_xml.py index 9a00438ce..ee1b3da7f 100644 --- a/anki/importing/supermemo_xml.py +++ b/anki/importing/supermemo_xml.py @@ -142,6 +142,28 @@ class SupermemoXmlImporter(NoteImporter): return unicode(btflsoup(s, selfClosingTags=['br','hr','img','wbr'], convertEntities=btflsoup.HTML_ENTITIES)) + def _afactor2efactor(self, af): + # Adapted from + + # Ranges for A-factors and E-factors + af_min = 1.2 + af_max = 6.9 + ef_min = 1.3 + ef_max = 3.3 + + # Sanity checks for the A-factor + if af < af_min: + af = af_min + elif af > af_max: + af = af_max + + # Scale af to the range 0..1 + af_scaled = (af - af_min) / (af_max - af_min) + # Rescale to the interval ef_min..ef_max + ef = ef_min + af_scaled * (ef_max - ef_min) + + return ef + ## DEFAULT IMPORTER METHODS def foreignNotes(self): @@ -191,7 +213,7 @@ class SupermemoXmlImporter(NoteImporter): nextDue = tLastrep + (float(item.Interval) * 86400.0) remDays = int((nextDue - time.time())/86400) card.due = self.col.sched.today+remDays - card.factor = int(float(item.AFactor.replace(',','.'))*1000) + card.factor = int(self._afactor2efactor(float(item.AFactor.replace(',','.')))*1000) note.cards[0] = card # categories & tags From 3b20de173f8f9313234899224f9b71c057039930 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 30 Jan 2014 00:34:42 +0900 Subject: [PATCH 020/109] preserve order in multi cloze type answer; bump version --- anki/__init__.py | 2 +- aqt/reviewer.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/anki/__init__.py b/anki/__init__.py index fb180dbee..fdbd9596d 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.21" # build scripts grep this line, so preserve formatting +version="2.0.22" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] diff --git a/aqt/reviewer.py b/aqt/reviewer.py index eb74ecb13..79216e00b 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -434,7 +434,14 @@ Please run Tools>Empty Cards""") return txt matches = [noHint(txt) for txt in matches] if len(matches) > 1: - txt = ", ".join(list(set(matches))) + arr = [] + seen = {} + for m in matches: + if m in seen: + continue + seen[m] = 1 + arr.append(m) + txt = ", ".join(arr) else: txt = matches[0] return txt From b97f913ba1ebdf80493724f4b7ab91ada9932c80 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 3 Feb 2014 01:51:51 +0900 Subject: [PATCH 021/109] catch another invalidTempFolder msg --- aqt/importing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aqt/importing.py b/aqt/importing.py index e41bbf1b5..fa07a07e8 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -17,7 +17,6 @@ import aqt.forms import aqt.modelchooser import aqt.deckchooser - class ChangeMap(QDialog): def __init__(self, mw, model, current): QDialog.__init__(self, mw, Qt.Window) @@ -338,6 +337,8 @@ with a different browser.""") msg = _("""\ Invalid file. Please restore from backup.""") showWarning(msg) + elif "invalidTempFolder" in err: + showWarning(mw.errorHandler.tempFolderMsg()) elif "readonly" in err: showWarning(_("""\ Unable to import from a read-only file.""")) From 640bcfa7d54d39500d2c08f1fb78c4531d0dfc38 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Feb 2014 05:33:58 +0900 Subject: [PATCH 022/109] fix 1000 cap in overview screen --- aqt/overview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/overview.py b/aqt/overview.py index 64f631874..2da65b55b 100644 --- a/aqt/overview.py +++ b/aqt/overview.py @@ -123,7 +123,7 @@ to their original deck.""") counts = list(self.mw.col.sched.counts()) finished = not sum(counts) for n in range(len(counts)): - if counts[n] == 1000: + if counts[n] >= 1000: counts[n] = "1000+" but = self.mw.button if finished: From 2ec399a0098b1457794b027a99c8d0ee5c84cf41 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Feb 2014 06:50:41 +0900 Subject: [PATCH 023/109] catch ssl error --- aqt/sync.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aqt/sync.py b/aqt/sync.py index fcadf86f7..c54000654 100644 --- a/aqt/sync.py +++ b/aqt/sync.py @@ -180,6 +180,8 @@ AnkiWeb is too busy at the moment. Please try again in a few minutes.""") return _("Proxy authentication required.") elif "code: 413" in err: return _("Your collection or a media file is too large to sync.") + elif "EOF occurred in violation of protocol" in err: + return _("Error establishing a secure connection. This is usually caused by filtering software, or problems with your ISP.") return err def _getUserPass(self): From 3c36ddfb82291c448abe3047986c652eef89ba93 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 10 Feb 2014 20:47:49 +0900 Subject: [PATCH 024/109] don't allow files named . or .. --- anki/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/anki/utils.py b/anki/utils.py index 06be1de67..8b5dc5a7c 100644 --- a/anki/utils.py +++ b/anki/utils.py @@ -360,6 +360,8 @@ def invalidFilename(str, dirsep=True): return "/" elif (dirsep or not isWin) and "\\" in str: return "\\" + elif str.strip().startswith("."): + return "." def platDesc(): # we may get an interrupted system call, so try this in a loop From 349c95d17a2196ec6f04dfc807335d8403968d64 Mon Sep 17 00:00:00 2001 From: Bryan Richter Date: Wed, 12 Feb 2014 23:21:02 -0800 Subject: [PATCH 025/109] Fix supermemo import test Broken by 656698d, "Add A Factor to E Factor conversion..." --- tests/test_importing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_importing.py b/tests/test_importing.py index 940d3642f..b644907b5 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -297,7 +297,7 @@ def test_supermemo_xml_01_unicode(): assert i.total == 1 cid = deck.db.scalar("select id from cards") c = deck.getCard(cid) - assert c.factor == 5701 + assert c.factor == int(i._afactor2efactor(float(5.701))*1000) assert c.reps == 7 deck.close() From 827bb844182bab8c5ed98c7bd189a132447c7fb7 Mon Sep 17 00:00:00 2001 From: Bryan Richter Date: Wed, 12 Feb 2014 23:33:44 -0800 Subject: [PATCH 026/109] Re-use an empty deck to save on creation time. --- tests/shared.py | 19 ++++++++++++++++++- tests/test_sync.py | 6 +++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index d15502d6d..0f19f548a 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -9,7 +9,24 @@ def assertException(exception, func): found = True assert found -def getEmptyDeck(**kwargs): + +# Creating new decks is expensive. Just do it once, and then spin off +# copies from the master. +def getEmptyDeck(): + if len(getEmptyDeck.master) == 0: + (fd, nam) = tempfile.mkstemp(suffix=".anki2") + os.unlink(nam) + col = aopen(nam) + col.db.close() + getEmptyDeck.master = nam + (fd, nam) = tempfile.mkstemp(suffix=".anki2") + shutil.copy(getEmptyDeck.master, nam) + return aopen(nam) + +getEmptyDeck.master = "" + +# Fallback for when the DB needs options passed in. +def getEmptyDeckWith(**kwargs): (fd, nam) = tempfile.mkstemp(suffix=".anki2") os.unlink(nam) return aopen(nam, **kwargs) diff --git a/tests/test_sync.py b/tests/test_sync.py index 3f2efaad9..5d26769c7 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -5,7 +5,7 @@ import nose, os, shutil, time from anki import Collection as aopen, Collection from anki.utils import intTime from anki.sync import Syncer, LocalServer -from tests.shared import getEmptyDeck +from tests.shared import getEmptyDeck, getEmptyDeckWith # Local tests ########################################################################## @@ -26,7 +26,7 @@ def setup_basic(): # answer it deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4) # repeat for deck2 - deck2 = getEmptyDeck(server=True) + deck2 = getEmptyDeckWith(server=True) f = deck2.newNote() f['Front'] = u"bar"; f['Back'] = u"bar"; f.tags = [u"bar"] deck2.addNote(f) @@ -325,7 +325,7 @@ def _test_speed(): deck1.tags.tags[tx] = -1 deck1._usn = -1 deck1.save() - deck2 = getEmptyDeck(server=True) + deck2 = getEmptyDeckWith(server=True) deck1.scm = deck2.scm = 0 server = LocalServer(deck2) client = Syncer(deck1, server) From 04d3901491efca8a04016caea78fc88b436f3d05 Mon Sep 17 00:00:00 2001 From: Bryan Richter Date: Wed, 12 Feb 2014 23:46:38 -0800 Subject: [PATCH 027/109] Test against the value itself --- tests/test_importing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_importing.py b/tests/test_importing.py index b644907b5..322b615f2 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -297,7 +297,8 @@ def test_supermemo_xml_01_unicode(): assert i.total == 1 cid = deck.db.scalar("select id from cards") c = deck.getCard(cid) - assert c.factor == int(i._afactor2efactor(float(5.701))*1000) + # Applies A Factor-to-E Factor conversion + assert c.factor == 2879 assert c.reps == 7 deck.close() From 770c6e9c4a8084cf98bd37334a077930200de900 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Sat, 15 Feb 2014 13:32:14 +1100 Subject: [PATCH 028/109] Add expand/collapse support for decks in browser tree. The state is preserved in a new deck configuration key named 'browserCollapsed'. --- anki/decks.py | 6 ++++++ aqt/browser.py | 22 +++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/anki/decks.py b/anki/decks.py index aa170b670..180eb4200 100644 --- a/anki/decks.py +++ b/anki/decks.py @@ -197,6 +197,12 @@ class DeckManager(object): deck['collapsed'] = not deck['collapsed'] self.save(deck) + def collapseBrowser(self, did): + deck = self.get(did) + collapsed = deck.get('browserCollapsed', False) + deck['browserCollapsed'] = not collapsed + self.save(deck) + def count(self): return len(self.decks) diff --git a/aqt/browser.py b/aqt/browser.py index 7622fe64b..2b5f20a11 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -731,9 +731,10 @@ by clicking on one on the left.""")) ###################################################################### class CallbackItem(QTreeWidgetItem): - def __init__(self, root, name, onclick): + def __init__(self, root, name, onclick, oncollapse=None): QTreeWidgetItem.__init__(self, root, [name]) self.onclick = onclick + self.oncollapse = oncollapse def setupTree(self): self.connect( @@ -743,6 +744,12 @@ by clicking on one on the left.""")) p.setColor(QPalette.Base, QColor("#d6dde0")) self.form.tree.setPalette(p) self.buildTree() + self.connect( + self.form.tree, SIGNAL("itemExpanded(QTreeWidgetItem*)"), + lambda item: self.onTreeCollapse(item)) + self.connect( + self.form.tree, SIGNAL("itemCollapsed(QTreeWidgetItem*)"), + lambda item: self.onTreeCollapse(item)) def buildTree(self): self.form.tree.clear() @@ -751,14 +758,16 @@ by clicking on one on the left.""")) self._decksTree(root) self._modelTree(root) self._userTagTree(root) - self.form.tree.expandAll() - self.form.tree.setItemsExpandable(False) self.form.tree.setIndentation(15) def onTreeClick(self, item, col): if getattr(item, 'onclick', None): item.onclick() + def onTreeCollapse(self, item): + if getattr(item, 'oncollapse', None): + item.oncollapse() + def setFilter(self, *args): if len(args) == 1: txt = args[0] @@ -821,10 +830,13 @@ by clicking on one on the left.""")) def fillGroups(root, grps, head=""): for g in grps: item = self.CallbackItem( - root, g[0], lambda g=g: self.setFilter( - "deck", head+g[0])) + root, g[0], + lambda g=g: self.setFilter("deck", head+g[0]), + lambda g=g: self.mw.col.decks.collapseBrowser(g[1])) item.setIcon(0, QIcon(":/icons/deck16.png")) newhead = head + g[0]+"::" + collapsed = self.mw.col.decks.get(g[1]).get('browserCollapsed', False) + item.setExpanded(not collapsed) fillGroups(item, g[5], newhead) fillGroups(root, grps) From a12e6f6b653b97dc3e68d64d5ed9a6003d592487 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Sat, 15 Feb 2014 17:26:03 +1100 Subject: [PATCH 029/109] Issue #998: Allow copy/paste and context menu in all card windows. --- aqt/browser.py | 3 +-- aqt/clayout.py | 4 ++-- aqt/main.py | 2 ++ aqt/webview.py | 17 +++++++++++++---- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index 7622fe64b..a267f2841 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -986,8 +986,7 @@ where id in %s""" % ids2str(sf)) c(self._previewWindow, SIGNAL("finished(int)"), self._onPreviewFinished) vbox = QVBoxLayout() vbox.setMargin(0) - self._previewWeb = AnkiWebView() - self._previewWeb.setFocusPolicy(Qt.NoFocus) + self._previewWeb = AnkiWebView(True) vbox.addWidget(self._previewWeb) bbox = QDialogButtonBox() self._previewPrev = bbox.addButton("<", QDialogButtonBox.ActionRole) diff --git a/aqt/clayout.py b/aqt/clayout.py index 2d3b858e1..aa5f423e6 100644 --- a/aqt/clayout.py +++ b/aqt/clayout.py @@ -120,9 +120,9 @@ class CardLayout(QDialog): self.model, joinFields(self.note.fields))) for g in pform.groupBox, pform.groupBox_2: g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1)) - pform.frontWeb = AnkiWebView() + pform.frontWeb = AnkiWebView(True) pform.frontPrevBox.addWidget(pform.frontWeb) - pform.backWeb = AnkiWebView() + pform.backWeb = AnkiWebView(True) pform.backPrevBox.addWidget(pform.backWeb) for wig in pform.frontWeb, pform.backWeb: wig.page().setLinkDelegationPolicy( diff --git a/aqt/main.py b/aqt/main.py index 5fe6bf3f5..273f2b066 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -409,10 +409,12 @@ the manual for information on how to restore from an automatic backup.")) def _reviewState(self, oldState): self.reviewer.show() + self.web.setCardViewer(True) def _reviewCleanup(self, newState): if newState != "resetRequired" and newState != "review": self.reviewer.cleanup() + self.web.setCardViewer(False) def noteChanged(self, nid): "Called when a card or note is edited (but not deleted)." diff --git a/aqt/webview.py b/aqt/webview.py index 278cfec15..3dd3ad990 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -40,7 +40,7 @@ class AnkiWebPage(QWebPage): class AnkiWebView(QWebView): - def __init__(self): + def __init__(self, isCardViewer=False): QWebView.__init__(self) self.setRenderHints( QPainter.TextAntialiasing | @@ -59,6 +59,7 @@ class AnkiWebView(QWebView): self.allowDrops = False # reset each time new html is set; used to detect if still in same state self.key = None + self.setCardViewer(isCardViewer) def keyPressEvent(self, evt): if evt.matches(QKeySequence.Copy): @@ -78,9 +79,7 @@ class AnkiWebView(QWebView): QWebView.keyReleaseEvent(self, evt) def contextMenuEvent(self, evt): - # lazy: only run in reviewer - import aqt - if aqt.mw.state != "review": + if not self.isCardViewer: return m = QMenu(self) a = m.addAction(_("Copy")) @@ -131,6 +130,16 @@ button { def setBridge(self, bridge): self._bridge.setBridge(bridge) + def setCardViewer(self, isCardViewer=False): + """Set flag to denote if this WebView should follow rules specific to + card display (e.g., allow context menu, copy/paste)""" + + self.isCardViewer = isCardViewer + if self.isCardViewer: + self.setFocusPolicy(Qt.WheelFocus) + else: + self.setFocusPolicy(Qt.NoFocus) + def eval(self, js): self.page().mainFrame().evaluateJavaScript(js) From 61ab1f5dfd57464d30e2f0442d599a201848af2f Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Sat, 15 Feb 2014 23:04:59 +1100 Subject: [PATCH 030/109] Remove marked/leech tags when exporting without sched data (#986) --- anki/exporting.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/anki/exporting.py b/anki/exporting.py index cc12e3d5a..47288a1ce 100644 --- a/anki/exporting.py +++ b/anki/exporting.py @@ -134,8 +134,14 @@ class AnkiExporter(Exporter): data) # notes strnids = ids2str(nids.keys()) - notedata = self.src.db.all("select * from notes where id in "+ - strnids) + notedata = [] + for row in self.src.db.all( + "select * from notes where id in "+strnids): + # remove system tags if not exporting scheduling info + if not self.includeSched: + row = list(row) + row[5] = self.removeSystemTags(row[5]) + notedata.append(row) self.dst.db.executemany( "insert into notes values (?,?,?,?,?,?,?,?,?,?,?)", notedata) @@ -206,6 +212,11 @@ class AnkiExporter(Exporter): # overwrite to apply customizations to the deck before it's closed, # such as update the deck description pass + + def removeSystemTags(self, tags): + tags = ' '.join(tag for tag in tags.split() + if (tag.lower() != "marked" and tag.lower() != "leech")) + return tags # Packaged Anki decks ###################################################################### From 81d88908a5ab8d95857de570b155cd1c889e02b7 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Mon, 17 Feb 2014 13:04:36 +1100 Subject: [PATCH 031/109] Remember scroll position in deck browser (#977). --- aqt/deckbrowser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 04357d969..74ad1b0a3 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -17,6 +17,7 @@ class DeckBrowser(object): self.mw = mw self.web = mw.web self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb) + self.scrollPos = QPoint(0, 0) def show(self): clearAudioQueue() @@ -65,6 +66,7 @@ class DeckBrowser(object): key = unicode(evt.text()) def _selDeck(self, did): + self.scrollPos = self.web.page().mainFrame().scrollPosition() self.mw.col.decks.select(did) self.mw.onOverview() @@ -152,7 +154,7 @@ body { margin: 1em; -webkit-user-select: none; } if self.web.key == "deckBrowser": return self.web.page().mainFrame().scrollPosition() else: - return QPoint(0,0) + return self.scrollPos def _renderStats(self): cards, thetime = self.mw.col.db.first(""" From 96cc48652894faff5d32c5610c01a2b6b9567dc4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 18 Feb 2014 15:13:59 +0900 Subject: [PATCH 032/109] editor needs to accept focus; rename cardViewer to canFocus --- aqt/editor.py | 2 +- aqt/main.py | 4 ++-- aqt/webview.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aqt/editor.py b/aqt/editor.py index 5dd6d1dab..ce76ebe3f 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -988,7 +988,7 @@ to a cloze type first, via Edit>Change Note Type.""")) class EditorWebView(AnkiWebView): def __init__(self, parent, editor): - AnkiWebView.__init__(self) + AnkiWebView.__init__(self, canFocus=True) self.editor = editor self.strip = self.editor.mw.pm.profile['stripHTML'] diff --git a/aqt/main.py b/aqt/main.py index 273f2b066..cdcc41a2f 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -409,12 +409,12 @@ the manual for information on how to restore from an automatic backup.")) def _reviewState(self, oldState): self.reviewer.show() - self.web.setCardViewer(True) + self.web.setCanFocus(True) def _reviewCleanup(self, newState): if newState != "resetRequired" and newState != "review": self.reviewer.cleanup() - self.web.setCardViewer(False) + self.web.setCanFocus(False) def noteChanged(self, nid): "Called when a card or note is edited (but not deleted)." diff --git a/aqt/webview.py b/aqt/webview.py index 3dd3ad990..3be755315 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -40,7 +40,7 @@ class AnkiWebPage(QWebPage): class AnkiWebView(QWebView): - def __init__(self, isCardViewer=False): + def __init__(self, canFocus=False): QWebView.__init__(self) self.setRenderHints( QPainter.TextAntialiasing | @@ -59,7 +59,7 @@ class AnkiWebView(QWebView): self.allowDrops = False # reset each time new html is set; used to detect if still in same state self.key = None - self.setCardViewer(isCardViewer) + self.setCanFocus(canFocus) def keyPressEvent(self, evt): if evt.matches(QKeySequence.Copy): @@ -130,7 +130,7 @@ button { def setBridge(self, bridge): self._bridge.setBridge(bridge) - def setCardViewer(self, isCardViewer=False): + def setCanFocus(self, isCardViewer=False): """Set flag to denote if this WebView should follow rules specific to card display (e.g., allow context menu, copy/paste)""" From 7e3597fb8e1ed087261b9e93643890ae8e1b3587 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Sat, 15 Feb 2014 13:59:58 +0000 Subject: [PATCH 033/109] Adding support for chained modifiers. One can now write e.g. {{cloze:text:Field}} or {{text:cloze:Field}}, it's order independent. --- anki/collection.py | 6 ++--- anki/models.py | 2 +- anki/template/template.py | 57 ++++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/anki/collection.py b/anki/collection.py index 1991ff895..6b0882e00 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -510,13 +510,11 @@ where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)): afmt = afmt or template['afmt'] for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": - format = format.replace("{{cloze:", "{{cq:%d:" % ( - data[4]+1)) + format = re.sub("{{(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%cq:%d:" % ( data[4]+1)) else: - format = format.replace("{{cloze:", "{{ca:%d:" % ( - data[4]+1)) + 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']) diff --git a/anki/models.py b/anki/models.py index 8ad5b158d..599a10c92 100644 --- a/anki/models.py +++ b/anki/models.py @@ -569,7 +569,7 @@ select id from notes where mid = ?)""" % " ".join(map), sflds = splitFields(flds) map = self.fieldMap(m) ords = set() - matches = re.findall("{{cloze:(.+?)}}", m['tmpls'][0]['qfmt']) + matches = re.findall("{{[^}]*?cloze:(?:.*?:)*(.+?)}}", m['tmpls'][0]['qfmt']) matches += re.findall("<%cloze:(.+?)%>", m['tmpls'][0]['qfmt']) for fname in matches: if fname not in map: diff --git a/anki/template/template.py b/anki/template/template.py index bdddb2422..7a091d91d 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -158,40 +158,41 @@ class Template(object): return txt # field modifiers - parts = tag_name.split(':',2) + parts = tag_name.split(':') 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 + else: + mods, tag = parts[:-1], parts[-1] #py3k has *mods, 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]) + + #Since 'text:' and oths mods can affect html on which Anki relies to + #process Clozes and Types, we need to make sure cloze/type are always + #treated after all the other mods, regardless of how they're specified + #in the template, so that {{cloze:text: == {{text:cloze: + mods.sort(key=lambda s: s.startswith("cq-") or s.startswith("ca-") or 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 + txt = "[[%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: - 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 + # 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 def clozeText(self, txt, ord, type): reg = clozeReg From 37753b1ffd31208a0a8720de8512c6eebd006237 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Tue, 18 Feb 2014 17:11:31 +0000 Subject: [PATCH 034/109] Adding tests for chained modifiers --- tests/test_models.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index 2d4d3d493..22c5e088d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -107,6 +107,29 @@ def test_templates(): mm.addTemplate(m, t) assert not d.models.remTemplate(m, m['tmpls'][0]) +def test_cloze_ordinals(): + d = getEmptyDeck() + d.models.setCurrent(d.models.byName("Cloze")) + m = d.models.current(); mm = d.models + + #We replace the default Cloze template + t = mm.newTemplate("ChainedCloze") + t['qfmt'] = "{{cloze:text:Text}}" + t['afmt'] = "{{text:cloze:Text}}" #independent of the order of mods + mm.addTemplate(m, t) + mm.save(m) + d.models.remTemplate(m, m['tmpls'][0]) + + f = d.newNote() + f['Text'] = u'{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}' + d.addNote(f) + assert d.cardCount() == 2 + (c, c2) = f.cards() + # first card should have first ord + assert c.ord == 0 + assert c2.ord == 1 + + def test_text(): d = getEmptyDeck() m = d.models.current() @@ -163,6 +186,29 @@ def test_cloze(): f.flush() assert len(f.cards()) == 2 +def test_chained_mods(): + d = getEmptyDeck() + d.models.setCurrent(d.models.byName("Cloze")) + m = d.models.current(); mm = d.models + + #We replace the default Cloze template + t = mm.newTemplate("ChainedCloze") + t['qfmt'] = "{{cloze:text:Text}}" + t['afmt'] = "{{text:cloze:Text}}" #independent of the order of mods + mm.addTemplate(m, t) + mm.save(m) + d.models.remTemplate(m, m['tmpls'][0]) + + f = d.newNote() + q1 = 'phrase' + a1 = 'sentence' + q2 = 'en chaine' + a2 = 'chained' + f['Text'] = "This {{c1::%s::%s}} demonstrates {{c1::%s::%s}} clozes." % (q1,a1,q2,a2) + assert d.addNote(f) == 1 + assert "This [sentence] demonstrates [chained] clozes." in f.cards()[0].q() + assert "This phrase demonstrates en chaine clozes." in f.cards()[0].a() + def test_modelChange(): deck = getEmptyDeck() basic = deck.models.byName("Basic") From fb1a255358f13db94fb7f168d23825ff8ec71709 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Wed, 19 Feb 2014 07:32:04 +0000 Subject: [PATCH 035/109] Chained modifiers are processed from innermost to outermost (i.e. rtl). Except cloze/type. --- anki/template/template.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/anki/template/template.py b/anki/template/template.py index 7a091d91d..828c042ca 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -167,10 +167,11 @@ class Template(object): txt = get_or_attr(context, tag) - #Since 'text:' and oths mods can affect html on which Anki relies to + #Since 'text:' and other mods can affect html on which Anki relies to #process Clozes and Types, we need to make sure cloze/type are always #treated after all the other mods, regardless of how they're specified #in the template, so that {{cloze:text: == {{text:cloze: + mods.reverse() mods.sort(key=lambda s: s.startswith("cq-") or s.startswith("ca-") or s=="type") for mod in mods: From 8b1cca04128912c40afa5bba8fd32606874a11d9 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 19 Feb 2014 18:01:29 +0900 Subject: [PATCH 036/109] remove debug statements from test --- tests/test_sched.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_sched.py b/tests/test_sched.py index 3073a02c2..644876a4c 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -176,10 +176,8 @@ def test_learn(): c.queue = 1 c.odue = 321 c.flush() - print "----begin" d.sched.removeLrn() c.load() - print c.__dict__ assert c.queue == 2 assert c.due == 321 From 777a3b8ec37af881afe186ed37b1907ae858e042 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Wed, 19 Feb 2014 10:09:48 +0000 Subject: [PATCH 037/109] Fixes behaviour for {{type:cloze:Field}} --- anki/collection.py | 2 +- anki/template/template.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/anki/collection.py b/anki/collection.py index 6b0882e00..b33f8d191 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -510,7 +510,7 @@ where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)): afmt = afmt or template['afmt'] for (type, format) in (("q", qfmt), ("a", afmt)): if type == "q": - format = re.sub("{{(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format) + format = re.sub("{{(?!type:)(.*?)cloze:", r"{{\1cq-%d:" % (data[4]+1), format) format = format.replace("<%cloze:", "<%%cq:%d:" % ( data[4]+1)) else: diff --git a/anki/template/template.py b/anki/template/template.py index 828c042ca..5edab4879 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -168,11 +168,14 @@ class Template(object): txt = get_or_attr(context, tag) #Since 'text:' and other mods can affect html on which Anki relies to - #process Clozes and Types, we need to make sure cloze/type are always + #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: s.startswith("cq-") or s.startswith("ca-") or s=="type") + mods.sort(key=lambda s: not s=="type") + mods.sort(key=lambda s: s.startswith("cq-") or s.startswith("ca-")) for mod in mods: # built-in modifiers @@ -182,7 +185,7 @@ class Template(object): elif mod == 'type': # type answer field; convert it to [[type:...]] for the gui code # to process - txt = "[[%s]]" % tag_name + return "[[%s]]" % tag_name elif mod.startswith('cq-') or mod.startswith('ca-'): # cloze deletion mod, extra = mod.split("-") From 4c65c594ddc066ea015195c81ed2d2cca40ef4aa Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 19 Feb 2014 21:21:11 +0900 Subject: [PATCH 038/109] don't force cloze to start This allows {{kana:cloze:Text}} to work. May allow users to shoot themselves it the foot - will see how it goes in the wild and reconsider if necessary --- anki/template/template.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/anki/template/template.py b/anki/template/template.py index 5edab4879..d2f055c61 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -175,8 +175,7 @@ class Template(object): #pre-defined mods) can be present and those are treated separately mods.reverse() mods.sort(key=lambda s: not s=="type") - mods.sort(key=lambda s: s.startswith("cq-") or s.startswith("ca-")) - + for mod in mods: # built-in modifiers if mod == 'text': From 8b443e80ef3760b805d0713d64474e22f9d588fa Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Thu, 20 Feb 2014 16:51:08 +1100 Subject: [PATCH 039/109] Remove leech tag on undo if it was added during that review (#973). --- anki/collection.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/anki/collection.py b/anki/collection.py index 1991ff895..b156f880a 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -608,13 +608,19 @@ where c.nid == f.id if self._undo[0] == 1: old = self._undo[2] self.clearUndo() - self._undo = [1, _("Review"), old + [copy.copy(card)]] + wasLeech = card.note().hasTag("leech") or False + self._undo = [1, _("Review"), old + [copy.copy(card)], wasLeech] def _undoReview(self): data = self._undo[2] + wasLeech = self._undo[3] c = data.pop() if not data: self.clearUndo() + # remove leech tag if it didn't have it before + if not wasLeech and c.note().hasTag("leech"): + c.note().delTag("leech") + c.note().flush() # write old data c.flush() # and delete revlog entry From 068a63270dd8f102ee226f8ae5578b83ea6ea922 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Sat, 22 Feb 2014 21:30:32 +1100 Subject: [PATCH 040/109] Changed location of import button (#1018). Also updated help link in same window to point to a valid anchor. --- aqt/importing.py | 11 ++++++----- designer/importing.ui | 19 ++----------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/aqt/importing.py b/aqt/importing.py index fa07a07e8..ef99958c8 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -79,6 +79,9 @@ class ImportDialog(QDialog): self.updateDelimiterButtonText() self.frm.allowHTML.setChecked(self.mw.pm.profile.get('allowHTML', True)) self.frm.importMode.setCurrentIndex(self.mw.pm.profile.get('importMode', 1)) + # import button + b = QPushButton(_("Import")) + self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole) self.exec_() def setupOptions(self): @@ -87,8 +90,6 @@ class ImportDialog(QDialog): self.mw, self.frm.modelArea, label=False) self.deck = aqt.deckchooser.DeckChooser( self.mw, self.frm.deckArea, label=False) - self.connect(self.frm.importButton, SIGNAL("clicked()"), - self.doImport) def modelChanged(self): self.importer.model = self.mw.col.models.current() @@ -138,8 +139,8 @@ you can enter it here. Use \\t to represent tab."""), d = `d` txt = _("Fields separated by: %s") % d self.frm.autoDetect.setText(txt) - - def doImport(self, update=False): + + def accept(self): self.importer.mapping = self.mapping if not self.importer.mappingOk(): showWarning( @@ -249,7 +250,7 @@ you can enter it here. Use \\t to represent tab."""), QDialog.reject(self) def helpRequested(self): - openHelp("FileImport") + openHelp("importing") def showUnicodeWarning(): diff --git a/designer/importing.ui b/designer/importing.ui index c05ab361b..ce14706bc 100644 --- a/designer/importing.ui +++ b/designer/importing.ui @@ -94,20 +94,6 @@ - - - - - - &Import - - - true - - - - - @@ -133,8 +119,8 @@ 0 0 - 402 - 206 + 529 + 251 @@ -158,7 +144,6 @@ - importButton buttonBox From de8adfecff06e8328530008fb32dde1895bc2c46 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Mar 2014 09:00:47 +0900 Subject: [PATCH 041/109] disallow \r and \n in media filenames --- anki/media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/media.py b/anki/media.py index a96135315..89fcbe69c 100644 --- a/anki/media.py +++ b/anki/media.py @@ -340,7 +340,7 @@ class MediaManager(object): # Illegal characters ########################################################################## - _illegalCharReg = re.compile(r'[][><:"/?*^\\|\0]') + _illegalCharReg = re.compile(r'[][><:"/?*^\\|\0\r\n]') def stripIllegal(self, str): return re.sub(self._illegalCharReg, "", str) From 8fecf53c3b2b9110adb51345c5883abc77033787 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Mar 2014 09:03:52 +0900 Subject: [PATCH 042/109] use qdesktopservices on qt5+pyqt4 --- aqt/profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/profiles.py b/aqt/profiles.py index ccf90fa47..4503240d6 100644 --- a/aqt/profiles.py +++ b/aqt/profiles.py @@ -179,7 +179,7 @@ documentation for information on using a flash drive.""") def _defaultBase(self): if isWin: - if qtmajor >= 5: + if False: #qtmajor >= 5: loc = QStandardPaths.writeableLocation(QStandardPaths.DocumentsLocation) else: loc = QDesktopServices.storageLocation(QDesktopServices.DocumentsLocation) From 93414007b813928f7799e1161a2c22618b68a143 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Mar 2014 09:04:28 +0900 Subject: [PATCH 043/109] stop providing pyqtconfig, as it's no longer available on newer plats --- aqt/qt.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/aqt/qt.py b/aqt/qt.py index a46aec0b9..328f82110 100644 --- a/aqt/qt.py +++ b/aqt/qt.py @@ -49,8 +49,3 @@ if qtmajor <= 4 and qtminor <= 6: import anki.template.furigana anki.template.furigana.ruby = r'\2\1' -if isWin or isMac: - # we no longer use this, but want it included in the mac+win builds - # so we don't break add-ons that use it. any new add-ons should use - # the above variables instead - from PyQt4 import pyqtconfig From e06312321896cfa5d9a080a60998bd8d4efa3b5a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 6 Mar 2014 09:07:03 +0900 Subject: [PATCH 044/109] use a new header key on qt5 to fix drawing bug --- aqt/browser.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index 47cdd6777..e81d620df 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -373,6 +373,11 @@ class Browser(QMainWindow): self.onSearch() self.show() + def _headerKey(self): + if qtmajor >= 5: + return "editor2" + return "editor" + def setupToolbar(self): self.toolbarWeb = AnkiWebView() self.toolbarWeb.setFixedHeight(32 + self.mw.fontHeightDelta) @@ -458,7 +463,7 @@ class Browser(QMainWindow): self.editor.setNote(None) saveGeom(self, "editor") saveState(self, "editor") - saveHeader(self.form.tableView.horizontalHeader(), "editor") + saveHeader(self.form.tableView.horizontalHeader(), self._headerKey()) self.col.conf['activeCols'] = self.model.activeCols self.col.setMod() self.hide() @@ -623,7 +628,7 @@ class Browser(QMainWindow): if not isWin: vh.hide() hh.show() - restoreHeader(hh, "editor") + restoreHeader(hh, self._headerKey()) hh.setHighlightSections(False) hh.setMinimumSectionSize(50) hh.setMovable(True) From 66908a172ac0320a049954455ab110df75353eba Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 7 Mar 2014 06:47:12 +0900 Subject: [PATCH 045/109] add houssam to about --- aqt/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/about.py b/aqt/about.py index 408c5fa17..e7945d5f8 100644 --- a/aqt/about.py +++ b/aqt/about.py @@ -31,7 +31,7 @@ system. It's free and open source.") Alex Fraser, Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, Charlene Barina, Christian Krause, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen, Emilio Wuerges, Emmanuel Jarri, Frank Harper, Gregor Skumavc, H. Mijail, -Ian Lewis, Immanuel Asmus, Iroiro, Jarvik7, +Houssam Salem, Ian Lewis, Immanuel Asmus, Iroiro, Jarvik7, Jin Eun-Deok, Jo Nakashima, Johanna Lindh, Kieran Clancy, LaC, Laurent Steffan, Luca Ban, Luciano Esposito, Marco Giancotti, Marcus Rubeus, Mari Egami, Michael Jürges, Mark Wilbur, Matthew Duggan, Matthew Holtz, Meelis Vasser, Michael Keppler, Michael From 9beed20dc0e21728c6d2f67e657fd62314af6684 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 7 Mar 2014 10:12:07 +0900 Subject: [PATCH 046/109] tweak heights and font sizes --- aqt/deckbrowser.py | 2 +- aqt/main.py | 2 +- aqt/reviewer.py | 2 +- aqt/webview.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 74ad1b0a3..60cbf1d9e 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -345,7 +345,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) if isMac: size = 28 else: - size = 36 + self.mw.fontHeightDelta*3 + size = 38 + self.mw.fontHeightDelta*3 self.bottom.web.setFixedHeight(size) self.bottom.web.setLinkHandler(self._linkHandler) diff --git a/aqt/main.py b/aqt/main.py index cdcc41a2f..0d90cf6af 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -1084,7 +1084,7 @@ will be lost. Continue?""")) def setupFonts(self): f = QFontInfo(self.font()) ws = QWebSettings.globalSettings() - self.fontHeight = f.pixelSize() + self.fontHeight = max(14, f.pixelSize()) self.fontFamily = f.family() self.fontHeightDelta = max(0, self.fontHeight - 13) ws.setFontFamily(QWebSettings.StandardFont, self.fontFamily) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 79216e00b..ffc239888 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -47,7 +47,7 @@ class Reviewer(object): if isMac: self.bottom.web.setFixedHeight(46) else: - self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4) + self.bottom.web.setFixedHeight(54+self.mw.fontHeightDelta*4) self.bottom.web.setLinkHandler(self._linkHandler) self._reps = None self.nextCard() diff --git a/aqt/webview.py b/aqt/webview.py index 3be755315..5afd0f72e 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -109,7 +109,7 @@ class AnkiWebView(QWebView): def stdHtml(self, body, css="", bodyClass="", loadCB=None, js=None, head=""): if isMac: - button = "font-weight: bold; height: 24px;" + button = "font-weight: normal; height: 24px;" else: button = "font-weight: normal;" self.setHtml(""" From fe2c94b819d6c249cca514ecf309b314d51adf5f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 7 Mar 2014 10:19:18 +0900 Subject: [PATCH 047/109] use different way of scrolling to answer to fix qt5 bug --- aqt/reviewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index ffc239888..4c17c7596 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -134,7 +134,8 @@ function _updateQA (q, answerMode, klass) { typeans.focus(); } if (answerMode) { - window.location = "#answer"; + var e = $("#answer"); + if (e[0]) { e[0].scrollIntoView(); } } else { window.scrollTo(0, 0); } From 7dcf0d8eadcdcc994e03dc9dd9f4ff435dcec49d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 9 Mar 2014 10:44:52 +0900 Subject: [PATCH 048/109] fix tag strip on export we must make sure to include spaces around tags or they can't be found --- anki/exporting.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/anki/exporting.py b/anki/exporting.py index 47288a1ce..2cbdfc53e 100644 --- a/anki/exporting.py +++ b/anki/exporting.py @@ -214,9 +214,7 @@ class AnkiExporter(Exporter): pass def removeSystemTags(self, tags): - tags = ' '.join(tag for tag in tags.split() - if (tag.lower() != "marked" and tag.lower() != "leech")) - return tags + return self.src.tags.remFromStr("marked leech", tags) # Packaged Anki decks ###################################################################### From 69a19b58c27695de1569bf8b1e83b9f77f819172 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Sun, 9 Mar 2014 18:12:39 +0000 Subject: [PATCH 049/109] Fixes creation of Cloze when having a chained modifier on the left of cloze --- aqt/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/editor.py b/aqt/editor.py index ce76ebe3f..755bfbd7e 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -690,7 +690,7 @@ class Editor(object): def onCloze(self): # check that the model is set up for cloze deletion - if '{{cloze:' not in self.note.model()['tmpls'][0]['qfmt']: + if not re.search('{{(.*:)*cloze:',self.note.model()['tmpls'][0]['qfmt']): if self.addMode: tooltip(_("Warning, cloze deletions will not work until " "you switch the type at the top to Cloze.")) From 90fd1c00c8e10aa547252abb0eb3806775debb94 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Wed, 12 Mar 2014 14:21:23 +0000 Subject: [PATCH 050/109] Adding support for parameters in modifiers: {{mod1(param1,param2):mod2(param3):field}} --- anki/template/template.py | 1 + 1 file changed, 1 insertion(+) diff --git a/anki/template/template.py b/anki/template/template.py index d2f055c61..33bfd6ea8 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -191,6 +191,7 @@ class Template(object): 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, context, tag, tag_name); if txt is None: From b1cb07077a52da853c3780babf3786bfa7ce6719 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 14 Mar 2014 11:56:15 +0900 Subject: [PATCH 051/109] include buried in suspended count --- anki/stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/stats.py b/anki/stats.py index bddf6119a..197e941a9 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -705,7 +705,7 @@ select sum(case when queue=2 and ivl >= 21 then 1 else 0 end), -- mtr sum(case when queue in (1,3) or (queue=2 and ivl < 21) then 1 else 0 end), -- yng/lrn sum(case when queue=0 then 1 else 0 end), -- new -sum(case when queue=-1 then 1 else 0 end) -- susp +sum(case when queue<0 then 1 else 0 end) -- susp from cards where did in %s""" % self._limit()) # Footer From b9ba2375a1f13aab90fdc22c3507404c4b691bf3 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 14 Mar 2014 12:51:38 +0900 Subject: [PATCH 052/109] fix some memory leaks --- aqt/addcards.py | 1 + aqt/browser.py | 1 + aqt/editcurrent.py | 1 + aqt/stats.py | 1 + 4 files changed, 4 insertions(+) diff --git a/aqt/addcards.py b/aqt/addcards.py index 3dcf46184..dd1de4d43 100644 --- a/aqt/addcards.py +++ b/aqt/addcards.py @@ -190,6 +190,7 @@ question on all cards."""), help="AddItems") saveGeom(self, "add") aqt.dialogs.close("AddCards") QDialog.reject(self) + self.deleteLater() def canClose(self): if (self.forceClose or self.editor.fieldsAreBlank() or diff --git a/aqt/browser.py b/aqt/browser.py index e81d620df..dcd1603c3 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -471,6 +471,7 @@ class Browser(QMainWindow): self.teardownHooks() self.mw.maybeReset() evt.accept() + self.deleteLater() def canClose(self): return True diff --git a/aqt/editcurrent.py b/aqt/editcurrent.py index c029fc36e..cb8d69067 100644 --- a/aqt/editcurrent.py +++ b/aqt/editcurrent.py @@ -64,6 +64,7 @@ class EditCurrent(QDialog): self.mw.moveToState("review") saveGeom(self, "editcurrent") aqt.dialogs.close("EditCurrent") + self.deleteLater() def canClose(self): return True diff --git a/aqt/stats.py b/aqt/stats.py index 93bf6601a..b7c6e0c54 100644 --- a/aqt/stats.py +++ b/aqt/stats.py @@ -45,6 +45,7 @@ class DeckStats(QDialog): def reject(self): saveGeom(self, self.name) QDialog.reject(self) + self.deleteLater() def browser(self): name = time.strftime("-%Y-%m-%d@%H-%M-%S.png", From 44031b065ccfca4607375f88bb49415cedebb5de Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 14 Mar 2014 15:28:34 +0900 Subject: [PATCH 053/109] update add-on forum link --- aqt/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/errors.py b/aqt/errors.py index 5c2a37330..533b37f46 100644 --- a/aqt/errors.py +++ b/aqt/errors.py @@ -75,7 +75,7 @@ into a bug report:""") pluginText = _("""\ An error occurred in an add-on.
Please post on the add-on forum:
%s
""") - pluginText %= "https://groups.google.com/forum/#!forum/anki-addons" + pluginText %= "https://anki.tenderapp.com/discussions/add-ons" if "addon" in error: txt = pluginText else: From f9d93fa8885d9dc084360598c04fcea84a5db497 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 14 Mar 2014 15:29:48 +0900 Subject: [PATCH 054/109] Revert "use a new header key on qt5 to fix drawing bug" This reverts commit e06312321896cfa5d9a080a60998bd8d4efa3b5a. --- aqt/browser.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index dcd1603c3..25380c78e 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -373,11 +373,6 @@ class Browser(QMainWindow): self.onSearch() self.show() - def _headerKey(self): - if qtmajor >= 5: - return "editor2" - return "editor" - def setupToolbar(self): self.toolbarWeb = AnkiWebView() self.toolbarWeb.setFixedHeight(32 + self.mw.fontHeightDelta) @@ -463,7 +458,7 @@ class Browser(QMainWindow): self.editor.setNote(None) saveGeom(self, "editor") saveState(self, "editor") - saveHeader(self.form.tableView.horizontalHeader(), self._headerKey()) + saveHeader(self.form.tableView.horizontalHeader(), "editor") self.col.conf['activeCols'] = self.model.activeCols self.col.setMod() self.hide() @@ -629,7 +624,7 @@ class Browser(QMainWindow): if not isWin: vh.hide() hh.show() - restoreHeader(hh, self._headerKey()) + restoreHeader(hh, "editor") hh.setHighlightSections(False) hh.setMinimumSectionSize(50) hh.setMovable(True) From 29026b08e2b871b210e7869a834677cf8f42278d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 14 Mar 2014 15:41:00 +0900 Subject: [PATCH 055/109] fix crashes and corruption in column headers set all columns to interactive at once to work around qt5 issues --- aqt/browser.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index 25380c78e..8ad8de788 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -717,11 +717,9 @@ by clicking on one on the left.""")) def setColumnSizes(self): hh = self.form.tableView.horizontalHeader() - for i in range(len(self.model.activeCols)): - if hh.visualIndex(i) == len(self.model.activeCols) - 1: - hh.setResizeMode(i, QHeaderView.Stretch) - else: - hh.setResizeMode(i, QHeaderView.Interactive) + hh.setResizeMode(QHeaderView.Interactive) + hh.setResizeMode(hh.logicalIndex(len(self.model.activeCols)-1), + QHeaderView.Stretch) # this must be set post-resize or it doesn't work hh.setCascadingSectionResizes(False) From 49919b4eb67975220b20b3d639a93efeba4ec8e3 Mon Sep 17 00:00:00 2001 From: ospalh Date: Wed, 19 Mar 2014 15:09:41 +0100 Subject: [PATCH 056/109] Fix png images causing libpng 1.6.3 to print warnings. --- designer/icons/add16.png | Bin 3045 -> 406 bytes designer/icons/addtag16.png | Bin 3187 -> 545 bytes designer/icons/clock-icon.png | Bin 3496 -> 786 bytes designer/icons/clock16.png | Bin 3147 -> 440 bytes designer/icons/deck16.png | Bin 3168 -> 514 bytes designer/icons/delete16.png | Bin 3135 -> 459 bytes designer/icons/deletetag16.png | Bin 3189 -> 537 bytes designer/icons/format-stroke-color.png | Bin 5017 -> 2378 bytes designer/icons/info.png | Bin 3096 -> 450 bytes designer/icons/plus-circle.png | Bin 3566 -> 802 bytes designer/icons/plus16.png | Bin 3220 -> 502 bytes designer/icons/star16.png | Bin 3128 -> 482 bytes designer/icons/star_off16.png | Bin 3131 -> 483 bytes designer/icons/stock_group.png | Bin 4555 -> 1687 bytes designer/icons/text_clear.png | Bin 3133 -> 475 bytes designer/icons/view-refresh.png | Bin 3176 -> 517 bytes designer/icons/view-statistics.png | Bin 3107 -> 457 bytes 17 files changed, 0 insertions(+), 0 deletions(-) diff --git a/designer/icons/add16.png b/designer/icons/add16.png index 7a43792ab979a03507c8be655997d9c74421931a..27822a8dc105a032ea4376c49d9eed68bd9ec857 100644 GIT binary patch delta 11 ScmaDVK8<;T^5!D0VnzTPGz07a delta 2668 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 LNkvXXu0mjffm_&a delta 3129 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 delta 3437 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 delta 3100 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 E0MINRqyPW_ delta 2779 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 TE&=-P00000NkvXXu0mjf&A6yI delta 3037 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 diff --git a/designer/icons/plus-circle.png b/designer/icons/plus-circle.png index 4fc5b8c81fec7628530ed728af50e6630a66902c..e1b390f3effbfb525cfe515a79a1fd2f95d411c4 100644 GIT binary patch delta 724 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 nt+FaZ3h-UecIL)=GSNdGF=r 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 UFp|QXVgLXD07*qoM6N<$f_TahssI20 diff --git a/designer/icons/star16.png b/designer/icons/star16.png index 1b42d54e7f9b4ff129de3b29485530c9f9f3fbf3..6402f6c06692e706515e6bdab371ab69e178c997 100644 GIT binary patch delta 406 zcmV;H0crlY7~%tvBa?CpH-B?UL_t(26$QahXpeCm0O05IzVCiB!^FnYY)WyEa!``U zadRXIb^ncf+Wm!^J%egS0X5W@m^L?KhN7o`m6hg=mxiA;gM@HV2*W8_6 z|GRr|RYVlI6mrygA%A9K`tZouO~LWT^X7%W+uO@rMxd3Ok$joFv+H#ExK*K5XtkOe zN^J^BsVP!F|?bawQMbeNFsQ7~nW__GluJ&&36UszKreFmrEq|@wy&2no zV%veOFNR)^e^$5A3^0L1Jzc#3u(o%ohwJzclOl`qYngwQ01E&B07*qoM6N<$f^V?K AkpKVy delta 3070 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 M07*qoM6N<$f)I=E&j0`b diff --git a/designer/icons/star_off16.png b/designer/icons/star_off16.png index 0458a3b975d9a0f1595108833261dc8547721c41..9146a976c6b3b42dd7724cd514f646bc62b82137 100644 GIT binary patch delta 407 zcmV;I0cifa7~=zwBa?CpH-B_VL_t(26$QaPNL5h)0O0T3d(_Op${v|uHaJUz5YgBY zL`y%k6|E6M&=xe5h5ZQ8Qh^W=5z*inL_!MD16wTA8bsd^f>EIfsn2^(-vLu!&>8KeSNy|IPST6bfiP8Vmp;t2qg4wr@sEa zzvtUSu`<4(OwHVkNeW4lBwKMSmzLAqyYl0&{%>vPYAA$I2!b3!F3gAenbFB|*sa+E zYdt4+hY-pf1vxbHYJZpwpH7dC-xavl`e5tP#$a2SV<==*-UyX1Ged`OmN%+Ms0vl} z&kj+mQl+#hMN%fRDwSbp)DqftiPl3xLO|pXGMG29xEW9O_AA81u1utZR5ibch4-It zkJabG!{Y;&2TnCx*bFkJaH+Ph8vttE7i-+Xf0s9ue04dqHw*v(002ovPDHLkV1n~h Byg2{> delta 3073 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 O0000EX>4Tx0C?J+Q+HUC_ZB|i_hk=OLfG)J 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 diff --git a/designer/icons/text_clear.png b/designer/icons/text_clear.png index 56479f981d7a3d007d201b2bd1580a208437818a..e9d6ee1bb07ffc0e04617f418e73c3c5972b177b 100644 GIT binary patch delta 429 zcmV;e0aE_G7~2DoBsKwaQb$4o*~u(_0000WV@Og>004R>004l5008;`004mK004C` z008P>0026e000+ooVrmwks%j<0aQsuK~y-)WBC97KLaJ0fvN_SmzO70R8+hN;{TuPjKwDdXo3H^u&HDoa z0HsO*=PmfkNSBGS*p z!(#x{5C+6Qf%pRuXMkvIl9Mh_Qxg#X1LE&MT!x`QU0waZyuAE>2?>e+0s;d6xwyFg zv$C@OXJ%&p&&bI5AE@aJ)DP;e3Q`O>3B>n-SOm)F1>!#d X>ZXf*liTsR00000NkvXXu0mjfRp!Hl delta 3128 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 S{+*Wq0000H#(#0cf^GAP0G~-_ zdBaC8SvNEeB8V0Xc7;251VpSbLxcN-3|TEAVtRAQGPk(K2D<=s_(2_?Hrd4VidJf! zc~Q8)c6H#F-gEQ0U=-UXz&C#6pdS4Wt|;1S-|HWJ`G;r zGfEqcMdKDFJj&dpMt_|Np7I-5Vwe^hiyO^R;W80Tu2W%*DqEy{XPkN5NYtyQ(+siz zd}fLnzOl<|?(&GSR6Q}7^V{zjVUSgJ0Qk!X@{ID9+_uRGqNRdeW|#qncu2q%1{mQz zd8?&8)w*^eSt6iJfs2$0SR`-t(~$#+Aabzh*qLh9j;FIUX*}{6FU|hxtM5A@M`eHa x(jGZ{>TKVG{&i!G?cX>(b@H4XmqX$p{{U8lSwICu$EE-P002ovPDHLkV1l=)(K!GB delta 3132 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 Date: Fri, 14 Mar 2014 15:55:15 +0900 Subject: [PATCH 057/109] show main window on startup on mac this causes a visible resize, but seems to fix the intermittent beachball on startup issue --- aqt/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aqt/main.py b/aqt/main.py index 0d90cf6af..9c579921e 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -63,6 +63,8 @@ class AnkiQt(QMainWindow): self.onAppMsg(unicode(args[0], sys.getfilesystemencoding(), "ignore")) # Load profile in a timer so we can let the window finish init and not # close on profile load error. + if isMac and qtmajor >= 5: + self.show() self.progress.timer(10, self.setupProfile, False) def setupUI(self): From b562b8c6dc1ef7188b88e868cbd3d780c4352aea Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 27 Mar 2014 13:58:30 +0900 Subject: [PATCH 058/109] Revert "fix some memory leaks" This reverts commit b9ba2375a1f13aab90fdc22c3507404c4b691bf3. testing assumption that this is causing the windows crashes --- aqt/addcards.py | 1 - aqt/browser.py | 1 - aqt/editcurrent.py | 1 - aqt/stats.py | 1 - 4 files changed, 4 deletions(-) diff --git a/aqt/addcards.py b/aqt/addcards.py index dd1de4d43..3dcf46184 100644 --- a/aqt/addcards.py +++ b/aqt/addcards.py @@ -190,7 +190,6 @@ question on all cards."""), help="AddItems") saveGeom(self, "add") aqt.dialogs.close("AddCards") QDialog.reject(self) - self.deleteLater() def canClose(self): if (self.forceClose or self.editor.fieldsAreBlank() or diff --git a/aqt/browser.py b/aqt/browser.py index 8ad8de788..28ca2445b 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -466,7 +466,6 @@ class Browser(QMainWindow): self.teardownHooks() self.mw.maybeReset() evt.accept() - self.deleteLater() def canClose(self): return True diff --git a/aqt/editcurrent.py b/aqt/editcurrent.py index cb8d69067..c029fc36e 100644 --- a/aqt/editcurrent.py +++ b/aqt/editcurrent.py @@ -64,7 +64,6 @@ class EditCurrent(QDialog): self.mw.moveToState("review") saveGeom(self, "editcurrent") aqt.dialogs.close("EditCurrent") - self.deleteLater() def canClose(self): return True diff --git a/aqt/stats.py b/aqt/stats.py index b7c6e0c54..93bf6601a 100644 --- a/aqt/stats.py +++ b/aqt/stats.py @@ -45,7 +45,6 @@ class DeckStats(QDialog): def reject(self): saveGeom(self, self.name) QDialog.reject(self) - self.deleteLater() def browser(self): name = time.strftime("-%Y-%m-%d@%H-%M-%S.png", From 5044089d42271a54959b78c6ccf536ecc1456e9f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 27 Mar 2014 14:02:55 +0900 Subject: [PATCH 059/109] Revert "tweak heights and font sizes" This reverts commit 9beed20dc0e21728c6d2f67e657fd62314af6684. beta testers all seem to prefer the original smaller size --- aqt/deckbrowser.py | 2 +- aqt/main.py | 2 +- aqt/reviewer.py | 2 +- aqt/webview.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 60cbf1d9e..74ad1b0a3 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -345,7 +345,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) if isMac: size = 28 else: - size = 38 + self.mw.fontHeightDelta*3 + size = 36 + self.mw.fontHeightDelta*3 self.bottom.web.setFixedHeight(size) self.bottom.web.setLinkHandler(self._linkHandler) diff --git a/aqt/main.py b/aqt/main.py index 9c579921e..292b2c079 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -1086,7 +1086,7 @@ will be lost. Continue?""")) def setupFonts(self): f = QFontInfo(self.font()) ws = QWebSettings.globalSettings() - self.fontHeight = max(14, f.pixelSize()) + self.fontHeight = f.pixelSize() self.fontFamily = f.family() self.fontHeightDelta = max(0, self.fontHeight - 13) ws.setFontFamily(QWebSettings.StandardFont, self.fontFamily) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 4c17c7596..1e5db7389 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -47,7 +47,7 @@ class Reviewer(object): if isMac: self.bottom.web.setFixedHeight(46) else: - self.bottom.web.setFixedHeight(54+self.mw.fontHeightDelta*4) + self.bottom.web.setFixedHeight(52+self.mw.fontHeightDelta*4) self.bottom.web.setLinkHandler(self._linkHandler) self._reps = None self.nextCard() diff --git a/aqt/webview.py b/aqt/webview.py index 5afd0f72e..3be755315 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -109,7 +109,7 @@ class AnkiWebView(QWebView): def stdHtml(self, body, css="", bodyClass="", loadCB=None, js=None, head=""): if isMac: - button = "font-weight: normal; height: 24px;" + button = "font-weight: bold; height: 24px;" else: button = "font-weight: normal;" self.setHtml(""" From 9f6b073defb3a56cab7e8c7b0ed3b2e6fa72038f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 27 Mar 2014 16:48:26 +0900 Subject: [PATCH 060/109] allow copy context item in info view --- aqt/browser.py | 2 +- aqt/webview.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index 28ca2445b..f167a7b37 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -861,7 +861,7 @@ by clicking on one on the left.""")) d = QDialog(self) l = QVBoxLayout() l.setMargin(0) - w = AnkiWebView() + w = AnkiWebView(canCopy=True) l.addWidget(w) w.stdHtml(info + "

" + reps) bb = QDialogButtonBox(QDialogButtonBox.Close) diff --git a/aqt/webview.py b/aqt/webview.py index 3be755315..54ac42bcd 100644 --- a/aqt/webview.py +++ b/aqt/webview.py @@ -40,7 +40,8 @@ class AnkiWebPage(QWebPage): class AnkiWebView(QWebView): - def __init__(self, canFocus=False): + # canFocus implies canCopy + def __init__(self, canFocus=False, canCopy=False): QWebView.__init__(self) self.setRenderHints( QPainter.TextAntialiasing | @@ -60,6 +61,7 @@ class AnkiWebView(QWebView): # reset each time new html is set; used to detect if still in same state self.key = None self.setCanFocus(canFocus) + self._canCopy = canCopy or canFocus def keyPressEvent(self, evt): if evt.matches(QKeySequence.Copy): @@ -79,7 +81,7 @@ class AnkiWebView(QWebView): QWebView.keyReleaseEvent(self, evt) def contextMenuEvent(self, evt): - if not self.isCardViewer: + if not self._canCopy: return m = QMenu(self) a = m.addAction(_("Copy")) @@ -130,12 +132,9 @@ button { def setBridge(self, bridge): self._bridge.setBridge(bridge) - def setCanFocus(self, isCardViewer=False): - """Set flag to denote if this WebView should follow rules specific to - card display (e.g., allow context menu, copy/paste)""" - - self.isCardViewer = isCardViewer - if self.isCardViewer: + def setCanFocus(self, canFocus=False): + self._canFocus = canFocus + if self._canFocus: self.setFocusPolicy(Qt.WheelFocus) else: self.setFocusPolicy(Qt.NoFocus) From 7dc26ed31d3e7b7e22d6bdb9b86a10bca7b1fd7d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 28 Mar 2014 07:25:27 +0900 Subject: [PATCH 061/109] only collapse cloze in type answer if single unique phrase --- aqt/reviewer.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/aqt/reviewer.py b/aqt/reviewer.py index 1e5db7389..c00bbf76c 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -434,17 +434,11 @@ Please run Tools>Empty Cards""") return txt.split("::")[0] return txt matches = [noHint(txt) for txt in matches] - if len(matches) > 1: - arr = [] - seen = {} - for m in matches: - if m in seen: - continue - seen[m] = 1 - arr.append(m) - txt = ", ".join(arr) - else: + uniqMatches = set(matches) + if len(uniqMatches) == 1: txt = matches[0] + else: + txt = ", ".join(matches) return txt def tokenizeComparison(self, given, correct): From 4a05c736fa3ee2bde8af87369ecf2a042f1ce86e Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 30 Mar 2014 10:19:48 +0900 Subject: [PATCH 062/109] tweak build_ui to handle qt5 ui translations --- tools/build_ui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build_ui.sh b/tools/build_ui.sh index a697fc77a..c26521cfd 100755 --- a/tools/build_ui.sh +++ b/tools/build_ui.sh @@ -29,7 +29,7 @@ do echo " * "$py pyuic4 $i -o $py # munge the output to use gettext - perl -pi.bak -e 's/QtGui.QApplication.translate\(".*?", /_(/; s/, None, QtGui.*/))/' $py + perl -pi.bak -e 's/(QtGui\.QApplication\.)?_?translate\(".*?", /_(/; s/, None.*/))/' $py rm $py.bak fi done From d56cf3c6b0967b327209b381279f9a232e4cc73d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 1 Apr 2014 13:13:59 +0900 Subject: [PATCH 063/109] add fix for AD issue --- anki/collection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/anki/collection.py b/anki/collection.py index f430eb98c..096507d37 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -695,6 +695,10 @@ select id from notes where mid not in """ + ids2str(self.models.ids())) self.remNotes(ids) # for each model for m in self.models.all(): + for t in m['tmpls']: + if t['did'] == "None": + t['did'] = None + problems.append(_("Fixed AnkiDroid deck override bug.")) if m['type'] == MODEL_STD: # model with missing req specification if 'req' not in m: From 3aeb5d86da28b417547620696dbf61a851c07320 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Wed, 9 Apr 2014 04:35:00 +1000 Subject: [PATCH 064/109] Added search saving feature to card browser. --- aqt/browser.py | 100 ++++++++++++++++++++++++ designer/icons.qrc | 2 + designer/icons/emblem-favorite-dark.png | Bin 0 -> 2324 bytes designer/icons/emblem-favorite-off.png | Bin 0 -> 2189 bytes 4 files changed, 102 insertions(+) create mode 100644 designer/icons/emblem-favorite-dark.png create mode 100644 designer/icons/emblem-favorite-off.png diff --git a/aqt/browser.py b/aqt/browser.py index f167a7b37..ae4f0439e 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -513,6 +513,9 @@ class Browser(QMainWindow): self.setTabOrder(self.form.searchEdit, self.form.tableView) self.form.searchEdit.setCompleter(None) self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory']) + self.connect(self.form.searchEdit.lineEdit(), + SIGNAL("returnPressed()"), + self.onSearch) def onSearch(self, reset=True): "Careful: if reset is true, the current note is saved." @@ -753,6 +756,7 @@ by clicking on one on the left.""")) self.form.tree.clear() root = self.form.tree self._systemTagTree(root) + self._favTree(root) self._decksTree(root) self._modelTree(root) self._userTagTree(root) @@ -815,6 +819,18 @@ by clicking on one on the left.""")) item.setIcon(0, QIcon(":/icons/" + icon)) return root + def _favTree(self, root): + saved = self.col.conf.get('savedFilters', []) + if not saved: + # Don't add favourites to tree if none saved + return + root = self.CallbackItem(root, _("My Searches"), None) + root.setExpanded(True) + root.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) + for name, filt in saved.items(): + item = self.CallbackItem(root, name, lambda s=filt: self.setFilter(s)) + item.setIcon(0, QIcon(":/icons/emblem-favorite-dark.png")) + def _userTagTree(self, root): for t in sorted(self.col.tags.all()): if t.lower() == "marked" or t.lower() == "leech": @@ -1723,3 +1739,87 @@ a { margin-right: 1em; } self.browser.addTags() elif l == "deletetag": self.browser.deleteTags() + + +# Favourites button +###################################################################### +class FavouritesLineEdit(QLineEdit): + buttonClicked = pyqtSignal(bool) + + def __init__(self, mw, browser, parent=None): + super(FavouritesLineEdit, self).__init__(parent) + self.mw = mw + self.browser = browser + # add conf if missing + if not self.mw.col.conf.has_key('savedFilters'): + self.mw.col.conf['savedFilters'] = {} + self.button = QToolButton(self) + self.button.setStyleSheet('border: 0px;') + self.button.setCursor(Qt.ArrowCursor) + self.button.clicked.connect(self.buttonClicked.emit) + self.setIcon(':/icons/emblem-favorite-off.png') + # flag to raise save or delete dialog on button click + self.doSave = True + # name of current saved filter (if query matches) + self.name = None + self.buttonClicked.connect(self.onClicked) + self.connect(self, SIGNAL("textEdited(QString)"), self.updateButton) + + def resizeEvent(self, event): + buttonSize = self.button.sizeHint() + frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) + self.button.move(self.rect().right() - frameWidth - buttonSize.width(), + (self.rect().bottom() - buttonSize.height() + 1) / 2) + super(FavouritesLineEdit, self).resizeEvent(event) + + def setIcon(self, path): + self.button.setIcon(QIcon(path)) + + def setText(self, txt): + super(FavouritesLineEdit, self).setText(txt) + self.updateButton() + + def updateButton(self, reset=True): + # If search text is a saved query, switch to the delete button. + # Otherwise show save button. + txt = unicode(self.text()).strip() + for key, value in self.mw.col.conf['savedFilters'].items(): + if txt == value: + self.doSave = False + self.name = key + self.setIcon(QIcon(":/icons/emblem-favorite.png")) + return + self.doSave = True + self.setIcon(QIcon(":/icons/emblem-favorite-off.png")) + + def onClicked(self): + if self.doSave: + self.saveClicked() + else: + self.deleteClicked() + + def saveClicked(self): + txt = unicode(self.text()).strip() + dlg = QInputDialog(self) + dlg.setInputMode(QInputDialog.TextInput) + dlg.setLabelText(_("The current search terms will be added as a new " + "item in the sidebar.\n" + "Search name:")) + dlg.setWindowTitle(_("Save search")) + ok = dlg.exec_() + name = dlg.textValue() + if ok: + self.mw.col.conf['savedFilters'][name] = txt + + self.updateButton() + self.browser.setupTree() + + def deleteClicked(self): + msg = _('Remove "%s" from your saved searches?') % self.name + ok = QMessageBox.question(self, _('Remove search'), + msg, QMessageBox.Yes, QMessageBox.No) + + if ok == QMessageBox.Yes: + self.mw.col.conf['savedFilters'].pop(self.name, None) + self.updateButton() + self.browser.setupTree() diff --git a/designer/icons.qrc b/designer/icons.qrc index cb3e36bd2..efcff2268 100644 --- a/designer/icons.qrc +++ b/designer/icons.qrc @@ -35,6 +35,8 @@ icons/editclear.png icons/view-statistics.png icons/emblem-favorite.png + icons/emblem-favorite-dark.png + icons/emblem-favorite-off.png icons/view-pim-calendar.png icons/anki-tag.png icons/edit-redo.png diff --git a/designer/icons/emblem-favorite-dark.png b/designer/icons/emblem-favorite-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..740209965700281a60e047ea3f0eda05b05dd4b0 GIT binary patch literal 2324 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 Date: Thu, 10 Apr 2014 14:01:24 +0900 Subject: [PATCH 065/109] disable previous osx workaround that may be causing crash on file dialog --- aqt/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/utils.py b/aqt/utils.py index 8f96286b8..b275ec020 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -240,7 +240,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None): ret = [] def accept(): # work around an osx crash - aqt.mw.app.processEvents() + #aqt.mw.app.processEvents() file = unicode(list(d.selectedFiles())[0]) if dirkey: dir = os.path.dirname(file) From fcc4df821fd92389ad8637019fbfec8e819b8f57 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 10 Apr 2014 14:02:35 +0900 Subject: [PATCH 066/109] disable explicit ca_certs may have added this to support beta.ankiweb.net as python doesn't support SNI, but it prevents changing cert --- anki/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/sync.py b/anki/sync.py index eb48fd591..7b69569ef 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -49,7 +49,7 @@ def httpCon(): else: assert 0, "Your distro has not packaged Anki correctly." return httplib2.Http( - timeout=HTTP_TIMEOUT, ca_certs=certs, + timeout=HTTP_TIMEOUT, #ca_certs=certs, proxy_info=HTTP_PROXY, disable_ssl_certificate_validation=not not HTTP_PROXY) From 01636dff00b3a97cfd11775e80a88556a73ce4d0 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 10 Apr 2014 15:00:32 +0900 Subject: [PATCH 067/109] bump ver --- anki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/__init__.py b/anki/__init__.py index fdbd9596d..9cd6cfa35 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.22" # build scripts grep this line, so preserve formatting +version="2.0.23" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] From c529a77686346b1ab6e75974db7b812e6efd505e Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 11 Apr 2014 01:13:55 +0900 Subject: [PATCH 068/109] bundle new and old cert fails to validate on some systems even when httplib2's default certs are bundled, so we need this not just for SNI --- anki/ankiweb.certs | 167 +++++++++++++++++++++++++++++++++++++++++++++ anki/sync.py | 2 +- 2 files changed, 168 insertions(+), 1 deletion(-) diff --git a/anki/ankiweb.certs b/anki/ankiweb.certs index 390503e10..c176bf785 100644 --- a/anki/ankiweb.certs +++ b/anki/ankiweb.certs @@ -53,3 +53,170 @@ Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEhjCCA26gAwIBAgIQUkIGSk83/kNpSHqWZ/9dJzANBgkqhkiG9w0BAQUFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow +gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl +IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY +aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0 +LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0 +qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt +KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr +ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX +vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM +vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN +SZzFkvSrMqFIWwIDAQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D +veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQWRkVHJ1c3RFeHRl +cm5hbENBUm9vdC5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BZGRU +cnVzdEV4dGVybmFsQ0FSb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAYGQ5WaJD +ZS79+R/WrjO76FMTxIjuIxpszthkWVNTkOg239T88055L9XmjwzvKkFtcb2beDgj +03BLhgz9EqciYhLYzOBR7y3lzQxFoura7X7s9zKa5wU1Xm7CLGhonf+M8cpVh8Qv +sUAG3IQiXG2zzdGbGgozKGYWDL0zwvYH8eOheZTg+NDQ099Shj+p4ckdPoaEsdtf +7uRJQ8E5fc8vlqd1XX5nZ4TlWSBAvzcivwdDtDDhQ4rNA11tuSnZhKf1YmOEhtY3 +vm9nu/9iVzmdDE2yKmE9HZzvmncgoC/uGnKdsJ2/eBMnBwpgEZP1Dy7J72skg/6b +kLRLaIHQwvrgPw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFAzCCA+ugAwIBAgIQTM1KmltFEyGMz5AviytRcTANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNMDYwOTE4MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBxMQswCQYD +VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT +YWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UEAxMOUG9z +aXRpdmVTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9T3lY +IpPJKD5SEQAvwKkgitctVR4Q57h/4oYqpOxe6eSSWJZUDfMXukGeFZFV78LuACAY +RYMm3yDMPbOhEzEKIVx5g3mrJBVcVvC0lZih2tIb6ha1y7ewwVP5pEba8C4kuGKe +joteK1qWoOpQ6Yj7KCpNmpxIT4O2h65Pxci12f2+P9GnncYsEw3AAcezcPOPabuw +PBDf6wkAhD9u7/zjLbTHXRHM9/Lx9uLjAH4SDt6NfQDKOj32cuh5JaYIFveriP9W +XgkXwFqCBWI0KyhIMpfQhAysExjbnmbHqhSLEWlN8QnTul2piDdi2L8Dm53X5gV+ +wmpSqo0HgOqODvMdAgMBAAGjggFuMIIBajAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd +BzfVhZadS9LDRTAdBgNVHQ4EFgQUuMoR6QYxedvDlMboGSq8uzUWMaQwDgYDVR0P +AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwewYDVR0fBHQwcjA4oDagNIYy +aHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5j +cmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9VVE4tVVNFUkZpcnN0LUhh +cmR3YXJlLmNybDCBhgYIKwYBBQUHAQEEejB4MDsGCCsGAQUFBzAChi9odHRwOi8v +Y3J0LmNvbW9kb2NhLmNvbS9VVE5BZGRUcnVzdFNlcnZlckNBLmNydDA5BggrBgEF +BQcwAoYtaHR0cDovL2NydC5jb21vZG8ubmV0L1VUTkFkZFRydXN0U2VydmVyQ0Eu +Y3J0MA0GCSqGSIb3DQEBBQUAA4IBAQAdtOf5GEhd7fpawx3jt++GFclsE0kWDTGM +MVzn2odkjq8SFqRaLZIaOz4hZaoXw5V+QBz9FGkGGM2sMexq8RaeiSY9WyGN6Oj5 +qz2qPMuZ8oZfiFMVBRflqNKFp05Jfdbdx4/OiL9lBeAUtTF37r0qhujop2ot2mUZ +jGfibfZKhWaDtjJNn0IjF9dFQWp2BNStuY9u3MI+6VHyntjzf/tQKvCL/W8NIjYu +zg5G8t6P2jt9HpOs/PQyKw+rAR+lQI/jJJkfXbKqDLnioeeSDJBLU30fKO5WPa8Y +Z0nf1R7CqJgrTEeDgUwuRMLvyGPui3tbMfYmYb95HLCpTqnJUHvi +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFjCCA/6gAwIBAgIQRi3682cfg0pV4BcKiOriGTANBgkqhkiG9w0BAQUFADBy +MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEYMBYGA1UE +AxMPRXNzZW50aWFsU1NMIENBMB4XDTE0MDQxMDAwMDAwMFoXDTE3MDQwOTIzNTk1 +OVowWzEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMR4wHAYDVQQL +ExVFc3NlbnRpYWxTU0wgV2lsZGNhcmQxFjAUBgNVBAMUDSouYW5raXdlYi5uZXQw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+A1hdxZ8NNorbzRF9qutY +7DNuKQdXRjMuRszAeAHI8Ln6+zfvMvLPvCHrZ9aDNTpKhAQBuE7NrFkR9oR0xHT8 +JtgVQKJ5zvVFfZmNoHyWYW0+dj8ay1Y/74V/I4Xhb43Fk4Q7UBgAl0kwm5vDWC6U +HsA9/KPfEbWCOLSVoA1sU60viggnfbIl0XmNkkt355KvUdJgT5LisOfi8KvH58RN +X8RHDsLI+Kv3rc11hLxwJ171NC0bPvjOIvQ5NKlNeDFZASx00kaGE3H26r1it2Gr +hrR8IlTSV967JDpYi1FO+Aom54/OZ6ozE/JNsbKlcwmdH2CO2aMizFZfoQmYEsyr +AgMBAAGjggG9MIIBuTAfBgNVHSMEGDAWgBTay+qtWwhdzP/8JlTOSeVVxjj0+DAd +BgNVHQ4EFgQU8P8DDEM/4k6bCQr8Z9BeoGw5ANgwDgYDVR0PAQH/BAQDAgWgMAwG +A1UdEwEB/wQCMAAwNAYDVR0lBC0wKwYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEE +AYI3CgMDBglghkgBhvhCBAEwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkG +CCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwB +AgEwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5jb21vZG9jYS5jb20vRXNz +ZW50aWFsU1NMQ0EuY3JsMG4GCCsGAQUFBwEBBGIwYDA4BggrBgEFBQcwAoYsaHR0 +cDovL2NydC5jb21vZG9jYS5jb20vRXNzZW50aWFsU1NMQ0FfMi5jcnQwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcgg0qLmFu +a2l3ZWIubmV0ggthbmtpd2ViLm5ldDANBgkqhkiG9w0BAQUFAAOCAQEAR2JPdym8 +MsSqmi2EyB4zR/UZH7XQUdIwx/1NhKf1XTN9akTNsS2y6Vp+TvaRVknEm7Z1i8CU +xiSZicsUOUr8MCzDVtTl3KUuYNUdsv0yXwTvGc01xJ26ix+KTmmQVKBq86gYGzXI +pLG0mfG1UAdZc6MxhcPXDROppvGXWk2vb6xYryVQiqV45SCiX2a4PtIf8zqO62eD +Xqazh3fpJ685rBnvWvpi8teYlYbJpJoaFEHa5ime2VixEYPVnfY2DBbiLQVImCgF +4NLwbCh79uD90CiGXHZbVUCq8fNf2gYzhK08erbDWhK39DzkBcE1XlbsPYzN9fUL +E1Re6AD7oBwALQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh +dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E +TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf +5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR +0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD +ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq +oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX +Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD +MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU +2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI +MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud +IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v +ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh +LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB +AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k +b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu +Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z +odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a +ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu +F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv ++Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL +XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEpjCCA46gAwIBAgIQRurwlgVMxeP6Zepun0LGZDANBgkqhkiG9w0BAQUFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow +gZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl +IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY +aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMRswGQYDVQQDExJVVE4gLSBEQVRBQ29y +cCBTR0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf7lgQoituVcSO +vy5GCefgCA8uK3oTlBu99raAjmUFkwAevK/iD44ZDRJH7Kyto/oucPjebvtWQhWe +LlzvI94huQV2JxkPT9bDnLS+lBlj8qYRCutTSJy+8ik7FugaoEymyfQYWWjAcPJT +AMBeUIKlVm82+UrgRIagTU7WR25JSstn16bEBbmOHvT8/83nNuCcBWyyMyIV0LTg +zBfAssD0/jI/KSqVe9jyp04PVHyhDYCzCQPB/1zdXpo+vK68R4pqrnHKH7EquF9C +BQvsRjDRcgvK6VZt9e/feL5hurKlrgRMvKisaRWXve/rtIy/NfjUw9EoDlw6n3AY +MyB3xKKvAgMBAAGjggEXMIIBEzAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g +JMtUGjAdBgNVHQ4EFgQUUzLRs89/+uDxoF2FTpLSnkUdtE8wDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI +AYb4QgQBMBEGA1UdIAQKMAgwBgYEVR0gADB7BgNVHR8EdDByMDigNqA0hjJodHRw +Oi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2 +oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJv +b3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBjhpIQsRP6N76OKrYbikP1XK4OFN/3 +aUB/vxpxAAnYv9QkSr/gk/8B2AvGD+x+R5ywXfd8FJ38wDOShFvSg/RS4iJYdPxD +Gz+no1jaA/288Drk7cwSu8m5rnsEoARyv+neLdKnUWYAc9K9fqqeU5Z9abIYPo6t +VlB+99Ww/zliZYKMllfDj/dg9sKNNIf8T0Pl278cqvaGzebfET+NB/dtgxPAOIg5 +YKF+MOHjiD6ku2NvLOmKaCzulmmsBGHhT04OnXJM9nk4yMdIaW+UD3S0vMjPV025 +dXGWDYoGC+vd0PA8fcYumEZqOMcCtci4smV13tqQCLZ3uFMAJctHynNf +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- diff --git a/anki/sync.py b/anki/sync.py index 7b69569ef..eb48fd591 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -49,7 +49,7 @@ def httpCon(): else: assert 0, "Your distro has not packaged Anki correctly." return httplib2.Http( - timeout=HTTP_TIMEOUT, #ca_certs=certs, + timeout=HTTP_TIMEOUT, ca_certs=certs, proxy_info=HTTP_PROXY, disable_ssl_certificate_validation=not not HTTP_PROXY) From 46146b687e6ef51aadef3ee7579b89117542454e Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 12 Apr 2014 13:33:53 +0900 Subject: [PATCH 069/109] tweak readme.dev --- README.development | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.development b/README.development index c58258379..aba57348a 100644 --- a/README.development +++ b/README.development @@ -1,7 +1,12 @@ Please see the README file for basic requirements. -You also need to have the python-pyqt development packages installed -(specifically, you need the binary pyuic4). +In addition to the basic requirements, you also need the PyQt development +tools (specifically pyrcc4 and pyuic4). These are often contained in a +separate package on Linux, such as 'pyqt4-dev-tools' on Debian/Ubuntu. On a Mac +they are part of the PyQt source install. + +WINDOWS USERS: I have not tested the build script on Windows, so you'll need +to fix any problems you encounter on your own. To use the development version: @@ -9,7 +14,11 @@ $ git clone https://github.com/dae/anki.git $ cd anki $ ./tools/build_ui.sh -Make sure you rebuild the UI every time you update the sources. +If you get any errors, you will not be able to proceed, so please return to +the top and check the requirements again. + +ALL USERS: Make sure you rebuild the UI every time you git pull, otherwise you +will get errors down the road. The translations are stored in a bazaar repo for integration with Launchpad's translation services. If you want to use a language other than English: From c23e32e027ea4529b8cf9fd00ab427235b992f0f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 12 Apr 2014 13:43:05 +0900 Subject: [PATCH 070/109] tweak readme.dev --- README.development | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.development b/README.development index aba57348a..aa135c7ff 100644 --- a/README.development +++ b/README.development @@ -5,8 +5,10 @@ tools (specifically pyrcc4 and pyuic4). These are often contained in a separate package on Linux, such as 'pyqt4-dev-tools' on Debian/Ubuntu. On a Mac they are part of the PyQt source install. -WINDOWS USERS: I have not tested the build script on Windows, so you'll need -to fix any problems you encounter on your own. +WINDOWS USERS: I have not tested the UI build script on Windows, so you'll need +to fix any problems you encounter on your own. If you can't figure it out, you +can still use the source packages of Anki, which contain pre-built copies +of the UI. To use the development version: From 962c814d5b0dcd8352f70a81b9acf5c7c65b72c4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 12 Apr 2014 15:54:25 +0900 Subject: [PATCH 071/109] bump version again, as new cert was missing an intermediate --- anki/__init__.py | 2 +- anki/ankiweb.certs | 249 +++++++++++++++++++++++++-------------------- 2 files changed, 139 insertions(+), 112 deletions(-) diff --git a/anki/__init__.py b/anki/__init__.py index 9cd6cfa35..62543b122 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.23" # build scripts grep this line, so preserve formatting +version="2.0.24" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] diff --git a/anki/ankiweb.certs b/anki/ankiweb.certs index c176bf785..c9ce9b4cc 100644 --- a/anki/ankiweb.certs +++ b/anki/ankiweb.certs @@ -1,4 +1,142 @@ -----BEGIN CERTIFICATE----- +MIIFFjCCA/6gAwIBAgIQRi3682cfg0pV4BcKiOriGTANBgkqhkiG9w0BAQUFADBy +MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEYMBYGA1UE +AxMPRXNzZW50aWFsU1NMIENBMB4XDTE0MDQxMDAwMDAwMFoXDTE3MDQwOTIzNTk1 +OVowWzEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMR4wHAYDVQQL +ExVFc3NlbnRpYWxTU0wgV2lsZGNhcmQxFjAUBgNVBAMUDSouYW5raXdlYi5uZXQw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+A1hdxZ8NNorbzRF9qutY +7DNuKQdXRjMuRszAeAHI8Ln6+zfvMvLPvCHrZ9aDNTpKhAQBuE7NrFkR9oR0xHT8 +JtgVQKJ5zvVFfZmNoHyWYW0+dj8ay1Y/74V/I4Xhb43Fk4Q7UBgAl0kwm5vDWC6U +HsA9/KPfEbWCOLSVoA1sU60viggnfbIl0XmNkkt355KvUdJgT5LisOfi8KvH58RN +X8RHDsLI+Kv3rc11hLxwJ171NC0bPvjOIvQ5NKlNeDFZASx00kaGE3H26r1it2Gr +hrR8IlTSV967JDpYi1FO+Aom54/OZ6ozE/JNsbKlcwmdH2CO2aMizFZfoQmYEsyr +AgMBAAGjggG9MIIBuTAfBgNVHSMEGDAWgBTay+qtWwhdzP/8JlTOSeVVxjj0+DAd +BgNVHQ4EFgQU8P8DDEM/4k6bCQr8Z9BeoGw5ANgwDgYDVR0PAQH/BAQDAgWgMAwG +A1UdEwEB/wQCMAAwNAYDVR0lBC0wKwYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEE +AYI3CgMDBglghkgBhvhCBAEwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkG +CCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwB +AgEwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5jb21vZG9jYS5jb20vRXNz +ZW50aWFsU1NMQ0EuY3JsMG4GCCsGAQUFBwEBBGIwYDA4BggrBgEFBQcwAoYsaHR0 +cDovL2NydC5jb21vZG9jYS5jb20vRXNzZW50aWFsU1NMQ0FfMi5jcnQwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcgg0qLmFu +a2l3ZWIubmV0ggthbmtpd2ViLm5ldDANBgkqhkiG9w0BAQUFAAOCAQEAR2JPdym8 +MsSqmi2EyB4zR/UZH7XQUdIwx/1NhKf1XTN9akTNsS2y6Vp+TvaRVknEm7Z1i8CU +xiSZicsUOUr8MCzDVtTl3KUuYNUdsv0yXwTvGc01xJ26ix+KTmmQVKBq86gYGzXI +pLG0mfG1UAdZc6MxhcPXDROppvGXWk2vb6xYryVQiqV45SCiX2a4PtIf8zqO62eD +Xqazh3fpJ685rBnvWvpi8teYlYbJpJoaFEHa5ime2VixEYPVnfY2DBbiLQVImCgF +4NLwbCh79uD90CiGXHZbVUCq8fNf2gYzhK08erbDWhK39DzkBcE1XlbsPYzN9fUL +E1Re6AD7oBwALQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh +dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E +TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf +5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR +0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD +ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq +oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX +Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD +MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU +2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI +MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud +IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v +ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh +LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB +AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k +b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu +Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z +odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a +ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu +F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv ++Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL +XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCA5OgAwIBAgIQLnmDLpCIh+qLjvMabuZ6RDANBgkqhkiG9w0BAQUFADCB +kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw +IFNHQzAeFw0wNjEyMDEwMDAwMDBaFw0yMDA1MzAxMDQ4MzhaMIGBMQswCQYDVQQG +EwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm +b3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RP +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZ +rts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAh +TaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23Iw +ambV4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVD +iOEjPqXSJDlqR6sA1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ +0o7KBWFxB3NH5YoZEr0ETc5OnKVIrLsm9wIDAQABo4IBCTCCAQUwHwYDVR0jBBgw +FoAUUzLRs89/+uDxoF2FTpLSnkUdtE8wHQYDVR0OBBYEFAtY5YvGTBU3pECpMKkh +vkc2Wlb/MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MCAGA1UdJQQZ +MBcGCisGAQQBgjcKAwMGCWCGSAGG+EIEATARBgNVHSAECjAIMAYGBFUdIAAwbQYD +VR0fBGYwZDAxoC+gLYYraHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLURBVEFD +b3JwU0dDLmNybDAvoC2gK4YpaHR0cDovL2NybC5jb21vZG8ubmV0L1VUTi1EQVRB +Q29ycFNHQy5jcmwwDQYJKoZIhvcNAQEFBQADggEBANheksSuFNxDrcKkw2dFBx35 +N6IZxxw3NZETHAfEfUKmDvCGXENrDkTPviRhOkKpzp1Mr3k5cN0OBCBOlZw83rdg +umNDQO1qD4FJRrsek8BL8/jhNkkbb7YMDfKQV4r8bZPyKMf6hgoosxcOWYoutr/N +4axMZmzyVZFWtzK/seR9teg6ti/bspzaUJOOTsWsmn5cnhI8O03GUHCzZSuO92uh +uyXAALv17BZlgQ771KMhlneaqHS8U6rCOVD/CwIJYcyVt9eIavZcxWjTFJUaR1/Z ++y3kL48ThqsxE0ATrG7ttRAwixtQqc7ujMrrfLW5Fj3U+m+SbR6ivfsCSsVwvvE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEpjCCA46gAwIBAgIQRurwlgVMxeP6Zepun0LGZDANBgkqhkiG9w0BAQUFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow +gZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl +IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY +aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMRswGQYDVQQDExJVVE4gLSBEQVRBQ29y +cCBTR0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf7lgQoituVcSO +vy5GCefgCA8uK3oTlBu99raAjmUFkwAevK/iD44ZDRJH7Kyto/oucPjebvtWQhWe +LlzvI94huQV2JxkPT9bDnLS+lBlj8qYRCutTSJy+8ik7FugaoEymyfQYWWjAcPJT +AMBeUIKlVm82+UrgRIagTU7WR25JSstn16bEBbmOHvT8/83nNuCcBWyyMyIV0LTg +zBfAssD0/jI/KSqVe9jyp04PVHyhDYCzCQPB/1zdXpo+vK68R4pqrnHKH7EquF9C +BQvsRjDRcgvK6VZt9e/feL5hurKlrgRMvKisaRWXve/rtIy/NfjUw9EoDlw6n3AY +MyB3xKKvAgMBAAGjggEXMIIBEzAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g +JMtUGjAdBgNVHQ4EFgQUUzLRs89/+uDxoF2FTpLSnkUdtE8wDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI +AYb4QgQBMBEGA1UdIAQKMAgwBgYEVR0gADB7BgNVHR8EdDByMDigNqA0hjJodHRw +Oi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2 +oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJv +b3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBjhpIQsRP6N76OKrYbikP1XK4OFN/3 +aUB/vxpxAAnYv9QkSr/gk/8B2AvGD+x+R5ywXfd8FJ38wDOShFvSg/RS4iJYdPxD +Gz+no1jaA/288Drk7cwSu8m5rnsEoARyv+neLdKnUWYAc9K9fqqeU5Z9abIYPo6t +VlB+99Ww/zliZYKMllfDj/dg9sKNNIf8T0Pl278cqvaGzebfET+NB/dtgxPAOIg5 +YKF+MOHjiD6ku2NvLOmKaCzulmmsBGHhT04OnXJM9nk4yMdIaW+UD3S0vMjPV025 +dXGWDYoGC+vd0PA8fcYumEZqOMcCtci4smV13tqQCLZ3uFMAJctHynNf +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- MIIFFzCCA/+gAwIBAgIRAP+ceCiXnKf8x8mBIBmTckswDQYJKoZIhvcNAQEFBQAw cTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ29tb2RvIENBIExpbWl0ZWQxFzAVBgNV @@ -109,114 +247,3 @@ jGfibfZKhWaDtjJNn0IjF9dFQWp2BNStuY9u3MI+6VHyntjzf/tQKvCL/W8NIjYu zg5G8t6P2jt9HpOs/PQyKw+rAR+lQI/jJJkfXbKqDLnioeeSDJBLU30fKO5WPa8Y Z0nf1R7CqJgrTEeDgUwuRMLvyGPui3tbMfYmYb95HLCpTqnJUHvi -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFFjCCA/6gAwIBAgIQRi3682cfg0pV4BcKiOriGTANBgkqhkiG9w0BAQUFADBy -MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD -VQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEYMBYGA1UE -AxMPRXNzZW50aWFsU1NMIENBMB4XDTE0MDQxMDAwMDAwMFoXDTE3MDQwOTIzNTk1 -OVowWzEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMR4wHAYDVQQL -ExVFc3NlbnRpYWxTU0wgV2lsZGNhcmQxFjAUBgNVBAMUDSouYW5raXdlYi5uZXQw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+A1hdxZ8NNorbzRF9qutY -7DNuKQdXRjMuRszAeAHI8Ln6+zfvMvLPvCHrZ9aDNTpKhAQBuE7NrFkR9oR0xHT8 -JtgVQKJ5zvVFfZmNoHyWYW0+dj8ay1Y/74V/I4Xhb43Fk4Q7UBgAl0kwm5vDWC6U -HsA9/KPfEbWCOLSVoA1sU60viggnfbIl0XmNkkt355KvUdJgT5LisOfi8KvH58RN -X8RHDsLI+Kv3rc11hLxwJ171NC0bPvjOIvQ5NKlNeDFZASx00kaGE3H26r1it2Gr -hrR8IlTSV967JDpYi1FO+Aom54/OZ6ozE/JNsbKlcwmdH2CO2aMizFZfoQmYEsyr -AgMBAAGjggG9MIIBuTAfBgNVHSMEGDAWgBTay+qtWwhdzP/8JlTOSeVVxjj0+DAd -BgNVHQ4EFgQU8P8DDEM/4k6bCQr8Z9BeoGw5ANgwDgYDVR0PAQH/BAQDAgWgMAwG -A1UdEwEB/wQCMAAwNAYDVR0lBC0wKwYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEE -AYI3CgMDBglghkgBhvhCBAEwTwYDVR0gBEgwRjA6BgsrBgEEAbIxAQICBzArMCkG -CCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzAIBgZngQwB -AgEwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5jb21vZG9jYS5jb20vRXNz -ZW50aWFsU1NMQ0EuY3JsMG4GCCsGAQUFBwEBBGIwYDA4BggrBgEFBQcwAoYsaHR0 -cDovL2NydC5jb21vZG9jYS5jb20vRXNzZW50aWFsU1NMQ0FfMi5jcnQwJAYIKwYB -BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcgg0qLmFu -a2l3ZWIubmV0ggthbmtpd2ViLm5ldDANBgkqhkiG9w0BAQUFAAOCAQEAR2JPdym8 -MsSqmi2EyB4zR/UZH7XQUdIwx/1NhKf1XTN9akTNsS2y6Vp+TvaRVknEm7Z1i8CU -xiSZicsUOUr8MCzDVtTl3KUuYNUdsv0yXwTvGc01xJ26ix+KTmmQVKBq86gYGzXI -pLG0mfG1UAdZc6MxhcPXDROppvGXWk2vb6xYryVQiqV45SCiX2a4PtIf8zqO62eD -Xqazh3fpJ685rBnvWvpi8teYlYbJpJoaFEHa5ime2VixEYPVnfY2DBbiLQVImCgF -4NLwbCh79uD90CiGXHZbVUCq8fNf2gYzhK08erbDWhK39DzkBcE1XlbsPYzN9fUL -E1Re6AD7oBwALQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh -dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E -TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf -5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR -0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD -ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq -oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX -Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD -MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU -2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI -MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud -IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v -ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh -LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB -AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k -b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu -Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z -odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a -ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu -F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv -+Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL -XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEpjCCA46gAwIBAgIQRurwlgVMxeP6Zepun0LGZDANBgkqhkiG9w0BAQUFADBv -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk -ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF -eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow -gZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl -IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY -aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMRswGQYDVQQDExJVVE4gLSBEQVRBQ29y -cCBTR0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf7lgQoituVcSO -vy5GCefgCA8uK3oTlBu99raAjmUFkwAevK/iD44ZDRJH7Kyto/oucPjebvtWQhWe -LlzvI94huQV2JxkPT9bDnLS+lBlj8qYRCutTSJy+8ik7FugaoEymyfQYWWjAcPJT -AMBeUIKlVm82+UrgRIagTU7WR25JSstn16bEBbmOHvT8/83nNuCcBWyyMyIV0LTg -zBfAssD0/jI/KSqVe9jyp04PVHyhDYCzCQPB/1zdXpo+vK68R4pqrnHKH7EquF9C -BQvsRjDRcgvK6VZt9e/feL5hurKlrgRMvKisaRWXve/rtIy/NfjUw9EoDlw6n3AY -MyB3xKKvAgMBAAGjggEXMIIBEzAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g -JMtUGjAdBgNVHQ4EFgQUUzLRs89/+uDxoF2FTpLSnkUdtE8wDgYDVR0PAQH/BAQD -AgEGMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI -AYb4QgQBMBEGA1UdIAQKMAgwBgYEVR0gADB7BgNVHR8EdDByMDigNqA0hjJodHRw -Oi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2 -oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJv -b3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBjhpIQsRP6N76OKrYbikP1XK4OFN/3 -aUB/vxpxAAnYv9QkSr/gk/8B2AvGD+x+R5ywXfd8FJ38wDOShFvSg/RS4iJYdPxD -Gz+no1jaA/288Drk7cwSu8m5rnsEoARyv+neLdKnUWYAc9K9fqqeU5Z9abIYPo6t -VlB+99Ww/zliZYKMllfDj/dg9sKNNIf8T0Pl278cqvaGzebfET+NB/dtgxPAOIg5 -YKF+MOHjiD6ku2NvLOmKaCzulmmsBGHhT04OnXJM9nk4yMdIaW+UD3S0vMjPV025 -dXGWDYoGC+vd0PA8fcYumEZqOMcCtci4smV13tqQCLZ3uFMAJctHynNf ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- From 468c4f0c4b212a124bb82ab6f58fe3e20247a739 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 13 Apr 2014 01:16:31 +0900 Subject: [PATCH 072/109] add julien --- aqt/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/about.py b/aqt/about.py index e7945d5f8..97e749a79 100644 --- a/aqt/about.py +++ b/aqt/about.py @@ -32,7 +32,7 @@ Alex Fraser, Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, Charlene Bari Christian Krause, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen, Emilio Wuerges, Emmanuel Jarri, Frank Harper, Gregor Skumavc, H. Mijail, Houssam Salem, Ian Lewis, Immanuel Asmus, Iroiro, Jarvik7, -Jin Eun-Deok, Jo Nakashima, Johanna Lindh, Kieran Clancy, LaC, Laurent Steffan, +Jin Eun-Deok, Jo Nakashima, Johanna Lindh, Julien Baley, Kieran Clancy, LaC, Laurent Steffan, Luca Ban, Luciano Esposito, Marco Giancotti, Marcus Rubeus, Mari Egami, Michael Jürges, Mark Wilbur, Matthew Duggan, Matthew Holtz, Meelis Vasser, Michael Keppler, Michael Montague, Michael Penkov, Michal Čadil, Morteza Salehi, Nathanael Law, Nick Cook, Niklas From b99e349695ffcf0b5889305bf091c4f57e3faf84 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 14 Apr 2014 15:32:30 +0900 Subject: [PATCH 073/109] fix copy not appearing when reviewing --- aqt/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/main.py b/aqt/main.py index 292b2c079..19716ea98 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -512,7 +512,7 @@ title="%s">%s''' % ( self.toolbar = aqt.toolbar.Toolbar(self, tweb) self.toolbar.draw() # main area - self.web = aqt.webview.AnkiWebView() + self.web = aqt.webview.AnkiWebView(canCopy=True) self.web.setObjectName("mainText") self.web.setFocusPolicy(Qt.WheelFocus) self.web.setMinimumWidth(400) From fac360d744b1a8d0618ac1fdf1031b1b84829fb2 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Mon, 14 Apr 2014 17:21:28 +0100 Subject: [PATCH 074/109] Allows smoother transition from old modifier syntax {{a:b:fld}} to new one {{a(b):fld}} --- anki/template/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/template/template.py b/anki/template/template.py index 33bfd6ea8..9431d30cc 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -192,7 +192,7 @@ class Template(object): else: # hook-based field modifier mod, extra = re.search("^(.*?)(?:\((.*)\))?$", mod).groups() - txt = runFilter('fmod_' + mod, txt or '', extra, context, + txt = runFilter('fmod_' + mod, txt or '', extra or '', context, tag, tag_name); if txt is None: return '{unknown field %s}' % tag_name From 966aca0f20691a7ff2afd59c4a34375855c14b12 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 16 Apr 2014 00:46:26 +0900 Subject: [PATCH 075/109] disable window modal progress dialog which is causing crashes --- aqt/progress.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aqt/progress.py b/aqt/progress.py index 13b218a88..f661f12a1 100644 --- a/aqt/progress.py +++ b/aqt/progress.py @@ -96,7 +96,8 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""" self._win.setCancelButton(None) self._win.setAutoClose(False) self._win.setAutoReset(False) - self._win.setWindowModality(Qt.ApplicationModal) + if not isMac: + self._win.setWindowModality(Qt.ApplicationModal) # we need to manually manage minimum time to show, as qt gets confused # by the db handler self._win.setMinimumDuration(100000) From 694682d964a5d1e9b7c755f4d330bf79e293f810 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 16 Apr 2014 01:15:59 +0900 Subject: [PATCH 076/109] fix multiple cloze tags contained on one line inner regex was capturing multiple clozes. fixes https://anki.tenderapp.com/discussions/ankidesktop/6599-empty-cards-3533-cards-to-delete --- anki/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/models.py b/anki/models.py index 599a10c92..5249ee128 100644 --- a/anki/models.py +++ b/anki/models.py @@ -569,7 +569,7 @@ select id from notes where mid = ?)""" % " ".join(map), sflds = splitFields(flds) map = self.fieldMap(m) ords = set() - matches = re.findall("{{[^}]*?cloze:(?:.*?:)*(.+?)}}", m['tmpls'][0]['qfmt']) + matches = re.findall("{{[^}]*?cloze:(?:[^}]?:)*(.+?)}}", m['tmpls'][0]['qfmt']) matches += re.findall("<%cloze:(.+?)%>", m['tmpls'][0]['qfmt']) for fname in matches: if fname not in map: From 5d1aeb4dde805b13b8c088adcd20017aa7e5321a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 16 Apr 2014 01:36:43 +0900 Subject: [PATCH 077/109] bump version --- anki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/__init__.py b/anki/__init__.py index 62543b122..21869eadd 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.24" # build scripts grep this line, so preserve formatting +version="2.0.25" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] From a88a8429e2344ee9b6e7724f44b47a02641ef302 Mon Sep 17 00:00:00 2001 From: Julien Baley Date: Tue, 15 Apr 2014 17:39:09 +0100 Subject: [PATCH 078/109] Makes the test agree with the current behaviour of chained mod (cloze not always first) --- tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_models.py b/tests/test_models.py index 22c5e088d..3db14e1c7 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -194,7 +194,7 @@ def test_chained_mods(): #We replace the default Cloze template t = mm.newTemplate("ChainedCloze") t['qfmt'] = "{{cloze:text:Text}}" - t['afmt'] = "{{text:cloze:Text}}" #independent of the order of mods + t['afmt'] = "{{cloze:text:Text}}" mm.addTemplate(m, t) mm.save(m) d.models.remTemplate(m, m['tmpls'][0]) From 28b2f8fe03ce51596951280e26bb1776c17da706 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 16 Apr 2014 01:43:33 +0900 Subject: [PATCH 079/109] allow main webview to focus, which may fix shortcut issues some users reported plugins that define reviewer shortcuts broke --- aqt/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/main.py b/aqt/main.py index 19716ea98..19ab5ea81 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -512,7 +512,7 @@ title="%s">%s''' % ( self.toolbar = aqt.toolbar.Toolbar(self, tweb) self.toolbar.draw() # main area - self.web = aqt.webview.AnkiWebView(canCopy=True) + self.web = aqt.webview.AnkiWebView(canFocus=True) self.web.setObjectName("mainText") self.web.setFocusPolicy(Qt.WheelFocus) self.web.setMinimumWidth(400) From 89c9af744535dac5788f6a51a387ba8ac0f503f3 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 18 Apr 2014 04:17:05 +0900 Subject: [PATCH 080/109] use default sample rate instead of forcing 44100 The 64 bit built of portaudio on OSX seems to generate a wav file that says it's 44100 but is actually the default rate, leading to samples playing too fast or slow. --- anki/sound.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/anki/sound.py b/anki/sound.py index ba1a7e2a9..f68ab9dd7 100644 --- a/anki/sound.py +++ b/anki/sound.py @@ -204,12 +204,12 @@ addHook("unloadProfile", stopMplayer) ########################################################################## try: + import pyaudio import wave PYAU_FORMAT = pyaudio.paInt16 PYAU_CHANNELS = 1 - PYAU_RATE = 44100 PYAU_INPUT_INDEX = None except: pass @@ -244,12 +244,16 @@ class PyAudioThreadedRecorder(threading.Thread): except NameError: raise Exception( "Pyaudio not installed (recording not supported on OSX10.3)") + + rate = int(p.get_default_input_device_info()['defaultSampleRate']) + stream = p.open(format=PYAU_FORMAT, channels=PYAU_CHANNELS, - rate=PYAU_RATE, + rate=rate, input=True, input_device_index=PYAU_INPUT_INDEX, frames_per_buffer=chunk) + all = [] while not self.finish: try: @@ -267,7 +271,7 @@ class PyAudioThreadedRecorder(threading.Thread): wf = wave.open(processingSrc, 'wb') wf.setnchannels(PYAU_CHANNELS) wf.setsampwidth(p.get_sample_size(PYAU_FORMAT)) - wf.setframerate(PYAU_RATE) + wf.setframerate(rate) wf.writeframes(data) wf.close() From fa57fd3ad9b10bd931d6b2efd083847842cd55cc Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 21 Apr 2014 14:50:18 +0900 Subject: [PATCH 081/109] don't fail if \n in cloze --- anki/template/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/template/template.py b/anki/template/template.py index 9431d30cc..89b64b52a 100644 --- a/anki/template/template.py +++ b/anki/template/template.py @@ -4,7 +4,7 @@ from anki.hooks import runFilter from anki.template import furigana; furigana.install() from anki.template import hint; hint.install() -clozeReg = r"\{\{c%s::(.*?)(::(.*?))?\}\}" +clozeReg = r"(?s)\{\{c%s::(.*?)(::(.*?))?\}\}" modifiers = {} def modifier(symbol): From bd289fc37e853a13438bd5384f21131743276e4c Mon Sep 17 00:00:00 2001 From: ispedals Date: Mon, 21 Apr 2014 17:04:46 -0400 Subject: [PATCH 082/109] Close file descriptors before unlinking Unlinking a file before closing may not be supported on non-POSIX systems such as Windows. This allows more tests to run to completion. --- tests/shared.py | 2 ++ tests/test_collection.py | 5 +++-- tests/test_exporting.py | 20 +++++++++++++++----- tests/test_upgrade.py | 5 +++-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index 0f19f548a..a8ba830bc 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -15,6 +15,7 @@ def assertException(exception, func): def getEmptyDeck(): if len(getEmptyDeck.master) == 0: (fd, nam) = tempfile.mkstemp(suffix=".anki2") + os.close(fd) os.unlink(nam) col = aopen(nam) col.db.close() @@ -28,6 +29,7 @@ getEmptyDeck.master = "" # Fallback for when the DB needs options passed in. def getEmptyDeckWith(**kwargs): (fd, nam) = tempfile.mkstemp(suffix=".anki2") + os.close(fd) os.unlink(nam) return aopen(nam, **kwargs) diff --git a/tests/test_collection.py b/tests/test_collection.py index 578b3fdd2..8e1e99a88 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -1,6 +1,6 @@ # coding: utf-8 -import os +import os, tempfile from tests.shared import assertException, getEmptyDeck from anki.stdmodels import addBasicModel @@ -11,8 +11,9 @@ newMod = None def test_create(): global newPath, newMod - path = "/tmp/test_attachNew.anki2" + (fd, path) = tempfile.mkstemp(suffix=".anki2", prefix="test_attachNew") try: + os.close(fd) os.unlink(path) except OSError: pass diff --git a/tests/test_exporting.py b/tests/test_exporting.py index 1e6d6b3fb..428a621a4 100644 --- a/tests/test_exporting.py +++ b/tests/test_exporting.py @@ -36,7 +36,9 @@ def test_export_anki(): deck.decks.setConf(dobj, confId) # export e = AnkiExporter(deck) - newname = unicode(tempfile.mkstemp(prefix="ankitest", suffix=".anki2")[1]) + fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2") + newname = unicode(newname) + os.close(fd) os.unlink(newname) e.exportInto(newname) # exporting should not have changed conf for original deck @@ -54,7 +56,9 @@ def test_export_anki(): # conf should be 1 assert dobj['conf'] == 1 # try again, limited to a deck - newname = unicode(tempfile.mkstemp(prefix="ankitest", suffix=".anki2")[1]) + fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2") + newname = unicode(newname) + os.close(fd) os.unlink(newname) e.did = 1 e.exportInto(newname) @@ -69,7 +73,9 @@ def test_export_ankipkg(): n['Front'] = u'[sound:今日.mp3]' deck.addNote(n) e = AnkiPackageExporter(deck) - newname = unicode(tempfile.mkstemp(prefix="ankitest", suffix=".apkg")[1]) + fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg") + newname = unicode(newname) + os.close(fd) os.unlink(newname) e.exportInto(newname) @@ -92,7 +98,9 @@ def test_export_anki_due(): # export e = AnkiExporter(deck) e.includeSched = True - newname = unicode(tempfile.mkstemp(prefix="ankitest", suffix=".anki2")[1]) + fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2") + newname = unicode(newname) + os.close(fd) os.unlink(newname) e.exportInto(newname) # importing into a new deck, the due date should be equivalent @@ -115,7 +123,9 @@ def test_export_anki_due(): @nose.with_setup(setup1) def test_export_textnote(): e = TextNoteExporter(deck) - f = unicode(tempfile.mkstemp(prefix="ankitest")[1]) + fd, f = tempfile.mkstemp(prefix="ankitest") + f = unicode(f) + os.close(fd) os.unlink(f) e.exportInto(f) e.includeTags = True diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index c293a9118..155c13ca0 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -1,6 +1,6 @@ # coding: utf-8 -import datetime, shutil +import datetime, shutil, tempfile from anki import Collection from anki.consts import * from shared import getUpgradeDeckPath, testDir @@ -63,8 +63,9 @@ def test_invalid_ords(): assert deck.db.scalar("select count() from cards where ord = 1") == 1 def test_upgrade2(): - p = "/tmp/alpha-upgrade.anki2" + fd, p = tempfile.mkstemp(suffix=".anki2", prefix="alpha-upgrade") if os.path.exists(p): + os.close(fd) os.unlink(p) shutil.copy2(os.path.join(testDir, "support/anki2-alpha.anki2"), p) col = Collection(p) From 5629533b3895bf0bf9e4143f18516b81d483967e Mon Sep 17 00:00:00 2001 From: Adam Mesha Date: Tue, 22 Apr 2014 01:16:39 +0300 Subject: [PATCH 083/109] Add hooks before and after a state change. Useful for plugin authors. --- aqt/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aqt/main.py b/aqt/main.py index 19ab5ea81..05c28c684 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -383,7 +383,9 @@ the manual for information on how to restore from an automatic backup.")) if cleanup: cleanup(state) self.state = state + runHook('beforeStateChange', state, oldState, *args) getattr(self, "_"+state+"State")(oldState, *args) + runHook('afterStateChange', state, oldState, *args) def _deckBrowserState(self, oldState): self.deckBrowser.show() From 00dd291819d97361e5790f82b26977493a0d294c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 23 Apr 2014 08:16:45 +0900 Subject: [PATCH 084/109] bump version --- anki/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/__init__.py b/anki/__init__.py index 21869eadd..d7f33c917 100644 --- a/anki/__init__.py +++ b/anki/__init__.py @@ -30,6 +30,6 @@ if arch[1] == "ELF": sys.path.insert(0, os.path.join(ext, "py2.%d-%s" % ( sys.version_info[1], arch[0][0:2]))) -version="2.0.25" # build scripts grep this line, so preserve formatting +version="2.0.26" # build scripts grep this line, so preserve formatting from anki.storage import Collection __all__ = ["Collection"] From b4c52150132a0b32b6cb943b527f956d36669f61 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 23 Apr 2014 08:20:00 +0900 Subject: [PATCH 085/109] allow app modal again now that qt's been fixed --- aqt/progress.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aqt/progress.py b/aqt/progress.py index f661f12a1..13b218a88 100644 --- a/aqt/progress.py +++ b/aqt/progress.py @@ -96,8 +96,7 @@ Your pysqlite2 is too old. Anki will appear frozen during long operations.""" self._win.setCancelButton(None) self._win.setAutoClose(False) self._win.setAutoReset(False) - if not isMac: - self._win.setWindowModality(Qt.ApplicationModal) + self._win.setWindowModality(Qt.ApplicationModal) # we need to manually manage minimum time to show, as qt gets confused # by the db handler self._win.setMinimumDuration(100000) From bbc8f75d70b0898b42fb44243b1c73a37f9b468d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 3 May 2014 12:07:18 +0900 Subject: [PATCH 086/109] make sure OR clause doesn't allow suspended cards in filtered deck fixes http://help.ankisrs.net/discussions/ankidesktop/6673-filter-decks-can-unsuspend-cards --- anki/sched.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/sched.py b/anki/sched.py index d49507080..0d6415d5a 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -954,7 +954,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)""" def _fillDyn(self, deck): search, limit, order = deck['terms'][0] orderlimit = self._dynOrder(order, limit) - search += " -is:suspended -is:buried -deck:filtered" + search = "(%s) -is:suspended -is:buried -deck:filtered" % search try: ids = self.col.findCards(search, order=orderlimit) except: From 226b2dbbfd3006f636984d99d1c572e2b06ba6c4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 10 May 2014 14:15:13 +0900 Subject: [PATCH 087/109] add chajadan's readme.dev note --- README.development | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/README.development b/README.development index aa135c7ff..69970209a 100644 --- a/README.development +++ b/README.development @@ -5,10 +5,7 @@ tools (specifically pyrcc4 and pyuic4). These are often contained in a separate package on Linux, such as 'pyqt4-dev-tools' on Debian/Ubuntu. On a Mac they are part of the PyQt source install. -WINDOWS USERS: I have not tested the UI build script on Windows, so you'll need -to fix any problems you encounter on your own. If you can't figure it out, you -can still use the source packages of Anki, which contain pre-built copies -of the UI. +Windows users, please see the note at the bottom of this file before proceeding. To use the development version: @@ -42,3 +39,20 @@ Before contributing code, please read the LICENSE file. If you'd like to contribute translations, please see the translations section of http://ankisrs.net/docs/manual.html#_contributing + +WINDOWS USERS: + +I have not tested the build scripts on Windows, so you'll need to solve any +problems you encounter on your own. The easiest way is to use a source +tarball instead of git, as that way you don't need to build the UI yourself. +If you do want to use git, a user contributed the following, which should get +you most of the way there: + + 1) Install "git bash". + 2) In the tools directory, modify build_ui.sh. Locate the line that reads + "pyuic4 $i -o $py" and alter it to be of the following form: + "" "" $i -o $py + These two paths must point to your python executable, and to pyuic.py, on your + system. Typical paths would be: + = C:\\Python27\\python.exe + = C:\\Python27\\Lib\\site-packages\\PyQt4\\uic\\pyuic.py From 7134da4cd67f6b467b5974c5e83c6049e0ecac59 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 12 May 2014 15:45:21 +0900 Subject: [PATCH 088/109] enable houssam's fav code --- aqt/browser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aqt/browser.py b/aqt/browser.py index ae4f0439e..955e77855 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -367,6 +367,7 @@ class Browser(QMainWindow): self.setupEditor() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) + self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) self.form.searchEdit.setFocus() self.form.searchEdit.lineEdit().setText("is:current") self.form.searchEdit.lineEdit().selectAll() From 980c68b2b09b0d5c7df76593f666efe6f7bc2215 Mon Sep 17 00:00:00 2001 From: Joel Meyer-Hamme Date: Tue, 20 May 2014 19:08:54 +0200 Subject: [PATCH 089/109] Expose latex command tool chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dvi isn't compatible with quite a few Latex packages. I have been trying to include chemical formulas with the chemfig package. Exposing the tool chain to plugins would be very useful. For the record, I'm currently using: ´´´python latexCmds = [ ["pdflatex", "-interaction=nonstopmode", "tmp.tex"], ["pdflatex", "-interaction=nonstopmode", "tmp.tex"], ["pdfcrop", "tmp.pdf"], ["convert", "-density", "300", "tmp-crop.pdf", "tmp.png"] ] ´´´ --- anki/latex.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/anki/latex.py b/anki/latex.py index 0d447952b..d5d4711fb 100644 --- a/anki/latex.py +++ b/anki/latex.py @@ -7,8 +7,11 @@ from anki.utils import checksum, call, namedtmp, tmpdir, isMac, stripHTML from anki.hooks import addHook from anki.lang import _ -latexCmd = ["latex", "-interaction=nonstopmode"] -latexDviPngCmd = ["dvipng", "-D", "200", "-T", "tight"] +latexCmds = [ + ["latex", "-interaction=nonstopmode", "tmp.tex"], + ["dvipng", "-D", "200", "-T", "tight", "tmp.dvi", "-o", "tmp.png"] +] + build = True # if off, use existing media but don't create new regexps = { "standard": re.compile(r"\[latex\](.+?)\[/latex\]", re.DOTALL | re.IGNORECASE), @@ -89,14 +92,10 @@ package in the LaTeX header instead.""") % bad oldcwd = os.getcwd() png = namedtmp("tmp.png") try: - # generate dvi - os.chdir(tmpdir()) - if call(latexCmd + ["tmp.tex"], stdout=log, stderr=log): - return _errMsg("latex", texpath) - # and png - if call(latexDviPngCmd + ["tmp.dvi", "-o", "tmp.png"], - stdout=log, stderr=log): - return _errMsg("dvipng", texpath) + # generate png + for latexCmd in latexCmds: + if call(latexCmd, stdout=log, stderr=log): + return _errMsg(latexCmd[0], texpath) # add to media shutil.copyfile(png, os.path.join(mdir, fname)) return From a83769b258ef1c8fb310122aec3c19d605788c29 Mon Sep 17 00:00:00 2001 From: rubyu Date: Wed, 21 May 2014 14:52:43 +0900 Subject: [PATCH 090/109] Fixes an issue fields are not being escaped in doExport(). --- anki/exporting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/anki/exporting.py b/anki/exporting.py index 2cbdfc53e..f61871d6c 100644 --- a/anki/exporting.py +++ b/anki/exporting.py @@ -20,10 +20,12 @@ class Exporter(object): file.close() def escapeText(self, text): - "Escape newlines, tabs and CSS." + "Escape newlines, tabs, CSS and quotechar." text = text.replace("\n", "
") text = text.replace("\t", " " * 8) text = re.sub("(?i)", "", text) + if "\"" in text: + text = "\"" + text.replace("\"", "\"\"") + "\"" return text def cardIds(self): From 07426a883cbe72fd4a8a7090d223128b7d1ebde2 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Thu, 22 May 2014 21:53:35 +1000 Subject: [PATCH 091/109] Replace search lineEdit before we connect signals. Avoids overriding the setup work we do on it. --- aqt/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/browser.py b/aqt/browser.py index 955e77855..d997ff190 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -367,7 +367,6 @@ class Browser(QMainWindow): self.setupEditor() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) - self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) self.form.searchEdit.setFocus() self.form.searchEdit.lineEdit().setText("is:current") self.form.searchEdit.lineEdit().selectAll() @@ -505,6 +504,7 @@ class Browser(QMainWindow): def setupSearch(self): self.filterTimer = None + self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) self.connect(self.form.searchButton, SIGNAL("clicked()"), self.onSearch) From 96a294039ef6279674f1495de54edc710397fff7 Mon Sep 17 00:00:00 2001 From: Houssam Salem Date: Sun, 25 May 2014 11:06:30 +1000 Subject: [PATCH 092/109] Don't manually specify tab order. The default order is more logical: Text input -> Search -> Preview -> Results table --- aqt/browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aqt/browser.py b/aqt/browser.py index d997ff190..dd1dde5db 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -511,7 +511,6 @@ class Browser(QMainWindow): self.connect(self.form.searchEdit.lineEdit(), SIGNAL("returnPressed()"), self.onSearch) - self.setTabOrder(self.form.searchEdit, self.form.tableView) self.form.searchEdit.setCompleter(None) self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory']) self.connect(self.form.searchEdit.lineEdit(), From cda86307c0a459966f272526387a01d3ee16e205 Mon Sep 17 00:00:00 2001 From: dae Date: Sun, 25 May 2014 14:55:29 +0900 Subject: [PATCH 093/109] round cards/minute (thanks to Markus) --- anki/stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/stats.py b/anki/stats.py index 197e941a9..7a93dbadd 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -353,7 +353,7 @@ group by day order by day""" % (self._limit(), lim), tot, period, unit)) if total and tot: perMin = total / float(tot) - perMin = ngettext("%d card/minute", "%d cards/minute", perMin) % perMin + perMin = ngettext("%d card/minute", "%d cards/minute", perMin) % round(perMin) self._line( i, _("Average answer time"), _("%(a)0.1fs (%(b)s)") % dict(a=(tot*60)/total, b=perMin)) From 8dd88c5f30df18ee01cfc933fed79ea1e2b4a310 Mon Sep 17 00:00:00 2001 From: dae Date: Sun, 25 May 2014 20:02:24 +0900 Subject: [PATCH 094/109] add m4a to usable formats --- aqt/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/editor.py b/aqt/editor.py index 755bfbd7e..bd509338c 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -21,7 +21,7 @@ import anki.js from BeautifulSoup import BeautifulSoup pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg") -audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv") +audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a") _html = """ %s