Merge branch 'master' of github.com:dae/anki

This commit is contained in:
dae 2014-06-26 08:58:22 +09:00
commit 1933779fa6
18 changed files with 125 additions and 41 deletions

View file

@ -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) ]

View file

@ -132,6 +132,6 @@ class TextImporter(NoteImporter):
def noteFromFields(self, fields):
note = ForeignNote()
note.fields.extend([x.strip().replace("\n", "<br>") for x in fields])
note.fields.extend([x for x in fields])
note.tags.extend(self.tagsToAdd)
return note

View file

@ -117,10 +117,13 @@ class NoteImporter(Importer):
self._ids = []
self._cards = []
self._emptyNotes = False
dupeCount = 0
dupes = []
for n in notes:
if not self.allowHTML:
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", "<br>")
fld0 = n.fields[fld0idx]
csum = fieldChecksum(fld0)
# first field must exist
@ -151,11 +154,18 @@ 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
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:
@ -183,9 +193,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 +233,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):

View file

@ -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 = []

View file

@ -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/")

View file

@ -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
@ -1321,7 +1329,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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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 \
@ -379,7 +379,11 @@ def replaceWithApkg(mw, file, backup):
mw.unloadCollection()
# overwrite collection
z = zipfile.ZipFile(file)
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

View file

@ -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
@ -764,9 +764,9 @@ title="%s">%s</button>''' % (
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
##########################################################################
@ -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(
@ -980,6 +982,7 @@ will be lost. Continue?"""))
mdir = self.col.media.dir()
for f in unused:
path = os.path.join(mdir, f)
if os.path.exists(path):
send2trash(path)
tooltip(_("Deleted."))
diag.close()
@ -1003,10 +1006,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)

View file

@ -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())

View file

@ -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

View file

@ -106,14 +106,19 @@ class Reviewer(object):
# Audio
##########################################################################
def replayAudio(self):
clearAudioQueue()
def replayAudio(self, previewer=None):
if previewer:
state = previewer._previewState
c = previewer.card
else:
state = self.state
c = self.card
if self.state == "question":
clearAudioQueue()
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(

View file

@ -61,9 +61,14 @@ class DeckStats(QDialog):
painter = QPainter(image)
p.mainFrame().render(painter)
painter.end()
image.save(path, "png")
p.setViewportSize(oldsize)
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)
def changePeriod(self, n):
self.period = n

View file

@ -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()

View file

@ -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"