From 6771af40b1d3d72d5a2b1ea3cc54f56ba93439eb Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Tue, 17 Jun 2014 08:31:38 -0500 Subject: [PATCH 01/13] fix error handling for selecting invalid file type (The error was never appearing because the test was missing quotation marks.) --- aqt/importing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aqt/importing.py b/aqt/importing.py index ef99958c8..fce17ccff 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -295,7 +295,7 @@ def importFile(mw, file): return except Exception, e: msg = repr(str(e)) - if msg == "unknownFormat": + if msg == "'unknownFormat'": if file.endswith(".anki2"): showWarning(_("""\ .anki2 files are not designed for importing. If you're trying to restore from a \ From 4de023ecba010ce986af49592674b5956239d3e7 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Tue, 17 Jun 2014 08:44:26 -0500 Subject: [PATCH 02/13] check that save to desktop was successful before showing message --- aqt/stats.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aqt/stats.py b/aqt/stats.py index 93bf6601a..40f062f21 100644 --- a/aqt/stats.py +++ b/aqt/stats.py @@ -61,9 +61,14 @@ class DeckStats(QDialog): painter = QPainter(image) p.mainFrame().render(painter) painter.end() - image.save(path, "png") + isOK = image.save(path, "png") + if isOK: + showInfo(_("An image was saved to your desktop.")) + else: + showInfo(_("""\ +Anki could not save the image. Please check that you have permission to write \ +to your desktop.""")) p.setViewportSize(oldsize) - showInfo(_("An image was saved to your desktop.")) def changePeriod(self, n): self.period = n From 8620b81817a72c37526e6556fe5f0c7b3ae5f126 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Tue, 17 Jun 2014 09:35:30 -0500 Subject: [PATCH 03/13] add unchanged count to import log --- anki/importing/noteimp.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/anki/importing/noteimp.py b/anki/importing/noteimp.py index 462fb662f..c5669ffc6 100644 --- a/anki/importing/noteimp.py +++ b/anki/importing/noteimp.py @@ -117,6 +117,7 @@ class NoteImporter(Importer): self._ids = [] self._cards = [] self._emptyNotes = False + dupeCount = 0 for n in notes: if not self.allowHTML: for c in range(len(n.fields)): @@ -151,8 +152,11 @@ class NoteImporter(Importer): if data: updates.append(data) updateLog.append(updateLogTxt % fld0) + dupeCount += 1 found = True break + elif self.importMode == 1: + dupeCount += 1 elif self.importMode == 2: # allow duplicates in this case updateLog.append(dupeLogTxt % fld0) @@ -183,9 +187,19 @@ class NoteImporter(Importer): self.col.sched.randomizeCards(did) else: self.col.sched.orderCards(did) + part1 = ngettext("%d note added", "%d notes added", len(new)) % len(new) - part2 = ngettext("%d note updated", "%d notes updated", self.updateCount) % self.updateCount - self.log.append("%s, %s." % (part1, part2)) + part2 = ngettext("%d note updated", "%d notes updated", + self.updateCount) % self.updateCount + if self.importMode == 0: + unchanged = dupeCount - self.updateCount + elif self.importMode == 1: + unchanged = dupeCount + else: + unchanged = 0 + part3 = ngettext("%d note unchanged", "%d notes unchanged", + unchanged) % unchanged + self.log.append("%s, %s, %s." % (part1, part2, part3)) self.log.extend(updateLog) if self._emptyNotes: self.log.append(_("""\ @@ -213,7 +227,6 @@ content in the text file to the correct fields.""")) "insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows) - # need to document that deck is ignored in this case def updateData(self, n, id, sflds): self._ids.append(id) if not self.processFields(n, sflds): From ebcab2122dae3d2d19100f392262de6c17df8999 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Tue, 17 Jun 2014 16:55:55 -0500 Subject: [PATCH 04/13] more helpful error message for corrupt collection package --- aqt/importing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aqt/importing.py b/aqt/importing.py index fce17ccff..5e7d90511 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -379,7 +379,11 @@ def replaceWithApkg(mw, file, backup): mw.unloadCollection() # overwrite collection z = zipfile.ZipFile(file) - z.extract("collection.anki2", mw.pm.profileFolder()) + try: + z.extract("collection.anki2", mw.pm.profileFolder()) + except: + showWarning(_("The provided file is not a valid .apkg file.")) + return # because users don't have a backup of media, it's safer to import new # data and rely on them running a media db check to get rid of any # unwanted media. in the future we might also want to deduplicate this From 5e74976361b329abaf87bb8c74474e636f6337b5 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Wed, 18 Jun 2014 16:26:49 -0500 Subject: [PATCH 05/13] prevent nesting things under filtered decks This change keeps drag-and-drop behavior the same except for displaying an error if an otherwise acceptable move would nest anything underneath a filtered deck. --- anki/decks.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/anki/decks.py b/anki/decks.py index 180eb4200..b7940abda 100644 --- a/anki/decks.py +++ b/anki/decks.py @@ -262,9 +262,14 @@ class DeckManager(object): self.rename(draggedDeck, ontoDeckName + "::" + self._basename(draggedDeckName)) def _canDragAndDrop(self, draggedDeckName, ontoDeckName): - return draggedDeckName <> ontoDeckName \ - and not self._isParent(ontoDeckName, draggedDeckName) \ - and not self._isAncestor(draggedDeckName, ontoDeckName) + if draggedDeckName == ontoDeckName \ + or self._isParent(ontoDeckName, draggedDeckName) \ + or self._isAncestor(draggedDeckName, ontoDeckName): + return False + elif self.byName(ontoDeckName)['dyn']: + raise DeckRenameError(_("A filtered deck cannot have subdecks.")) + else: + return True def _isParent(self, parentDeckName, childDeckName): return self._path(childDeckName) == self._path(parentDeckName) + [ self._basename(childDeckName) ] From 28bb109dedf187208c5977733e3b8f2700e28a34 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Thu, 19 Jun 2014 17:19:56 -0500 Subject: [PATCH 06/13] only display "added duplicate" message once Even if there are several (duplicate) notes in the collection that match, only send the message once to avoid making it look like multiple dupes have been added. --- anki/importing/noteimp.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/anki/importing/noteimp.py b/anki/importing/noteimp.py index c5669ffc6..fd3a7877a 100644 --- a/anki/importing/noteimp.py +++ b/anki/importing/noteimp.py @@ -118,6 +118,7 @@ class NoteImporter(Importer): self._cards = [] self._emptyNotes = False dupeCount = 0 + dupes = [] for n in notes: if not self.allowHTML: for c in range(len(n.fields)): @@ -159,7 +160,11 @@ class NoteImporter(Importer): dupeCount += 1 elif self.importMode == 2: # allow duplicates in this case - updateLog.append(dupeLogTxt % fld0) + if fld0 not in dupes: + # only show message once, no matter how many + # duplicates are in the collection already + updateLog.append(dupeLogTxt % fld0) + dupes.append(fld0) found = False # newly add if not found: From b4784b7437e982c71473ab4d2fa0338dab8b096f Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Thu, 19 Jun 2014 19:13:12 -0500 Subject: [PATCH 07/13] add "export" button to deck menus --- aqt/deckbrowser.py | 5 +++++ aqt/exporting.py | 11 ++++++++--- aqt/main.py | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 74ad1b0a3..ca13c78b3 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -268,10 +268,15 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000) a.connect(a, SIGNAL("triggered()"), lambda did=did: self._rename(did)) a = m.addAction(_("Options")) a.connect(a, SIGNAL("triggered()"), lambda did=did: self._options(did)) + a = m.addAction(_("Export")) + a.connect(a, SIGNAL("triggered()"), lambda did=did: self._export(did)) a = m.addAction(_("Delete")) a.connect(a, SIGNAL("triggered()"), lambda did=did: self._delete(did)) m.exec_(QCursor.pos()) + def _export(self, did): + self.mw.onExport(did=did) + def _rename(self, did): self.mw.checkpoint(_("Rename Deck")) deck = self.mw.col.decks.get(did) diff --git a/aqt/exporting.py b/aqt/exporting.py index 7f4ee90c4..cb5bdd1ae 100644 --- a/aqt/exporting.py +++ b/aqt/exporting.py @@ -12,17 +12,17 @@ from anki.exporting import exporters class ExportDialog(QDialog): - def __init__(self, mw): + def __init__(self, mw, did=None): QDialog.__init__(self, mw, Qt.Window) self.mw = mw self.col = mw.col self.frm = aqt.forms.exporting.Ui_ExportDialog() self.frm.setupUi(self) self.exporter = None - self.setup() + self.setup(did) self.exec_() - def setup(self): + def setup(self, did): self.frm.format.insertItems(0, list(zip(*exporters())[0])) self.connect(self.frm.format, SIGNAL("activated(int)"), self.exporterChanged) @@ -32,6 +32,11 @@ class ExportDialog(QDialog): # save button b = QPushButton(_("Export...")) self.frm.buttonBox.addButton(b, QDialogButtonBox.AcceptRole) + # set default option if accessed through deck button + if did: + name = self.mw.col.decks.get(did)['name'] + index = self.frm.deck.findText(name) + self.frm.deck.setCurrentIndex(index) def exporterChanged(self, idx): self.exporter = exporters()[idx][1](self.col) diff --git a/aqt/main.py b/aqt/main.py index 05c28c684..126912861 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -764,9 +764,9 @@ title="%s">%s''' % ( import aqt.importing aqt.importing.onImport(self) - def onExport(self): + def onExport(self, did=None): import aqt.exporting - aqt.exporting.ExportDialog(self) + aqt.exporting.ExportDialog(self, did=did) # Cramming ########################################################################## From 50e0d0b8116e49c9d07ff90ff697c6c1c6427bcf Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Fri, 20 Jun 2014 10:00:44 -0500 Subject: [PATCH 08/13] better message when Anki can't create a profile folder --- aqt/profiles.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aqt/profiles.py b/aqt/profiles.py index 4503240d6..a7472947c 100644 --- a/aqt/profiles.py +++ b/aqt/profiles.py @@ -88,8 +88,10 @@ class ProfileManager(object): # can't translate, as lang not initialized QMessageBox.critical( None, "Error", """\ -Anki can't write to the harddisk. Please see the \ -documentation for information on using a flash drive.""") +Anki could not create the folder %s. Please ensure that location is not \ +read-only and you have permission to write to it. If you cannot fix this \ +issue, please see the documentation for information on running Anki from \ +a flash drive.""" % self.base) raise # Profile load/save From 436f257e36e319749efac50c82bc123287f936c7 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Wed, 18 Jun 2014 13:47:45 -0500 Subject: [PATCH 09/13] dialog box size and position improvements - keep track of size and position for a number of dialogs - make sure addons dialog opens at an appropriate size for the system font size - add optional argument to showText to restore geom on creation and save on reject (other buttons, if used, need to be programmed to save individually) --- aqt/addons.py | 4 +++- aqt/browser.py | 5 ++++- aqt/deckconf.py | 5 +++-- aqt/dyndeckconf.py | 4 +++- aqt/main.py | 8 ++++++-- aqt/models.py | 2 ++ aqt/studydeck.py | 1 + aqt/utils.py | 15 ++++++++++++--- 8 files changed, 34 insertions(+), 10 deletions(-) diff --git a/aqt/addons.py b/aqt/addons.py index 4e8f6cd7a..47279c503 100644 --- a/aqt/addons.py +++ b/aqt/addons.py @@ -6,7 +6,7 @@ import sys, os, traceback from cStringIO import StringIO from aqt.qt import * from aqt.utils import showInfo, openFolder, isWin, openLink, \ - askUser + askUser, restoreGeom, saveGeom from zipfile import ZipFile import aqt.forms import aqt @@ -141,7 +141,9 @@ class GetAddons(QDialog): b = self.form.buttonBox.addButton( _("Browse"), QDialogButtonBox.ActionRole) self.connect(b, SIGNAL("clicked()"), self.onBrowse) + restoreGeom(self, "getaddons", adjustSize=True) self.exec_() + saveGeom(self, "getaddons") def onBrowse(self): openLink(aqt.appShared + "addons/") diff --git a/aqt/browser.py b/aqt/browser.py index 8a4efd980..d18916568 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -1321,7 +1321,10 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, frm.field.addItems([_("All Fields")] + fields) self.connect(frm.buttonBox, SIGNAL("helpRequested()"), self.onFindReplaceHelp) - if not d.exec_(): + restoreGeom(d, "findreplace") + r = d.exec_() + saveGeom(d, "findreplace") + if not r: return if frm.field.currentIndex() == 0: field = None diff --git a/aqt/deckconf.py b/aqt/deckconf.py index 3404bc703..0e71b6b07 100644 --- a/aqt/deckconf.py +++ b/aqt/deckconf.py @@ -7,7 +7,7 @@ from anki.consts import NEW_CARDS_RANDOM from aqt.qt import * import aqt from aqt.utils import showInfo, showWarning, openHelp, getOnlyText, askUser, \ - tooltip + tooltip, saveGeom, restoreGeom class DeckConf(QDialog): def __init__(self, mw, deck): @@ -33,9 +33,10 @@ class DeckConf(QDialog): self.onRestore) self.setWindowTitle(_("Options for %s") % self.deck['name']) # qt doesn't size properly with altered fonts otherwise + restoreGeom(self, "deckconf", adjustSize=True) self.show() - self.adjustSize() self.exec_() + saveGeom(self, "deckconf") def setupCombos(self): import anki.consts as cs diff --git a/aqt/dyndeckconf.py b/aqt/dyndeckconf.py index 1f3f0f838..1b782e44a 100644 --- a/aqt/dyndeckconf.py +++ b/aqt/dyndeckconf.py @@ -4,7 +4,7 @@ from aqt.qt import * import aqt -from aqt.utils import showWarning, openHelp, askUser +from aqt.utils import showWarning, openHelp, askUser, saveGeom, restoreGeom class DeckConf(QDialog): def __init__(self, mw, first=False, search="", deck=None): @@ -26,6 +26,7 @@ class DeckConf(QDialog): SIGNAL("helpRequested()"), lambda: openHelp("filtered")) self.setWindowTitle(_("Options for %s") % self.deck['name']) + restoreGeom(self, "dyndeckconf") self.setupOrder() self.loadConf() if search: @@ -33,6 +34,7 @@ class DeckConf(QDialog): self.form.search.selectAll() self.show() self.exec_() + saveGeom(self, "dyndeckconf") def setupOrder(self): import anki.consts as cs diff --git a/aqt/main.py b/aqt/main.py index 05c28c684..1028cc5de 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -16,7 +16,7 @@ import aqt.progress import aqt.webview import aqt.toolbar import aqt.stats -from aqt.utils import restoreGeom, showInfo, showWarning,\ +from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \ restoreState, getOnlyText, askUser, applyStyles, showText, tooltip, \ openHelp, openLink, checkInvalidFilename import anki.db @@ -971,7 +971,9 @@ will be lost. Continue?""")) diag.connect(box, SIGNAL("rejected()"), diag, SLOT("reject()")) diag.setMinimumHeight(400) diag.setMinimumWidth(500) + restoreGeom(diag, "checkmediadb") diag.exec_() + saveGeom(diag, "checkmediadb") def deleteUnused(self, unused, diag): if not askUser( @@ -1003,10 +1005,12 @@ will be lost. Continue?""")) self.progress.finish() part1 = ngettext("%d card", "%d cards", len(cids)) % len(cids) part1 = _("%s to delete:") % part1 - diag, box = showText(part1 + "\n\n" + report, run=False) + diag, box = showText(part1 + "\n\n" + report, run=False, + geomKey="emptyCards") box.addButton(_("Delete Cards"), QDialogButtonBox.AcceptRole) box.button(QDialogButtonBox.Close).setDefault(True) def onDelete(): + saveGeom(diag, "emptyCards") QDialog.accept(diag) self.checkpoint(_("Delete Empty")) self.col.remCards(cids) diff --git a/aqt/models.py b/aqt/models.py index e09729922..a3c71afa9 100644 --- a/aqt/models.py +++ b/aqt/models.py @@ -115,7 +115,9 @@ class Models(QDialog): self.connect( frm.buttonBox, SIGNAL("helpRequested()"), lambda: openHelp("latex")) + restoreGeom(d, "modelopts") d.exec_() + saveGeom(d, "modelopts") self.model['latexPre'] = unicode(frm.latexHeader.toPlainText()) self.model['latexPost'] = unicode(frm.latexFooter.toPlainText()) diff --git a/aqt/studydeck.py b/aqt/studydeck.py index 81a96e477..636d6c4e1 100644 --- a/aqt/studydeck.py +++ b/aqt/studydeck.py @@ -118,6 +118,7 @@ class StudyDeck(QDialog): QDialog.accept(self) def reject(self): + saveGeom(self, self.geomKey) remHook('reset', self.onReset) if not self.cancel: return self.accept() diff --git a/aqt/utils.py b/aqt/utils.py index 88ada7067..21312b232 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -47,7 +47,7 @@ def showInfo(text, parent=False, help="", type="info"): b.setAutoDefault(False) return mb.exec_() -def showText(txt, parent=None, type="text", run=True): +def showText(txt, parent=None, type="text", run=True, geomKey=None): if not parent: parent = aqt.mw.app.activeWindow() or aqt.mw diag = QDialog(parent) @@ -63,9 +63,15 @@ def showText(txt, parent=None, type="text", run=True): layout.addWidget(text) box = QDialogButtonBox(QDialogButtonBox.Close) layout.addWidget(box) - diag.connect(box, SIGNAL("rejected()"), diag, SLOT("reject()")) + def onReject(): + if geomKey: + saveGeom(diag, geomKey) + QDialog.reject(diag) + diag.connect(box, SIGNAL("rejected()"), onReject) diag.setMinimumHeight(400) diag.setMinimumWidth(500) + if geomKey: + restoreGeom(diag, geomKey) if run: diag.exec_() else: @@ -280,7 +286,7 @@ def saveGeom(widget, key): key += "Geom" aqt.mw.pm.profile[key] = widget.saveGeometry() -def restoreGeom(widget, key, offset=None): +def restoreGeom(widget, key, offset=None, adjustSize=False): key += "Geom" if aqt.mw.pm.profile.get(key): widget.restoreGeometry(aqt.mw.pm.profile[key]) @@ -289,6 +295,9 @@ def restoreGeom(widget, key, offset=None): # bug in osx toolkit s = widget.size() widget.resize(s.width(), s.height()+offset*2) + else: + if adjustSize: + widget.adjustSize() def saveState(widget, key): key += "State" From 71ff86e3f3230f870d166f56b6a353ecd150c4f0 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Fri, 20 Jun 2014 19:23:53 -0500 Subject: [PATCH 10/13] change "suspended" label to "suspended+buried" --- anki/stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anki/stats.py b/anki/stats.py index 7a93dbadd..681157785 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -655,7 +655,7 @@ group by hour having count() > 30 order by hour""" % lim, (_("Mature"), colMature), (_("Young+Learn"), colYoung), (_("Unseen"), colUnseen), - (_("Suspended"), colSusp))): + (_("Suspended+Buried"), colSusp))): d.append(dict(data=div[c], label="%s: %s" % (t, div[c]), color=col)) # text data i = [] From 5b61db7d0adc264fd82af8f462d72cf0d1f15e58 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Fri, 20 Jun 2014 19:41:56 -0500 Subject: [PATCH 11/13] make sure unused media exist before deleting them --- aqt/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aqt/main.py b/aqt/main.py index 0e27b1271..7e8836510 100644 --- a/aqt/main.py +++ b/aqt/main.py @@ -982,7 +982,8 @@ will be lost. Continue?""")) mdir = self.col.media.dir() for f in unused: path = os.path.join(mdir, f) - send2trash(path) + if os.path.exists(path): + send2trash(path) tooltip(_("Deleted.")) diag.close() From ba084cb46adde67e3639712386cab4ac9772c8f0 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Sat, 21 Jun 2014 11:02:34 -0500 Subject: [PATCH 12/13] don't insert
into text of cards on import When "allow HTML" was turned off, Anki was replacing newlines with
s in the text of the new notes before it escaped HTML characters, so the line breaks were becoming <br>. --- anki/importing/csvfile.py | 2 +- anki/importing/noteimp.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/anki/importing/csvfile.py b/anki/importing/csvfile.py index bd0b1ff4b..612cdd27a 100644 --- a/anki/importing/csvfile.py +++ b/anki/importing/csvfile.py @@ -132,6 +132,6 @@ class TextImporter(NoteImporter): def noteFromFields(self, fields): note = ForeignNote() - note.fields.extend([x.strip().replace("\n", "
") for x in fields]) + note.fields.extend([x for x in fields]) note.tags.extend(self.tagsToAdd) return note diff --git a/anki/importing/noteimp.py b/anki/importing/noteimp.py index fd3a7877a..e9e435d67 100644 --- a/anki/importing/noteimp.py +++ b/anki/importing/noteimp.py @@ -120,9 +120,10 @@ class NoteImporter(Importer): dupeCount = 0 dupes = [] for n in notes: - if not self.allowHTML: - for c in range(len(n.fields)): + for c in range(len(n.fields)): + if not self.allowHTML: n.fields[c] = cgi.escape(n.fields[c]) + n.fields[c] = n.fields[c].strip().replace("\n", "
") fld0 = n.fields[fld0idx] csum = fieldChecksum(fld0) # first field must exist From d659d9cff763659337abc048f854d75a41240034 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Sat, 21 Jun 2014 15:35:45 -0500 Subject: [PATCH 13/13] add 'replay audio' button to previewer --- aqt/browser.py | 8 ++++++++ aqt/reviewer.py | 22 ++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/aqt/browser.py b/aqt/browser.py index d18916568..e43a58a60 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -1015,6 +1015,10 @@ where id in %s""" % ids2str(sf)) self._previewWeb = AnkiWebView(True) vbox.addWidget(self._previewWeb) bbox = QDialogButtonBox() + self._previewReplay = bbox.addButton(_("Replay Audio"), QDialogButtonBox.ActionRole) + self._previewReplay.setAutoDefault(False) + self._previewReplay.setShortcut(QKeySequence("R")) + self._previewReplay.setToolTip(_("Shortcut key: %s" % "R")) self._previewPrev = bbox.addButton("<", QDialogButtonBox.ActionRole) self._previewPrev.setAutoDefault(False) self._previewPrev.setShortcut(QKeySequence("Left")) @@ -1023,6 +1027,7 @@ where id in %s""" % ids2str(sf)) self._previewNext.setShortcut(QKeySequence("Right")) c(self._previewPrev, SIGNAL("clicked()"), self._onPreviewPrev) c(self._previewNext, SIGNAL("clicked()"), self._onPreviewNext) + c(self._previewReplay, SIGNAL("clicked()"), self._onReplayAudio) vbox.addWidget(bbox) self._previewWindow.setLayout(vbox) restoreGeom(self._previewWindow, "preview") @@ -1050,6 +1055,9 @@ where id in %s""" % ids2str(sf)) self.onNextCard() self._updatePreviewButtons() + def _onReplayAudio(self): + self.mw.reviewer.replayAudio(self) + def _updatePreviewButtons(self): if not self._previewWindow: return diff --git a/aqt/reviewer.py b/aqt/reviewer.py index c00bbf76c..40a5dcdc9 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -106,14 +106,19 @@ class Reviewer(object): # Audio ########################################################################## - def replayAudio(self): + def replayAudio(self, previewer=None): + if previewer: + state = previewer._previewState + c = previewer.card + else: + state = self.state + c = self.card clearAudioQueue() - c = self.card - if self.state == "question": + if state == "question": playFromText(c.q()) - elif self.state == "answer": + elif state == "answer": txt = "" - if self._replayq(c): + if self._replayq(c, previewer): txt = c.q() txt += c.a() playFromText(txt) @@ -218,9 +223,10 @@ The front of this card is empty. Please run Tools>Empty Cards.""") return self.mw.col.decks.confForDid( card.odid or card.did)['autoplay'] - def _replayq(self, card): - return self.mw.col.decks.confForDid( - self.card.odid or self.card.did).get('replayq', True) + def _replayq(self, card, previewer=None): + s = previewer if previewer else self + return s.mw.col.decks.confForDid( + s.card.odid or s.card.did).get('replayq', True) def _toggleStar(self): self.web.eval("_toggleStar(%s);" % json.dumps(