mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Merge branch 'master' of github.com:dae/anki
This commit is contained in:
commit
1933779fa6
18 changed files with 125 additions and 41 deletions
|
@ -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) ]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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/")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
13
aqt/main.py
13
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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
15
aqt/utils.py
15
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"
|
||||
|
|
Loading…
Reference in a new issue