mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00
refactor; add profile support
This commit is contained in:
parent
87da9f48b1
commit
f4150a5df4
25 changed files with 532 additions and 1066 deletions
|
@ -9,13 +9,13 @@ appVersion="1.99"
|
||||||
appWebsite="http://ankisrs.net/"
|
appWebsite="http://ankisrs.net/"
|
||||||
appHelpSite="http://ankisrs.net/docs/dev/"
|
appHelpSite="http://ankisrs.net/docs/dev/"
|
||||||
appDonate="http://ankisrs.net/support/"
|
appDonate="http://ankisrs.net/support/"
|
||||||
modDir=os.path.dirname(os.path.abspath(__file__))
|
|
||||||
runningDir=os.path.split(modDir)[0]
|
|
||||||
mw = None # set on init
|
mw = None # set on init
|
||||||
|
|
||||||
|
moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
|
||||||
|
|
||||||
# py2exe
|
# py2exe
|
||||||
if hasattr(sys, "frozen"):
|
# if hasattr(sys, "frozen"):
|
||||||
sys.path.append(modDir)
|
# sys.path.append(moduleDir)
|
||||||
modDir = os.path.dirname(sys.argv[0])
|
|
||||||
|
|
||||||
def openHelp(name):
|
def openHelp(name):
|
||||||
if "#" in name:
|
if "#" in name:
|
||||||
|
@ -60,46 +60,6 @@ class DialogManager(object):
|
||||||
|
|
||||||
dialogs = DialogManager()
|
dialogs = DialogManager()
|
||||||
|
|
||||||
# Splash screen
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
class SplashScreen(object):
|
|
||||||
|
|
||||||
def __init__(self, max=100):
|
|
||||||
self.finished = False
|
|
||||||
self.pixmap = QPixmap(":/icons/anki-logo.png")
|
|
||||||
self.splash = QSplashScreen(self.pixmap)
|
|
||||||
self.prog = QProgressBar(self.splash)
|
|
||||||
self.prog.setMaximum(max)
|
|
||||||
if QApplication.instance().style().objectName() != "plastique":
|
|
||||||
self.style = QStyleFactory.create("plastique")
|
|
||||||
self.prog.setStyle(self.style)
|
|
||||||
self.prog.setStyleSheet("""* {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: #061824;
|
|
||||||
margin:0px;
|
|
||||||
border:0px;
|
|
||||||
padding: 0px;
|
|
||||||
text-align: center;}
|
|
||||||
*::chunk {
|
|
||||||
color: #13486c;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
x = 8
|
|
||||||
self.prog.setGeometry(self.splash.width()/10, 8.85*self.splash.height()/10,
|
|
||||||
x*self.splash.width()/10, self.splash.height()/10)
|
|
||||||
self.splash.show()
|
|
||||||
self.val = 1
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
self.prog.setValue(self.val)
|
|
||||||
self.val += 1
|
|
||||||
QApplication.instance().processEvents()
|
|
||||||
|
|
||||||
def finish(self, obj):
|
|
||||||
self.splash.finish(obj)
|
|
||||||
self.finished = True
|
|
||||||
|
|
||||||
# App initialisation
|
# App initialisation
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -116,52 +76,26 @@ def run():
|
||||||
global mw
|
global mw
|
||||||
from anki.utils import isWin, isMac
|
from anki.utils import isWin, isMac
|
||||||
|
|
||||||
# home on win32 is broken
|
|
||||||
mustQuit = False
|
|
||||||
if isWin:
|
|
||||||
# use appdata if available
|
|
||||||
if 'APPDATA' in os.environ:
|
|
||||||
os.environ['HOME'] = os.environ['APPDATA']
|
|
||||||
else:
|
|
||||||
mustQuit = True
|
|
||||||
# make and check accessible
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.expanduser("~/.anki"))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
os.listdir(os.path.expanduser("~/.anki"))
|
|
||||||
except:
|
|
||||||
mustQuit = True
|
|
||||||
|
|
||||||
# on osx we'll need to add the qt plugins to the search path
|
# on osx we'll need to add the qt plugins to the search path
|
||||||
rd = runningDir
|
|
||||||
if isMac and getattr(sys, 'frozen', None):
|
if isMac and getattr(sys, 'frozen', None):
|
||||||
rd = os.path.abspath(runningDir + "/../../..")
|
rd = os.path.abspath(moduleDir + "/../../..")
|
||||||
QCoreApplication.setLibraryPaths([rd])
|
QCoreApplication.setLibraryPaths([rd])
|
||||||
|
|
||||||
# create the app
|
# create the app
|
||||||
app = AnkiApp(sys.argv)
|
app = AnkiApp(sys.argv)
|
||||||
QCoreApplication.setApplicationName("Anki")
|
QCoreApplication.setApplicationName("Anki")
|
||||||
if mustQuit:
|
|
||||||
QMessageBox.warning(
|
|
||||||
None, "Anki", "Can't open APPDATA, nor c:\\anki.\n"
|
|
||||||
"Please try removing foreign characters from your username.")
|
|
||||||
sys.exit(1)
|
|
||||||
splash = SplashScreen(3)
|
|
||||||
|
|
||||||
# parse args
|
# parse args
|
||||||
import optparse
|
import optparse
|
||||||
parser = optparse.OptionParser()
|
parser = optparse.OptionParser()
|
||||||
parser.usage = "%prog [<deck.anki>]"
|
parser.usage = "%prog [OPTIONS]"
|
||||||
parser.add_option("-c", "--config", help="path to config dir",
|
parser.add_option("-b", "--base", help="Path to base folder")
|
||||||
default=os.path.expanduser("~/.anki"))
|
parser.add_option("-p", "--profile", help="Profile name to load")
|
||||||
(opts, args) = parser.parse_args(sys.argv[1:])
|
(opts, args) = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
# setup config
|
# profile manager
|
||||||
import aqt.config
|
from aqt.profiles import ProfileManager
|
||||||
conf = aqt.config.Config(
|
pm = ProfileManager(opts.base, opts.profile)
|
||||||
unicode(os.path.abspath(opts.config), sys.getfilesystemencoding()))
|
|
||||||
|
|
||||||
# qt translations
|
# qt translations
|
||||||
translationPath = ''
|
translationPath = ''
|
||||||
|
@ -175,10 +109,8 @@ def run():
|
||||||
qtTranslator.load("qt_" + short, translationPath):
|
qtTranslator.load("qt_" + short, translationPath):
|
||||||
app.installTranslator(qtTranslator)
|
app.installTranslator(qtTranslator)
|
||||||
|
|
||||||
# load main window
|
|
||||||
splash.update()
|
|
||||||
import aqt.main
|
import aqt.main
|
||||||
mw = aqt.main.AnkiQt(app, conf, args, splash)
|
mw = aqt.main.AnkiQt(app, pm)
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -19,7 +19,7 @@ system. It's free and open source.")
|
||||||
abouttext += '<p>' + _("Written by Damien Elmes, with patches, translation,\
|
abouttext += '<p>' + _("Written by Damien Elmes, with patches, translation,\
|
||||||
testing and design from:<p>%(cont)s") % {'cont': u"""
|
testing and design from:<p>%(cont)s") % {'cont': u"""
|
||||||
|
|
||||||
Alex Fraser, Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, Charlene
|
Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, Charlene
|
||||||
Barina, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen, Emilio
|
Barina, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen, Emilio
|
||||||
Wuerges, Emmanuel Jarri, Frank Harper, H. Mijail, Ian Lewis, Iroiro, Jin
|
Wuerges, Emmanuel Jarri, Frank Harper, H. Mijail, Ian Lewis, Iroiro, Jin
|
||||||
Eun-Deok, Jarvik7, Jo Nakashima, Christian Krause, LaC, Laurent Steffan, Marco
|
Eun-Deok, Jarvik7, Jo Nakashima, Christian Krause, LaC, Laurent Steffan, Marco
|
||||||
|
@ -30,6 +30,9 @@ Petr Michalec, Piotr Kubowicz, Richard Colley, Samson Melamed, Stefaan
|
||||||
De Pooter, Susanna Björverud, Tacutu, Timm Preetz, Timo Paulssen, Ursus, Victor
|
De Pooter, Susanna Björverud, Tacutu, Timm Preetz, Timo Paulssen, Ursus, Victor
|
||||||
Suba, and Xtru.
|
Suba, and Xtru.
|
||||||
|
|
||||||
|
Anki icon by Alex Fraser (CC GNU GPL)
|
||||||
|
Deck icon by Laurent Baumann (CC BY-NC-SA 3.0)
|
||||||
|
Other icons under LGPL or public domain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
|
|
||||||
from aqt.qt import *
|
|
||||||
import aqt
|
|
||||||
from anki.utils import parseTags, joinTags, canonifyTags
|
|
||||||
from aqt.ui.utils import saveGeom, restoreGeom
|
|
||||||
|
|
||||||
class ActiveTagsChooser(QDialog):
|
|
||||||
|
|
||||||
def __init__(self, parent, type):
|
|
||||||
QDialog.__init__(self, parent, Qt.Window)
|
|
||||||
self.parent = parent
|
|
||||||
self.deck = self.parent.deck
|
|
||||||
self.dialog = aqt.forms.activetags.Ui_Dialog()
|
|
||||||
self.dialog.setupUi(self)
|
|
||||||
if type == "new":
|
|
||||||
self.active = "newActive"
|
|
||||||
self.inactive = "newInactive"
|
|
||||||
else:
|
|
||||||
self.active = "revActive"
|
|
||||||
self.inactive = "revInactive"
|
|
||||||
if (self.deck.getVar("newActive") == self.deck.getVar("revActive") and
|
|
||||||
self.deck.getVar("newInactive") == self.deck.getVar("revInactive")):
|
|
||||||
self.dialog.bothButton.click()
|
|
||||||
elif type == "new":
|
|
||||||
self.dialog.newButton.click()
|
|
||||||
else:
|
|
||||||
self.dialog.revButton.click()
|
|
||||||
self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"),
|
|
||||||
self.onHelp)
|
|
||||||
self.rebuildTagList()
|
|
||||||
restoreGeom(self, "activeTags")
|
|
||||||
|
|
||||||
def rebuildTagList(self):
|
|
||||||
usertags = self.deck.allTags()
|
|
||||||
self.items = []
|
|
||||||
self.suspended = {}
|
|
||||||
yes = parseTags(self.deck.getVar(self.active))
|
|
||||||
no = parseTags(self.deck.getVar(self.inactive))
|
|
||||||
yesHash = {}
|
|
||||||
noHash = {}
|
|
||||||
for y in yes:
|
|
||||||
yesHash[y] = True
|
|
||||||
for n in no:
|
|
||||||
noHash[n] = True
|
|
||||||
groupedTags = []
|
|
||||||
usertags.sort()
|
|
||||||
# render models and templates
|
|
||||||
for (type, sql, icon) in (
|
|
||||||
("models", "select tags from models", "contents.png"),
|
|
||||||
("cms", "select name from cardModels", "Anki_Card.png")):
|
|
||||||
d = {}
|
|
||||||
tagss = self.deck.db.column0(sql)
|
|
||||||
for tags in tagss:
|
|
||||||
for tag in parseTags(tags):
|
|
||||||
d[tag] = 1
|
|
||||||
sortedtags = sorted(d.keys())
|
|
||||||
icon = QIcon(":/icons/" + icon)
|
|
||||||
groupedTags.append([icon, sortedtags])
|
|
||||||
# remove from user tags
|
|
||||||
for tag in groupedTags[0][1] + groupedTags[1][1]:
|
|
||||||
try:
|
|
||||||
usertags.remove(tag)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
# user tags
|
|
||||||
icon = QIcon(":/icons/Anki_Fact.png")
|
|
||||||
groupedTags.append([icon, usertags])
|
|
||||||
self.tags = []
|
|
||||||
for (icon, tags) in groupedTags:
|
|
||||||
for t in tags:
|
|
||||||
self.tags.append(t)
|
|
||||||
item = QListWidgetItem(icon, t.replace("_", " "))
|
|
||||||
self.dialog.activeList.addItem(item)
|
|
||||||
if t in yesHash:
|
|
||||||
mode = QItemSelectionModel.Select
|
|
||||||
self.dialog.activeCheck.setChecked(True)
|
|
||||||
else:
|
|
||||||
mode = QItemSelectionModel.Deselect
|
|
||||||
idx = self.dialog.activeList.indexFromItem(item)
|
|
||||||
self.dialog.activeList.selectionModel().select(idx, mode)
|
|
||||||
# inactive
|
|
||||||
item = QListWidgetItem(icon, t.replace("_", " "))
|
|
||||||
self.dialog.inactiveList.addItem(item)
|
|
||||||
if t in noHash:
|
|
||||||
mode = QItemSelectionModel.Select
|
|
||||||
self.dialog.inactiveCheck.setChecked(True)
|
|
||||||
else:
|
|
||||||
mode = QItemSelectionModel.Deselect
|
|
||||||
idx = self.dialog.inactiveList.indexFromItem(item)
|
|
||||||
self.dialog.inactiveList.selectionModel().select(idx, mode)
|
|
||||||
|
|
||||||
def accept(self):
|
|
||||||
self.hide()
|
|
||||||
n = 0
|
|
||||||
yes = []
|
|
||||||
no = []
|
|
||||||
for c in range(self.dialog.activeList.count()):
|
|
||||||
# active
|
|
||||||
item = self.dialog.activeList.item(c)
|
|
||||||
idx = self.dialog.activeList.indexFromItem(item)
|
|
||||||
if self.dialog.activeList.selectionModel().isSelected(idx):
|
|
||||||
yes.append(self.tags[c])
|
|
||||||
# inactive
|
|
||||||
item = self.dialog.inactiveList.item(c)
|
|
||||||
idx = self.dialog.inactiveList.indexFromItem(item)
|
|
||||||
if self.dialog.inactiveList.selectionModel().isSelected(idx):
|
|
||||||
no.append(self.tags[c])
|
|
||||||
types = []
|
|
||||||
if (self.dialog.newButton.isChecked() or
|
|
||||||
self.dialog.bothButton.isChecked()):
|
|
||||||
types.append(["newActive", "newInactive"])
|
|
||||||
if (self.dialog.revButton.isChecked() or
|
|
||||||
self.dialog.bothButton.isChecked()):
|
|
||||||
types.append(["revActive", "revInactive"])
|
|
||||||
for (active, inactive) in types:
|
|
||||||
if self.dialog.activeCheck.isChecked():
|
|
||||||
self.deck.setVar(active, joinTags(yes))
|
|
||||||
else:
|
|
||||||
self.deck.setVar(active, "")
|
|
||||||
if self.dialog.inactiveCheck.isChecked():
|
|
||||||
self.deck.setVar(inactive, joinTags(no))
|
|
||||||
else:
|
|
||||||
self.deck.setVar(inactive, "")
|
|
||||||
self.parent.reset()
|
|
||||||
saveGeom(self, "activeTags")
|
|
||||||
QDialog.accept(self)
|
|
||||||
|
|
||||||
def onHelp(self):
|
|
||||||
aqt.openHelp("SelectiveStudy")
|
|
||||||
|
|
||||||
def show(parent, type):
|
|
||||||
at = ActiveTagsChooser(parent, type)
|
|
||||||
at.exec_()
|
|
|
@ -76,7 +76,7 @@ class AddCards(QDialog):
|
||||||
|
|
||||||
# FIXME: need to make sure to clean up note on exit
|
# FIXME: need to make sure to clean up note on exit
|
||||||
def setupNewNote(self, set=True):
|
def setupNewNote(self, set=True):
|
||||||
f = self.mw.deck.newNote()
|
f = self.mw.col.newNote()
|
||||||
f.tags = f.model()['tags']
|
f.tags = f.model()['tags']
|
||||||
if set:
|
if set:
|
||||||
self.editor.setNote(f)
|
self.editor.setNote(f)
|
||||||
|
@ -104,7 +104,7 @@ class AddCards(QDialog):
|
||||||
if not note or not note.id:
|
if not note or not note.id:
|
||||||
return
|
return
|
||||||
# we don't have to worry about cards; just the note
|
# we don't have to worry about cards; just the note
|
||||||
self.mw.deck._remNotes([note.id])
|
self.mw.col._remNotes([note.id])
|
||||||
|
|
||||||
def addHistory(self, note):
|
def addHistory(self, note):
|
||||||
txt = stripHTMLMedia(",".join(note.fields))[:30]
|
txt = stripHTMLMedia(",".join(note.fields))[:30]
|
||||||
|
@ -131,7 +131,7 @@ class AddCards(QDialog):
|
||||||
"Some fields are missing or not unique."),
|
"Some fields are missing or not unique."),
|
||||||
help="AddItems#AddError")
|
help="AddItems#AddError")
|
||||||
return
|
return
|
||||||
cards = self.mw.deck.addNote(note)
|
cards = self.mw.col.addNote(note)
|
||||||
if not cards:
|
if not cards:
|
||||||
showWarning(_("""\
|
showWarning(_("""\
|
||||||
The input you have provided would make an empty
|
The input you have provided would make an empty
|
||||||
|
@ -151,7 +151,7 @@ question or answer on all cards."""), help="AddItems")
|
||||||
# stop anything playing
|
# stop anything playing
|
||||||
clearAudioQueue()
|
clearAudioQueue()
|
||||||
self.onReset(keep=True)
|
self.onReset(keep=True)
|
||||||
self.mw.deck.autosave()
|
self.mw.col.autosave()
|
||||||
|
|
||||||
def keyPressEvent(self, evt):
|
def keyPressEvent(self, evt):
|
||||||
"Show answer on RET or register answer."
|
"Show answer on RET or register answer."
|
||||||
|
|
126
aqt/browser.py
126
aqt/browser.py
|
@ -28,14 +28,14 @@ COLOUR_MARKED2 = "#aaaaff"
|
||||||
# Data model
|
# Data model
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
class DeckModel(QAbstractTableModel):
|
class DataModel(QAbstractTableModel):
|
||||||
|
|
||||||
def __init__(self, browser):
|
def __init__(self, browser):
|
||||||
QAbstractTableModel.__init__(self)
|
QAbstractTableModel.__init__(self)
|
||||||
self.browser = browser
|
self.browser = browser
|
||||||
self.deck = browser.deck
|
self.col = browser.col
|
||||||
self.sortKey = None
|
self.sortKey = None
|
||||||
self.activeCols = self.deck.conf.get(
|
self.activeCols = self.col.conf.get(
|
||||||
"activeCols", ["noteFld", "template", "cardDue", "cardEase"])
|
"activeCols", ["noteFld", "template", "cardDue", "cardEase"])
|
||||||
self.cards = []
|
self.cards = []
|
||||||
self.cardObjs = {}
|
self.cardObjs = {}
|
||||||
|
@ -43,7 +43,7 @@ class DeckModel(QAbstractTableModel):
|
||||||
def getCard(self, index):
|
def getCard(self, index):
|
||||||
id = self.cards[index.row()]
|
id = self.cards[index.row()]
|
||||||
if not id in self.cardObjs:
|
if not id in self.cardObjs:
|
||||||
self.cardObjs[id] = self.deck.getCard(id)
|
self.cardObjs[id] = self.col.getCard(id)
|
||||||
return self.cardObjs[id]
|
return self.cardObjs[id]
|
||||||
|
|
||||||
def refreshNote(self, note):
|
def refreshNote(self, note):
|
||||||
|
@ -112,7 +112,7 @@ class DeckModel(QAbstractTableModel):
|
||||||
# the db progress handler may cause a refresh, so we need to zero out
|
# the db progress handler may cause a refresh, so we need to zero out
|
||||||
# old data first
|
# old data first
|
||||||
self.cards = []
|
self.cards = []
|
||||||
self.cards = self.deck.findCards(txt, self.browser.mw.config['fullSearch'])
|
self.cards = self.col.findCards(txt, self.browser.mw.config['fullSearch'])
|
||||||
print "fetch cards in %dms" % ((time.time() - t)*1000)
|
print "fetch cards in %dms" % ((time.time() - t)*1000)
|
||||||
if reset:
|
if reset:
|
||||||
self.endReset()
|
self.endReset()
|
||||||
|
@ -206,7 +206,7 @@ class DeckModel(QAbstractTableModel):
|
||||||
return self.answer()
|
return self.answer()
|
||||||
elif type == "noteFld":
|
elif type == "noteFld":
|
||||||
f = c.note()
|
f = c.note()
|
||||||
return self.formatQA(f.fields[self.deck.models.sortIdx(f.model())])
|
return self.formatQA(f.fields[self.col.models.sortIdx(f.model())])
|
||||||
elif type == "template":
|
elif type == "template":
|
||||||
return c.template()['name']
|
return c.template()['name']
|
||||||
elif type == "cardDue":
|
elif type == "cardDue":
|
||||||
|
@ -230,9 +230,9 @@ class DeckModel(QAbstractTableModel):
|
||||||
return _("(new)")
|
return _("(new)")
|
||||||
return "%d%%" % (c.factor/10)
|
return "%d%%" % (c.factor/10)
|
||||||
elif type == "cardGroup":
|
elif type == "cardGroup":
|
||||||
return self.browser.mw.deck.groups.name(c.gid)
|
return self.browser.mw.col.groups.name(c.gid)
|
||||||
elif type == "noteGroup":
|
elif type == "noteGroup":
|
||||||
return self.browser.mw.deck.groups.name(c.note().gid)
|
return self.browser.mw.col.groups.name(c.note().gid)
|
||||||
|
|
||||||
def question(self):
|
def question(self):
|
||||||
return self.formatQA(c.a())
|
return self.formatQA(c.a())
|
||||||
|
@ -255,7 +255,7 @@ class DeckModel(QAbstractTableModel):
|
||||||
elif c.queue == 1:
|
elif c.queue == 1:
|
||||||
date = c.due
|
date = c.due
|
||||||
elif c.queue == 2:
|
elif c.queue == 2:
|
||||||
date = time.time() + ((c.due - self.deck.sched.today)*86400)
|
date = time.time() + ((c.due - self.col.sched.today)*86400)
|
||||||
else:
|
else:
|
||||||
return _("(susp.)")
|
return _("(susp.)")
|
||||||
return time.strftime("%Y-%m-%d", time.localtime(date))
|
return time.strftime("%Y-%m-%d", time.localtime(date))
|
||||||
|
@ -301,7 +301,7 @@ class Browser(QMainWindow):
|
||||||
QMainWindow.__init__(self, None)
|
QMainWindow.__init__(self, None)
|
||||||
#applyStyles(self)
|
#applyStyles(self)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.deck = self.mw.deck
|
self.col = self.mw.col
|
||||||
self.currentRow = None
|
self.currentRow = None
|
||||||
self.lastFilter = ""
|
self.lastFilter = ""
|
||||||
self.form = aqt.forms.browser.Ui_Dialog()
|
self.form = aqt.forms.browser.Ui_Dialog()
|
||||||
|
@ -385,7 +385,7 @@ class Browser(QMainWindow):
|
||||||
saveGeom(self, "editor")
|
saveGeom(self, "editor")
|
||||||
saveState(self, "editor")
|
saveState(self, "editor")
|
||||||
saveHeader(self.form.tableView.horizontalHeader(), "editor")
|
saveHeader(self.form.tableView.horizontalHeader(), "editor")
|
||||||
self.deck.conf['activeCols'] = self.model.activeCols
|
self.col.conf['activeCols'] = self.model.activeCols
|
||||||
self.hide()
|
self.hide()
|
||||||
aqt.dialogs.close("Browser")
|
aqt.dialogs.close("Browser")
|
||||||
self.teardownHooks()
|
self.teardownHooks()
|
||||||
|
@ -467,7 +467,7 @@ class Browser(QMainWindow):
|
||||||
cur) % {
|
cur) % {
|
||||||
"cur": cur,
|
"cur": cur,
|
||||||
"sel": ngettext("%d selected", "%d selected", selected) % selected
|
"sel": ngettext("%d selected", "%d selected", selected) % selected
|
||||||
} + " - " + self.deck.name())
|
} + " - " + self.col.name())
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
def onReset(self):
|
def onReset(self):
|
||||||
|
@ -478,7 +478,7 @@ class Browser(QMainWindow):
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def setupTable(self):
|
def setupTable(self):
|
||||||
self.model = DeckModel(self)
|
self.model = DataModel(self)
|
||||||
self.form.tableView.setSortingEnabled(True)
|
self.form.tableView.setSortingEnabled(True)
|
||||||
self.form.tableView.setShowGrid(False)
|
self.form.tableView.setShowGrid(False)
|
||||||
self.form.tableView.setModel(self.model)
|
self.form.tableView.setModel(self.model)
|
||||||
|
@ -537,28 +537,28 @@ class Browser(QMainWindow):
|
||||||
if type in noSort:
|
if type in noSort:
|
||||||
showInfo(_("Sorting on this column is not supported. Please "
|
showInfo(_("Sorting on this column is not supported. Please "
|
||||||
"choose another."))
|
"choose another."))
|
||||||
type = self.deck.conf['sortType']
|
type = self.col.conf['sortType']
|
||||||
if self.deck.conf['sortType'] != type:
|
if self.col.conf['sortType'] != type:
|
||||||
self.deck.conf['sortType'] = type
|
self.col.conf['sortType'] = type
|
||||||
# default to descending for non-text fields
|
# default to descending for non-text fields
|
||||||
if type == "noteFld":
|
if type == "noteFld":
|
||||||
ord = not ord
|
ord = not ord
|
||||||
self.deck.conf['sortBackwards'] = ord
|
self.col.conf['sortBackwards'] = ord
|
||||||
self.onSearch()
|
self.onSearch()
|
||||||
else:
|
else:
|
||||||
if self.deck.conf['sortBackwards'] != ord:
|
if self.col.conf['sortBackwards'] != ord:
|
||||||
self.deck.conf['sortBackwards'] = ord
|
self.col.conf['sortBackwards'] = ord
|
||||||
self.model.reverse()
|
self.model.reverse()
|
||||||
self.setSortIndicator()
|
self.setSortIndicator()
|
||||||
|
|
||||||
def setSortIndicator(self):
|
def setSortIndicator(self):
|
||||||
hh = self.form.tableView.horizontalHeader()
|
hh = self.form.tableView.horizontalHeader()
|
||||||
type = self.deck.conf['sortType']
|
type = self.col.conf['sortType']
|
||||||
if type not in self.model.activeCols:
|
if type not in self.model.activeCols:
|
||||||
hh.setSortIndicatorShown(False)
|
hh.setSortIndicatorShown(False)
|
||||||
return
|
return
|
||||||
idx = self.model.activeCols.index(type)
|
idx = self.model.activeCols.index(type)
|
||||||
if self.deck.conf['sortBackwards']:
|
if self.col.conf['sortBackwards']:
|
||||||
ord = Qt.DescendingOrder
|
ord = Qt.DescendingOrder
|
||||||
else:
|
else:
|
||||||
ord = Qt.AscendingOrder
|
ord = Qt.AscendingOrder
|
||||||
|
@ -649,7 +649,7 @@ class Browser(QMainWindow):
|
||||||
self.onSearch()
|
self.onSearch()
|
||||||
|
|
||||||
def _modelTree(self, root):
|
def _modelTree(self, root):
|
||||||
for m in sorted(self.deck.models.all(), key=itemgetter("name")):
|
for m in sorted(self.col.models.all(), key=itemgetter("name")):
|
||||||
mitem = self.CallbackItem(
|
mitem = self.CallbackItem(
|
||||||
m['name'], lambda m=m: self.setFilter("model", m['name']))
|
m['name'], lambda m=m: self.setFilter("model", m['name']))
|
||||||
mitem.setIcon(0, QIcon(":/icons/product_design.png"))
|
mitem.setIcon(0, QIcon(":/icons/product_design.png"))
|
||||||
|
@ -662,7 +662,7 @@ class Browser(QMainWindow):
|
||||||
mitem.addChild(titem)
|
mitem.addChild(titem)
|
||||||
|
|
||||||
def _groupTree(self, root):
|
def _groupTree(self, root):
|
||||||
grps = self.deck.sched.groupTree()
|
grps = self.col.sched.groupTree()
|
||||||
def fillGroups(root, grps, head=""):
|
def fillGroups(root, grps, head=""):
|
||||||
for g in grps:
|
for g in grps:
|
||||||
item = self.CallbackItem(
|
item = self.CallbackItem(
|
||||||
|
@ -694,7 +694,7 @@ class Browser(QMainWindow):
|
||||||
return root
|
return root
|
||||||
|
|
||||||
def _userTagTree(self, root):
|
def _userTagTree(self, root):
|
||||||
for t in sorted(self.deck.tags.all()):
|
for t in sorted(self.col.tags.all()):
|
||||||
item = self.CallbackItem(
|
item = self.CallbackItem(
|
||||||
t, lambda t=t: self.setFilter("tag", t))
|
t, lambda t=t: self.setFilter("tag", t))
|
||||||
item.setIcon(0, QIcon(":/icons/anki-tag.png"))
|
item.setIcon(0, QIcon(":/icons/anki-tag.png"))
|
||||||
|
@ -706,7 +706,7 @@ class Browser(QMainWindow):
|
||||||
def setupCardInfo(self):
|
def setupCardInfo(self):
|
||||||
from anki.stats import CardStats
|
from anki.stats import CardStats
|
||||||
self.card = None
|
self.card = None
|
||||||
self.cardStats = CardStats(self.deck, None)
|
self.cardStats = CardStats(self.col, None)
|
||||||
self.connect(self.form.cardLabel,
|
self.connect(self.form.cardLabel,
|
||||||
SIGNAL("linkActivated(const QString&)"),
|
SIGNAL("linkActivated(const QString&)"),
|
||||||
self.onCardLink)
|
self.onCardLink)
|
||||||
|
@ -717,7 +717,7 @@ class Browser(QMainWindow):
|
||||||
rep = "<style>table * { font-size: 12px; }</style>" + rep
|
rep = "<style>table * { font-size: 12px; }</style>" + rep
|
||||||
m = self.card.model()
|
m = self.card.model()
|
||||||
# add sort field
|
# add sort field
|
||||||
sortf = m['flds'][self.mw.deck.models.sortIdx(m)]['name']
|
sortf = m['flds'][self.mw.col.models.sortIdx(m)]['name']
|
||||||
extra = self.cardStats.makeLine(
|
extra = self.cardStats.makeLine(
|
||||||
_("Sort Field"), "<a href=sort>%s</a>" % sortf)
|
_("Sort Field"), "<a href=sort>%s</a>" % sortf)
|
||||||
# and revlog
|
# and revlog
|
||||||
|
@ -767,7 +767,7 @@ class Browser(QMainWindow):
|
||||||
s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date")
|
s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date")
|
||||||
s += ("<th align=right>%s</th>" * 5) % (
|
s += ("<th align=right>%s</th>" * 5) % (
|
||||||
_("Type"), _("Ease"), _("Interval"), _("Factor"), _("Time"))
|
_("Type"), _("Ease"), _("Interval"), _("Factor"), _("Time"))
|
||||||
for (date, ease, ivl, factor, taken, type) in self.mw.deck.db.execute(
|
for (date, ease, ivl, factor, taken, type) in self.mw.col.db.execute(
|
||||||
"select time/1000, ease, ivl, factor, taken/1000.0, type "
|
"select time/1000, ease, ivl, factor, taken/1000.0, type "
|
||||||
"from revlog where cid = ?", self.card.id):
|
"from revlog where cid = ?", self.card.id):
|
||||||
s += "<tr><td>%s</td>" % time.strftime(_("<b>%Y-%m-%d</b> @ %H:%M"),
|
s += "<tr><td>%s</td>" % time.strftime(_("<b>%Y-%m-%d</b> @ %H:%M"),
|
||||||
|
@ -810,14 +810,14 @@ class Browser(QMainWindow):
|
||||||
self.form.tableView.selectionModel().selectedRows()]
|
self.form.tableView.selectionModel().selectedRows()]
|
||||||
|
|
||||||
def selectedNotes(self):
|
def selectedNotes(self):
|
||||||
return self.deck.db.list("""
|
return self.col.db.list("""
|
||||||
select distinct nid from cards
|
select distinct nid from cards
|
||||||
where id in %s""" % ids2str(
|
where id in %s""" % ids2str(
|
||||||
[self.model.cards[idx.row()] for idx in
|
[self.model.cards[idx.row()] for idx in
|
||||||
self.form.tableView.selectionModel().selectedRows()]))
|
self.form.tableView.selectionModel().selectedRows()]))
|
||||||
|
|
||||||
def selectedNotesAsCards(self):
|
def selectedNotesAsCards(self):
|
||||||
return self.deck.db.list(
|
return self.col.db.list(
|
||||||
"select id from cards where nid in (%s)" %
|
"select id from cards where nid in (%s)" %
|
||||||
",".join([str(s) for s in self.selectedNotes()]))
|
",".join([str(s) for s in self.selectedNotes()]))
|
||||||
|
|
||||||
|
@ -825,7 +825,7 @@ where id in %s""" % ids2str(
|
||||||
sf = self.selectedNotes()
|
sf = self.selectedNotes()
|
||||||
if not sf:
|
if not sf:
|
||||||
return
|
return
|
||||||
mods = self.deck.db.scalar("""
|
mods = self.col.db.scalar("""
|
||||||
select count(distinct mid) from notes
|
select count(distinct mid) from notes
|
||||||
where id in %s""" % ids2str(sf))
|
where id in %s""" % ids2str(sf))
|
||||||
if mods > 1:
|
if mods > 1:
|
||||||
|
@ -861,7 +861,7 @@ where id in %s""" % ids2str(sf))
|
||||||
self.mw.checkpoint(_("Delete Cards"))
|
self.mw.checkpoint(_("Delete Cards"))
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
oldRow = self.form.tableView.selectionModel().currentIndex().row()
|
oldRow = self.form.tableView.selectionModel().currentIndex().row()
|
||||||
self.deck.remCards(self.selectedCards())
|
self.col.remCards(self.selectedCards())
|
||||||
self.onSearch(reset=False)
|
self.onSearch(reset=False)
|
||||||
if len(self.model.cards):
|
if len(self.model.cards):
|
||||||
new = min(oldRow, len(self.model.cards) - 1)
|
new = min(oldRow, len(self.model.cards) - 1)
|
||||||
|
@ -880,7 +880,7 @@ where id in %s""" % ids2str(sf))
|
||||||
from aqt.tagedit import TagEdit
|
from aqt.tagedit import TagEdit
|
||||||
te = TagEdit(d, type=1)
|
te = TagEdit(d, type=1)
|
||||||
frm.groupBox.layout().insertWidget(0, te)
|
frm.groupBox.layout().insertWidget(0, te)
|
||||||
te.setDeck(self.deck)
|
te.setCol(self.col)
|
||||||
d.connect(d, SIGNAL("accepted()"), lambda: self.onSetGroup(frm, te))
|
d.connect(d, SIGNAL("accepted()"), lambda: self.onSetGroup(frm, te))
|
||||||
self.setTabOrder(frm.setCur, te)
|
self.setTabOrder(frm.setCur, te)
|
||||||
self.setTabOrder(te, frm.setInitial)
|
self.setTabOrder(te, frm.setInitial)
|
||||||
|
@ -894,16 +894,16 @@ where id in %s""" % ids2str(sf))
|
||||||
self.mw.checkpoint(_("Set Group"))
|
self.mw.checkpoint(_("Set Group"))
|
||||||
mod = intTime()
|
mod = intTime()
|
||||||
if frm.setCur.isChecked():
|
if frm.setCur.isChecked():
|
||||||
gid = self.deck.groups.id(unicode(te.text()))
|
gid = self.col.groups.id(unicode(te.text()))
|
||||||
self.deck.db.execute(
|
self.col.db.execute(
|
||||||
"update cards set mod=?, gid=? where id in " + ids2str(
|
"update cards set mod=?, gid=? where id in " + ids2str(
|
||||||
self.selectedCards()), mod, gid)
|
self.selectedCards()), mod, gid)
|
||||||
if frm.setInitial.isChecked():
|
if frm.setInitial.isChecked():
|
||||||
self.deck.db.execute(
|
self.col.db.execute(
|
||||||
"update notes set mod=?, gid=? where id in " + ids2str(
|
"update notes set mod=?, gid=? where id in " + ids2str(
|
||||||
self.selectedNotes()), mod, gid)
|
self.selectedNotes()), mod, gid)
|
||||||
else:
|
else:
|
||||||
self.deck.db.execute("""
|
self.col.db.execute("""
|
||||||
update cards set mod=?, gid=(select gid from notes where id = cards.nid)
|
update cards set mod=?, gid=(select gid from notes where id = cards.nid)
|
||||||
where id in %s""" % ids2str(self.selectedCards()), mod)
|
where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
self.onSearch(reset=False)
|
self.onSearch(reset=False)
|
||||||
|
@ -917,13 +917,13 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
if prompt is None:
|
if prompt is None:
|
||||||
prompt = _("Enter tags to add:")
|
prompt = _("Enter tags to add:")
|
||||||
if tags is None:
|
if tags is None:
|
||||||
(tags, r) = getTag(self, self.deck, prompt)
|
(tags, r) = getTag(self, self.col, prompt)
|
||||||
else:
|
else:
|
||||||
r = True
|
r = True
|
||||||
if not r:
|
if not r:
|
||||||
return
|
return
|
||||||
if func is None:
|
if func is None:
|
||||||
func = self.deck.tags.bulkAdd
|
func = self.col.tags.bulkAdd
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
if label is None:
|
if label is None:
|
||||||
label = _("Add Tags")
|
label = _("Add Tags")
|
||||||
|
@ -938,7 +938,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
if label is None:
|
if label is None:
|
||||||
label = _("Delete Tags")
|
label = _("Delete Tags")
|
||||||
self.addTags(tags, label, _("Enter tags to delete:"),
|
self.addTags(tags, label, _("Enter tags to delete:"),
|
||||||
func=self.deck.tags.bulkRem)
|
func=self.col.tags.bulkRem)
|
||||||
|
|
||||||
# Suspending and marking
|
# Suspending and marking
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -955,9 +955,9 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
self.editor.saveNow()
|
self.editor.saveNow()
|
||||||
c = self.selectedCards()
|
c = self.selectedCards()
|
||||||
if sus:
|
if sus:
|
||||||
self.deck.sched.suspendCards(c)
|
self.col.sched.suspendCards(c)
|
||||||
else:
|
else:
|
||||||
self.deck.sched.unsuspendCards(c)
|
self.col.sched.unsuspendCards(c)
|
||||||
self.model.reset()
|
self.model.reset()
|
||||||
self.mw.requireReset()
|
self.mw.requireReset()
|
||||||
|
|
||||||
|
@ -975,7 +975,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
|
|
||||||
def reposition(self):
|
def reposition(self):
|
||||||
cids = self.selectedCards()
|
cids = self.selectedCards()
|
||||||
cids = self.deck.db.list(
|
cids = self.col.db.list(
|
||||||
"select id from cards where type = 0 and id in " + ids2str(cids))
|
"select id from cards where type = 0 and id in " + ids2str(cids))
|
||||||
if not cids:
|
if not cids:
|
||||||
return showInfo(_("Only new cards can be repositioned."))
|
return showInfo(_("Only new cards can be repositioned."))
|
||||||
|
@ -983,7 +983,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
d.setWindowModality(Qt.WindowModal)
|
d.setWindowModality(Qt.WindowModal)
|
||||||
frm = aqt.forms.reposition.Ui_Dialog()
|
frm = aqt.forms.reposition.Ui_Dialog()
|
||||||
frm.setupUi(d)
|
frm.setupUi(d)
|
||||||
(pmin, pmax) = self.deck.db.first(
|
(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")
|
||||||
txt = _("Queue top: %d") % pmin
|
txt = _("Queue top: %d") % pmin
|
||||||
txt += "\n" + _("Queue bottom: %d") % pmax
|
txt += "\n" + _("Queue bottom: %d") % pmax
|
||||||
|
@ -992,7 +992,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
return
|
return
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Reposition"))
|
self.mw.checkpoint(_("Reposition"))
|
||||||
self.deck.sched.sortCards(
|
self.col.sched.sortCards(
|
||||||
cids, start=frm.start.value(), step=frm.step.value(),
|
cids, start=frm.start.value(), step=frm.step.value(),
|
||||||
shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked())
|
shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked())
|
||||||
self.onSearch(reset=False)
|
self.onSearch(reset=False)
|
||||||
|
@ -1012,9 +1012,9 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
self.mw.checkpoint(_("Reschedule"))
|
self.mw.checkpoint(_("Reschedule"))
|
||||||
if frm.asNew.isChecked():
|
if frm.asNew.isChecked():
|
||||||
self.deck.sched.forgetCards(self.selectedCards())
|
self.col.sched.forgetCards(self.selectedCards())
|
||||||
else:
|
else:
|
||||||
self.deck.sched.reschedCards(
|
self.col.sched.reschedCards(
|
||||||
self.selectedCards(), frm.min.value(), frm.max.value())
|
self.selectedCards(), frm.min.value(), frm.max.value())
|
||||||
self.onSearch(reset=False)
|
self.onSearch(reset=False)
|
||||||
self.mw.requireReset()
|
self.mw.requireReset()
|
||||||
|
@ -1088,7 +1088,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
if not sf:
|
if not sf:
|
||||||
return
|
return
|
||||||
import anki.find
|
import anki.find
|
||||||
fields = sorted(anki.find.fieldNames(self.deck, downcase=False))
|
fields = sorted(anki.find.fieldNames(self.col, downcase=False))
|
||||||
d = QDialog(self)
|
d = QDialog(self)
|
||||||
frm = aqt.forms.findreplace.Ui_Dialog()
|
frm = aqt.forms.findreplace.Ui_Dialog()
|
||||||
frm.setupUi(d)
|
frm.setupUi(d)
|
||||||
|
@ -1106,7 +1106,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
self.model.beginReset()
|
self.model.beginReset()
|
||||||
try:
|
try:
|
||||||
changed = self.deck.findReplace(sf,
|
changed = self.col.findReplace(sf,
|
||||||
unicode(frm.find.text()),
|
unicode(frm.find.text()),
|
||||||
unicode(frm.replace.text()),
|
unicode(frm.replace.text()),
|
||||||
frm.re.isChecked(),
|
frm.re.isChecked(),
|
||||||
|
@ -1143,12 +1143,12 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
|
||||||
restoreGeom(win, "findDupes")
|
restoreGeom(win, "findDupes")
|
||||||
fields = sorted(self.card.note.model.fieldModels, key=attrgetter("name"))
|
fields = sorted(self.card.note.model.fieldModels, key=attrgetter("name"))
|
||||||
# per-model data
|
# per-model data
|
||||||
data = self.deck.db.all("""
|
data = self.col.db.all("""
|
||||||
select fm.id, m.name || '>' || fm.name from fieldmodels fm, models m
|
select fm.id, m.name || '>' || fm.name from fieldmodels fm, models m
|
||||||
where fm.modelId = m.id""")
|
where fm.modelId = m.id""")
|
||||||
data.sort(key=itemgetter(1))
|
data.sort(key=itemgetter(1))
|
||||||
# all-model data
|
# all-model data
|
||||||
data2 = self.deck.db.all("""
|
data2 = self.col.db.all("""
|
||||||
select fm.id, fm.name from fieldmodels fm""")
|
select fm.id, fm.name from fieldmodels fm""")
|
||||||
byName = {}
|
byName = {}
|
||||||
for d in data2:
|
for d in data2:
|
||||||
|
@ -1187,9 +1187,9 @@ select fm.id, fm.name from fieldmodels fm""")
|
||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
def duplicatesReport(self, web, fmids):
|
def duplicatesReport(self, web, fmids):
|
||||||
self.deck.startProgress(2)
|
self.col.startProgress(2)
|
||||||
self.deck.updateProgress(_("Finding..."))
|
self.col.updateProgress(_("Finding..."))
|
||||||
res = self.deck.findDuplicates(fmids)
|
res = self.col.findDuplicates(fmids)
|
||||||
t = "<html><body>"
|
t = "<html><body>"
|
||||||
t += _("Duplicate Groups: %d") % len(res)
|
t += _("Duplicate Groups: %d") % len(res)
|
||||||
t += "<p><ol>"
|
t += "<p><ol>"
|
||||||
|
@ -1202,7 +1202,7 @@ select fm.id, fm.name from fieldmodels fm""")
|
||||||
t += "</ol>"
|
t += "</ol>"
|
||||||
t += "</body></html>"
|
t += "</body></html>"
|
||||||
web.setHtml(t)
|
web.setHtml(t)
|
||||||
self.deck.finishProgress()
|
self.col.finishProgress()
|
||||||
|
|
||||||
def dupeLinkClicked(self, link):
|
def dupeLinkClicked(self, link):
|
||||||
self.form.searchEdit.setText(link.toString())
|
self.form.searchEdit.setText(link.toString())
|
||||||
|
@ -1262,7 +1262,7 @@ class GenCards(QDialog):
|
||||||
|
|
||||||
def getSelection(self):
|
def getSelection(self):
|
||||||
# get cards to enable
|
# get cards to enable
|
||||||
f = self.browser.deck.getNote(self.nids[0])
|
f = self.browser.col.getNote(self.nids[0])
|
||||||
self.model = f.model()
|
self.model = f.model()
|
||||||
self.items = []
|
self.items = []
|
||||||
for t in self.model.templates:
|
for t in self.model.templates:
|
||||||
|
@ -1297,15 +1297,15 @@ class GenCards(QDialog):
|
||||||
mw.checkpoint(_("Generate Cards"))
|
mw.checkpoint(_("Generate Cards"))
|
||||||
mw.progress.start()
|
mw.progress.start()
|
||||||
for c, nid in enumerate(self.nids):
|
for c, nid in enumerate(self.nids):
|
||||||
f = mw.deck.getNote(nid)
|
f = mw.col.getNote(nid)
|
||||||
mw.deck.genCards(f, tplates)
|
mw.col.genCards(f, tplates)
|
||||||
if c % 100 == 0:
|
if c % 100 == 0:
|
||||||
mw.progress.update()
|
mw.progress.update()
|
||||||
if unused:
|
if unused:
|
||||||
cids = mw.deck.db.list("""
|
cids = mw.col.db.list("""
|
||||||
select id from cards where nid in %s and ord in %s""" % (
|
select id from cards where nid in %s and ord in %s""" % (
|
||||||
ids2str(self.nids), ids2str(unused)))
|
ids2str(self.nids), ids2str(unused)))
|
||||||
mw.deck.remCards(cids)
|
mw.col.remCards(cids)
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
mw.requireReset()
|
mw.requireReset()
|
||||||
self.browser.onSearch()
|
self.browser.onSearch()
|
||||||
|
@ -1344,8 +1344,8 @@ class ChangeModel(QDialog):
|
||||||
self.form.templateMap.setLayout(self.tlayout)
|
self.form.templateMap.setLayout(self.tlayout)
|
||||||
# model chooser
|
# model chooser
|
||||||
import aqt.modelchooser
|
import aqt.modelchooser
|
||||||
self.oldCurrentModel = self.browser.deck.conf['currentModelId']
|
self.oldCurrentModel = self.browser.col.conf['currentModelId']
|
||||||
self.browser.deck.conf['currentModelId'] = self.oldModel.id
|
self.browser.col.conf['currentModelId'] = self.oldModel.id
|
||||||
self.form.oldModelLabel.setText(self.oldModel.name)
|
self.form.oldModelLabel.setText(self.oldModel.name)
|
||||||
self.modelChooser = aqt.modelchooser.ModelChooser(
|
self.modelChooser = aqt.modelchooser.ModelChooser(
|
||||||
self.browser.mw, self.form.modelChooserWidget, cards=False, label=False)
|
self.browser.mw, self.form.modelChooserWidget, cards=False, label=False)
|
||||||
|
@ -1356,7 +1356,7 @@ class ChangeModel(QDialog):
|
||||||
self.pauseUpdate = False
|
self.pauseUpdate = False
|
||||||
|
|
||||||
def onReset(self):
|
def onReset(self):
|
||||||
self.modelChanged(self.browser.deck.currentModel())
|
self.modelChanged(self.browser.col.currentModel())
|
||||||
|
|
||||||
def modelChanged(self, model):
|
def modelChanged(self, model):
|
||||||
self.targetModel = model
|
self.targetModel = model
|
||||||
|
@ -1446,7 +1446,7 @@ class ChangeModel(QDialog):
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
removeHook("reset", self.onReset)
|
removeHook("reset", self.onReset)
|
||||||
removeHook("currentModelChanged", self.onReset)
|
removeHook("currentModelChanged", self.onReset)
|
||||||
self.oldCurrentModel = self.browser.deck.conf['currentModelId']
|
self.oldCurrentModel = self.browser.col.conf['currentModelId']
|
||||||
self.modelChooser.cleanup()
|
self.modelChooser.cleanup()
|
||||||
saveGeom(self, "changeModel")
|
saveGeom(self, "changeModel")
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ class CardLayout(QDialog):
|
||||||
self.note = note
|
self.note = note
|
||||||
self.type = type
|
self.type = type
|
||||||
self.ord = ord
|
self.ord = ord
|
||||||
self.deck = self.mw.deck
|
self.col = self.mw.col
|
||||||
self.mm = self.mw.deck.models
|
self.mm = self.mw.col.models
|
||||||
self.model = note.model()
|
self.model = note.model()
|
||||||
self.form = aqt.forms.clayout.Ui_Dialog()
|
self.form = aqt.forms.clayout.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
|
@ -50,7 +50,7 @@ class CardLayout(QDialog):
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def reload(self, first=False):
|
def reload(self, first=False):
|
||||||
self.cards = self.deck.previewCards(self.note, self.type)
|
self.cards = self.col.previewCards(self.note, self.type)
|
||||||
if not self.cards:
|
if not self.cards:
|
||||||
self.accept()
|
self.accept()
|
||||||
if first:
|
if first:
|
||||||
|
@ -220,7 +220,7 @@ class CardLayout(QDialog):
|
||||||
styles += "\n.cloze { font-weight: bold; color: blue; }"
|
styles += "\n.cloze { font-weight: bold; color: blue; }"
|
||||||
self.form.preview.setHtml(
|
self.form.preview.setHtml(
|
||||||
('<html><head>%s</head><body class="%s">' %
|
('<html><head>%s</head><body class="%s">' %
|
||||||
(getBase(self.deck), c.cssClass())) +
|
(getBase(self.col), c.cssClass())) +
|
||||||
"<style>" + styles + "</style>" +
|
"<style>" + styles + "</style>" +
|
||||||
mungeQA(c.q(reload=True)) +
|
mungeQA(c.q(reload=True)) +
|
||||||
self.maybeTextInput() +
|
self.maybeTextInput() +
|
||||||
|
@ -251,22 +251,22 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
modified = False
|
modified = False
|
||||||
self.mw.startProgress()
|
self.mw.startProgress()
|
||||||
self.deck.updateProgress(_("Applying changes..."))
|
self.col.updateProgress(_("Applying changes..."))
|
||||||
reset=True
|
reset=True
|
||||||
if len(self.fieldOrdinalUpdatedIds) > 0:
|
if len(self.fieldOrdinalUpdatedIds) > 0:
|
||||||
self.deck.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds)
|
self.col.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds)
|
||||||
modified = True
|
modified = True
|
||||||
if self.needFieldRebuild:
|
if self.needFieldRebuild:
|
||||||
modified = True
|
modified = True
|
||||||
if modified:
|
if modified:
|
||||||
self.note.model.setModified()
|
self.note.model.setModified()
|
||||||
self.deck.flushMod()
|
self.col.flushMod()
|
||||||
if self.noteedit and self.noteedit.onChange:
|
if self.noteedit and self.noteedit.onChange:
|
||||||
self.noteedit.onChange("all")
|
self.noteedit.onChange("all")
|
||||||
reset=False
|
reset=False
|
||||||
if reset:
|
if reset:
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
self.deck.finishProgress()
|
self.col.finishProgress()
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
def onHelp(self):
|
def onHelp(self):
|
||||||
|
@ -358,7 +358,7 @@ class CardLayout(QDialog):
|
||||||
if fld['name'] != name:
|
if fld['name'] != name:
|
||||||
self.mm.renameField(self.model, fld, name)
|
self.mm.renameField(self.model, fld, name)
|
||||||
# as the field name has changed, we have to regenerate cards
|
# as the field name has changed, we have to regenerate cards
|
||||||
self.cards = self.deck.previewCards(self.note, self.type)
|
self.cards = self.col.previewCards(self.note, self.type)
|
||||||
self.cardChanged(0)
|
self.cardChanged(0)
|
||||||
self.renderPreview()
|
self.renderPreview()
|
||||||
self.fillFieldList()
|
self.fillFieldList()
|
||||||
|
|
135
aqt/config.py
135
aqt/config.py
|
@ -1,135 +0,0 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
|
|
||||||
# User configuration handling
|
|
||||||
##########################################################################
|
|
||||||
# The majority of the config is serialized into a string, so we can access it
|
|
||||||
# easily and pickle objects like window state. A separate table keeps track of
|
|
||||||
# seen decks, so that multiple instances can update the recent decks list.
|
|
||||||
|
|
||||||
import os, sys, time, random, cPickle
|
|
||||||
from anki.db import DB
|
|
||||||
from anki.utils import isMac
|
|
||||||
|
|
||||||
defaultConf = {
|
|
||||||
'confVer': 3,
|
|
||||||
'interfaceLang': "en",
|
|
||||||
'fullSearch': False,
|
|
||||||
'autoplaySounds': True,
|
|
||||||
'searchHistory': [],
|
|
||||||
'checkForUpdates': True, # ui?
|
|
||||||
'created': time.time(),
|
|
||||||
'deleteMedia': False,
|
|
||||||
'documentDir': u"",
|
|
||||||
'dropboxPublicFolder': u"",
|
|
||||||
'editFontFamily': 'Arial',
|
|
||||||
'editFontSize': 12,
|
|
||||||
'editLineSize': 20,
|
|
||||||
'iconSize': 32,
|
|
||||||
'id': random.randrange(0, 2**63),
|
|
||||||
'lastMsg': -1,
|
|
||||||
'loadLastDeck': False,
|
|
||||||
'mainWindowGeom': None,
|
|
||||||
'mainWindowState': None,
|
|
||||||
'mediaLocation': "",
|
|
||||||
'numBackups': 30,
|
|
||||||
'preserveKeyboard': True,
|
|
||||||
'proxyHost': '',
|
|
||||||
'proxyPass': '',
|
|
||||||
'proxyPort': 8080,
|
|
||||||
'proxyUser': '',
|
|
||||||
'recentColours': ["#000000", "#0000ff"],
|
|
||||||
'showProgress': True,
|
|
||||||
'showToolbar': True,
|
|
||||||
'centerQA': True,
|
|
||||||
'stripHTML': True,
|
|
||||||
'suppressEstimates': False,
|
|
||||||
'suppressUpdate': False,
|
|
||||||
'syncDisableWhenMoved': True,
|
|
||||||
'syncOnProgramOpen': True,
|
|
||||||
'syncPassword': "",
|
|
||||||
'syncUsername': "",
|
|
||||||
}
|
|
||||||
|
|
||||||
class Config(object):
|
|
||||||
configDbName = "ankiprefs.db"
|
|
||||||
|
|
||||||
def __init__(self, confDir):
|
|
||||||
self.confDir = confDir
|
|
||||||
self._conf = {}
|
|
||||||
if isMac and (
|
|
||||||
self.confDir == os.path.expanduser("~/.anki")):
|
|
||||||
self.confDir = os.path.expanduser(
|
|
||||||
"~/Library/Application Support/Anki")
|
|
||||||
self._addAnkiDirs()
|
|
||||||
self.load()
|
|
||||||
|
|
||||||
# dict interface
|
|
||||||
def get(self, *args):
|
|
||||||
return self._conf.get(*args)
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._conf[key]
|
|
||||||
def __setitem__(self, key, val):
|
|
||||||
self._conf[key] = val
|
|
||||||
def __contains__(self, key):
|
|
||||||
return self._conf.__contains__(key)
|
|
||||||
|
|
||||||
# load/save
|
|
||||||
def load(self):
|
|
||||||
path = self._dbPath()
|
|
||||||
self.db = DB(path, text=str)
|
|
||||||
self.db.executescript("""
|
|
||||||
create table if not exists decks (path text primary key);
|
|
||||||
create table if not exists config (conf text not null);
|
|
||||||
""")
|
|
||||||
conf = self.db.scalar("select conf from config")
|
|
||||||
if conf:
|
|
||||||
self._conf.update(cPickle.loads(conf))
|
|
||||||
else:
|
|
||||||
self._conf.update(defaultConf)
|
|
||||||
# ensure there's something to update
|
|
||||||
self.db.execute("insert or ignore into config values ('')")
|
|
||||||
self._addDefaults()
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.db.execute("update config set conf = ?",
|
|
||||||
cPickle.dumps(self._conf))
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
# recent deck support
|
|
||||||
def recentDecks(self):
|
|
||||||
"Return a list of paths to remembered decks."
|
|
||||||
# have to convert to unicode manually because of the text factory
|
|
||||||
return [unicode(d[0], 'utf8') for d in
|
|
||||||
self.db.execute("select path from decks")]
|
|
||||||
|
|
||||||
def addRecentDeck(self, path):
|
|
||||||
"Add PATH to the list of recent decks if not already. Must be unicode."
|
|
||||||
self.db.execute("insert or ignore into decks values (?)",
|
|
||||||
path.encode("utf-8"))
|
|
||||||
|
|
||||||
def delRecentDeck(self, path):
|
|
||||||
"Remove PATH from the list if it exists. Must be unicode."
|
|
||||||
self.db.execute("delete from decks where path = ?",
|
|
||||||
path.encode("utf-8"))
|
|
||||||
|
|
||||||
# helpers
|
|
||||||
def _addDefaults(self):
|
|
||||||
if self.get('confVer') >= defaultConf['confVer']:
|
|
||||||
return
|
|
||||||
for (k,v) in defaultConf.items():
|
|
||||||
if k not in self:
|
|
||||||
self[k] = v
|
|
||||||
|
|
||||||
def _dbPath(self):
|
|
||||||
return os.path.join(self.confDir, self.configDbName)
|
|
||||||
|
|
||||||
def _addAnkiDirs(self):
|
|
||||||
base = self.confDir
|
|
||||||
for x in (base,
|
|
||||||
os.path.join(base, "addons"),
|
|
||||||
os.path.join(base, "backups")):
|
|
||||||
try:
|
|
||||||
os.mkdir(x)
|
|
||||||
except:
|
|
||||||
pass
|
|
|
@ -6,13 +6,13 @@ import sys, re
|
||||||
import aqt
|
import aqt
|
||||||
from aqt.utils import maybeHideClose
|
from aqt.utils import maybeHideClose
|
||||||
|
|
||||||
class DeckOptions(QDialog):
|
class ColOptions(QDialog):
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw):
|
||||||
QDialog.__init__(self, mw, Qt.Window)
|
QDialog.__init__(self, mw, Qt.Window)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.d = mw.deck
|
self.d = mw.col
|
||||||
self.form = aqt.forms.deckopts.Ui_Dialog()
|
self.form = aqt.forms.colopts.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.setup()
|
self.setup()
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
@ -28,7 +28,7 @@ class DeckOptions(QDialog):
|
||||||
self.form.mediaURL.setText(self.d.conf['mediaURL'])
|
self.form.mediaURL.setText(self.d.conf['mediaURL'])
|
||||||
|
|
||||||
def helpRequested(self):
|
def helpRequested(self):
|
||||||
aqt.openHelp("DeckOptions")
|
aqt.openHelp("ColOptions")
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
needSync = False
|
needSync = False
|
||||||
|
@ -49,4 +49,4 @@ class DeckOptions(QDialog):
|
||||||
self.d.conf['mediaURL'] = url
|
self.d.conf['mediaURL'] = url
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
if needSync:
|
if needSync:
|
||||||
aqt.mw.syncDeck(interactive=-1)
|
aqt.mw.syncCol(interactive=-1)
|
||||||
|
|
|
@ -39,8 +39,8 @@ class EditCurrent(QDialog):
|
||||||
r = self.mw.reviewer
|
r = self.mw.reviewer
|
||||||
r.card.load()
|
r.card.load()
|
||||||
r.keep = True
|
r.keep = True
|
||||||
# we don't need to reset the deck, but there may be new groups
|
# we don't need to reset the col, but there may be new groups
|
||||||
self.mw.deck.sched._resetConf()
|
self.mw.col.sched._resetConf()
|
||||||
self.mw.moveToState("review")
|
self.mw.moveToState("review")
|
||||||
saveGeom(self, "editcurrent")
|
saveGeom(self, "editcurrent")
|
||||||
self.close()
|
self.close()
|
||||||
|
|
|
@ -371,7 +371,7 @@ class Editor(object):
|
||||||
self.note = note
|
self.note = note
|
||||||
# change timer
|
# change timer
|
||||||
if self.note:
|
if self.note:
|
||||||
self.web.setHtml(_html % (getBase(self.mw.deck), anki.js.all,
|
self.web.setHtml(_html % (getBase(self.mw.col), anki.js.all,
|
||||||
_("Show Duplicates")),
|
_("Show Duplicates")),
|
||||||
loadCB=self._loadFinished)
|
loadCB=self._loadFinished)
|
||||||
self.updateTagsAndGroup()
|
self.updateTagsAndGroup()
|
||||||
|
@ -490,28 +490,28 @@ class Editor(object):
|
||||||
self.outerLayout.addWidget(g)
|
self.outerLayout.addWidget(g)
|
||||||
|
|
||||||
def updateTagsAndGroup(self):
|
def updateTagsAndGroup(self):
|
||||||
if self.tags.deck != self.mw.deck:
|
if self.tags.col != self.mw.col:
|
||||||
self.tags.setDeck(self.mw.deck)
|
self.tags.setCol(self.mw.col)
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
self.group.setDeck(self.mw.deck)
|
self.group.setCol(self.mw.col)
|
||||||
self.tags.setText(self.note.stringTags().strip())
|
self.tags.setText(self.note.stringTags().strip())
|
||||||
if getattr(self.note, 'gid', None):
|
if getattr(self.note, 'gid', None):
|
||||||
gid = self.note.gid
|
gid = self.note.gid
|
||||||
else:
|
else:
|
||||||
gid = self.note.model().conf['gid']
|
gid = self.note.model().conf['gid']
|
||||||
self.group.setText(self.mw.deck.groups.name(gid))
|
self.group.setText(self.mw.col.groups.name(gid))
|
||||||
|
|
||||||
def saveTagsAndGroup(self):
|
def saveTagsAndGroup(self):
|
||||||
if not self.note:
|
if not self.note:
|
||||||
return
|
return
|
||||||
self.note.tags = self.mw.deck.tags.split(unicode(self.tags.text()))
|
self.note.tags = self.mw.col.tags.split(unicode(self.tags.text()))
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
# save group and tags to model
|
# save group and tags to model
|
||||||
self.note.gid = self.mw.deck.groups.id(unicode(self.group.text()))
|
self.note.gid = self.mw.col.groups.id(unicode(self.group.text()))
|
||||||
m = self.note.model()
|
m = self.note.model()
|
||||||
m['gid'] = self.note.gid
|
m['gid'] = self.note.gid
|
||||||
m['tags'] = self.note.tags
|
m['tags'] = self.note.tags
|
||||||
self.mw.deck.models.save(m)
|
self.mw.col.models.save(m)
|
||||||
self.note.flush()
|
self.note.flush()
|
||||||
runHook("tagsAndGroupUpdated", self.note)
|
runHook("tagsAndGroupUpdated", self.note)
|
||||||
|
|
||||||
|
@ -696,7 +696,7 @@ class Editor(object):
|
||||||
def _addMedia(self, path, canDelete=False):
|
def _addMedia(self, path, canDelete=False):
|
||||||
"Add to media folder and return basename."
|
"Add to media folder and return basename."
|
||||||
# copy to media folder
|
# copy to media folder
|
||||||
name = self.mw.deck.media.addFile(path)
|
name = self.mw.col.media.addFile(path)
|
||||||
# remove original?
|
# remove original?
|
||||||
if canDelete and self.mw.config['deleteMedia']:
|
if canDelete and self.mw.config['deleteMedia']:
|
||||||
if os.path.abspath(name) != os.path.abspath(path):
|
if os.path.abspath(name) != os.path.abspath(path):
|
||||||
|
|
|
@ -40,7 +40,7 @@ Error was:<pre>%s</pre>""")
|
||||||
self.setupTable()
|
self.setupTable()
|
||||||
self.onChangeType(type)
|
self.onChangeType(type)
|
||||||
if type == 0:
|
if type == 0:
|
||||||
self.setWindowTitle(_("Download Shared Deck"))
|
self.setWindowTitle(_("Download Shared Col"))
|
||||||
else:
|
else:
|
||||||
self.setWindowTitle(_("Download Shared Plugin"))
|
self.setWindowTitle(_("Download Shared Plugin"))
|
||||||
if self.ok:
|
if self.ok:
|
||||||
|
@ -221,7 +221,7 @@ Error was:<pre>%s</pre>""")
|
||||||
tit = re.sub("[^][A-Za-z0-9 ()\-]", "", tit)
|
tit = re.sub("[^][A-Za-z0-9 ()\-]", "", tit)
|
||||||
tit = tit[0:40]
|
tit = tit[0:40]
|
||||||
if self.type == 0:
|
if self.type == 0:
|
||||||
# deck
|
# col
|
||||||
dd = self.parent.config['documentDir']
|
dd = self.parent.config['documentDir']
|
||||||
p = os.path.join(dd, tit + ".anki")
|
p = os.path.join(dd, tit + ".anki")
|
||||||
if os.path.exists(p):
|
if os.path.exists(p):
|
||||||
|
@ -237,7 +237,7 @@ Error was:<pre>%s</pre>""")
|
||||||
pass
|
pass
|
||||||
open(os.path.join(dd, tit + ".media",
|
open(os.path.join(dd, tit + ".media",
|
||||||
os.path.basename(l)),"wb").write(z.read(l))
|
os.path.basename(l)),"wb").write(z.read(l))
|
||||||
self.parent.loadDeck(dpath)
|
self.parent.loadCol(dpath)
|
||||||
else:
|
else:
|
||||||
pd = self.parent.pluginsFolder()
|
pd = self.parent.pluginsFolder()
|
||||||
if z:
|
if z:
|
||||||
|
|
|
@ -17,7 +17,7 @@ class GroupConf(QDialog):
|
||||||
self.gcid = gcid
|
self.gcid = gcid
|
||||||
self.form = aqt.forms.groupconf.Ui_Dialog()
|
self.form = aqt.forms.groupconf.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
(self.name, self.conf) = self.mw.deck.db.first(
|
(self.name, self.conf) = self.mw.col.db.first(
|
||||||
"select name, conf from gconf where id = ?", self.gcid)
|
"select name, conf from gconf where id = ?", self.gcid)
|
||||||
self.conf = simplejson.loads(self.conf)
|
self.conf = simplejson.loads(self.conf)
|
||||||
self.setWindowTitle(self.name)
|
self.setWindowTitle(self.name)
|
||||||
|
@ -131,7 +131,7 @@ class GroupConf(QDialog):
|
||||||
c['maxTaken'] = f.maxTaken.value()
|
c['maxTaken'] = f.maxTaken.value()
|
||||||
# update db
|
# update db
|
||||||
self.mw.checkpoint(_("Group Options"))
|
self.mw.checkpoint(_("Group Options"))
|
||||||
self.mw.deck.db.execute(
|
self.mw.col.db.execute(
|
||||||
"update gconf set conf = ? where id = ?",
|
"update gconf set conf = ? where id = ?",
|
||||||
simplejson.dumps(self.conf), self.gcid)
|
simplejson.dumps(self.conf), self.gcid)
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ class GroupConfSelector(QDialog):
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.connect(self.form.list, SIGNAL("itemChanged(QListWidgetItem*)"),
|
self.connect(self.form.list, SIGNAL("itemChanged(QListWidgetItem*)"),
|
||||||
self.onRename)
|
self.onRename)
|
||||||
self.defaultId = self.mw.deck.db.scalar(
|
self.defaultId = self.mw.col.db.scalar(
|
||||||
"select gcid from groups where id = ?", self.gids[0])
|
"select gcid from groups where id = ?", self.gids[0])
|
||||||
self.reload()
|
self.reload()
|
||||||
self.addButtons()
|
self.addButtons()
|
||||||
|
@ -155,7 +155,7 @@ class GroupConfSelector(QDialog):
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
# save
|
# save
|
||||||
self.mw.deck.db.execute(
|
self.mw.col.db.execute(
|
||||||
"update groups set gcid = ? where id in "+ids2str(self.gids),
|
"update groups set gcid = ? where id in "+ids2str(self.gids),
|
||||||
self.gcid())
|
self.gcid())
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
@ -164,7 +164,7 @@ class GroupConfSelector(QDialog):
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
self.confs = self.mw.deck.groupConfs()
|
self.confs = self.mw.col.groupConfs()
|
||||||
self.form.list.clear()
|
self.form.list.clear()
|
||||||
deflt = None
|
deflt = None
|
||||||
for c in self.confs:
|
for c in self.confs:
|
||||||
|
@ -190,7 +190,7 @@ class GroupConfSelector(QDialog):
|
||||||
|
|
||||||
def onRename(self, item):
|
def onRename(self, item):
|
||||||
gcid = self.gcid()
|
gcid = self.gcid()
|
||||||
self.mw.deck.db.execute("update gconf set name = ? where id = ?",
|
self.mw.col.db.execute("update gconf set name = ? where id = ?",
|
||||||
unicode(item.text()), gcid)
|
unicode(item.text()), gcid)
|
||||||
|
|
||||||
def onEdit(self):
|
def onEdit(self):
|
||||||
|
@ -198,10 +198,10 @@ class GroupConfSelector(QDialog):
|
||||||
|
|
||||||
def onCopy(self):
|
def onCopy(self):
|
||||||
gcid = self.gcid()
|
gcid = self.gcid()
|
||||||
gc = list(self.mw.deck.db.first("select * from gconf where id = ?", gcid))
|
gc = list(self.mw.col.db.first("select * from gconf where id = ?", gcid))
|
||||||
gc[0] = self.mw.deck.nextID("gcid")
|
gc[0] = self.mw.col.nextID("gcid")
|
||||||
gc[2] = _("%s copy")%gc[2]
|
gc[2] = _("%s copy")%gc[2]
|
||||||
self.mw.deck.db.execute("insert into gconf values (?,?,?,?)", *gc)
|
self.mw.col.db.execute("insert into gconf values (?,?,?,?)", *gc)
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self):
|
||||||
|
@ -210,8 +210,8 @@ class GroupConfSelector(QDialog):
|
||||||
showInfo(_("The default configuration can't be removed."), self)
|
showInfo(_("The default configuration can't be removed."), self)
|
||||||
else:
|
else:
|
||||||
self.mw.checkpoint(_("Delete Group Config"))
|
self.mw.checkpoint(_("Delete Group Config"))
|
||||||
self.mw.deck.db.execute(
|
self.mw.col.db.execute(
|
||||||
"update groups set gcid = 1 where gcid = ?", gcid)
|
"update groups set gcid = 1 where gcid = ?", gcid)
|
||||||
self.mw.deck.db.execute(
|
self.mw.col.db.execute(
|
||||||
"delete from gconf where id = ?", gcid)
|
"delete from gconf where id = ?", gcid)
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Groups(QDialog):
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
grps = self.mw.deck.sched.groupCountTree()
|
grps = self.mw.col.sched.groupCountTree()
|
||||||
self.mw.progress.finish()
|
self.mw.progress.finish()
|
||||||
self.groupMap = {}
|
self.groupMap = {}
|
||||||
self.fullNames = {}
|
self.fullNames = {}
|
||||||
|
@ -107,7 +107,7 @@ class Groups(QDialog):
|
||||||
err.append(
|
err.append(
|
||||||
_("The default group can't be deleted."))
|
_("The default group can't be deleted."))
|
||||||
continue
|
continue
|
||||||
self.mw.deck.delGroup(gid)
|
self.mw.col.delGroup(gid)
|
||||||
self.reload()
|
self.reload()
|
||||||
if err:
|
if err:
|
||||||
showInfo("\n".join(err))
|
showInfo("\n".join(err))
|
||||||
|
@ -127,7 +127,7 @@ class Groups(QDialog):
|
||||||
gid = self.groupMap[cold]
|
gid = self.groupMap[cold]
|
||||||
cnew = self.fullNames[cold].replace(old, txt)
|
cnew = self.fullNames[cold].replace(old, txt)
|
||||||
if gid:
|
if gid:
|
||||||
self.mw.deck.db.execute(
|
self.mw.col.db.execute(
|
||||||
"update groups set name = ? where id = ?",
|
"update groups set name = ? where id = ?",
|
||||||
cnew, gid)
|
cnew, gid)
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
|
@ -167,15 +167,15 @@ class Groups(QDialog):
|
||||||
if len(gids) == self.gidCount:
|
if len(gids) == self.gidCount:
|
||||||
# all enabled is same as empty
|
# all enabled is same as empty
|
||||||
gids = []
|
gids = []
|
||||||
# if gids != self.mw.deck.conf['groups']:
|
# if gids != self.mw.col.conf['groups']:
|
||||||
# self.mw.deck.conf['groups'] = gids
|
# self.mw.col.conf['groups'] = gids
|
||||||
# self.mw.reset()
|
# self.mw.reset()
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
def _makeItems(self, grps):
|
def _makeItems(self, grps):
|
||||||
self.gidCount = 0
|
self.gidCount = 0
|
||||||
on = {}
|
on = {}
|
||||||
a = self.mw.deck.groups.active()
|
a = self.mw.col.groups.active()
|
||||||
if not a:
|
if not a:
|
||||||
on = None
|
on = None
|
||||||
else:
|
else:
|
||||||
|
@ -196,7 +196,7 @@ class Groups(QDialog):
|
||||||
branch.setCheckState(COLCHECK, Qt.Unchecked)
|
branch.setCheckState(COLCHECK, Qt.Unchecked)
|
||||||
branch.setText(COLNAME, grp[0])
|
branch.setText(COLNAME, grp[0])
|
||||||
if gid:
|
if gid:
|
||||||
branch.setText(COLOPTS, self.mw.deck.groups.name(gid))
|
branch.setText(COLOPTS, self.mw.col.groups.name(gid))
|
||||||
branch.setText(COLCOUNT, "")
|
branch.setText(COLCOUNT, "")
|
||||||
branch.setText(COLDUE, str(grp[2]))
|
branch.setText(COLDUE, str(grp[2]))
|
||||||
branch.setText(COLNEW, str(grp[3]))
|
branch.setText(COLNEW, str(grp[3]))
|
||||||
|
|
|
@ -61,7 +61,7 @@ class UpdateMap(QDialog):
|
||||||
for i in range(numFields):
|
for i in range(numFields):
|
||||||
self.dialog.fileField.addItem("Field %d" % (i+1))
|
self.dialog.fileField.addItem("Field %d" % (i+1))
|
||||||
for m in fieldModels:
|
for m in fieldModels:
|
||||||
self.dialog.deckField.addItem(m.name)
|
self.dialog.colField.addItem(m.name)
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def helpRequested(self):
|
def helpRequested(self):
|
||||||
|
@ -70,7 +70,7 @@ class UpdateMap(QDialog):
|
||||||
def accept(self):
|
def accept(self):
|
||||||
self.updateKey = (
|
self.updateKey = (
|
||||||
self.dialog.fileField.currentIndex(),
|
self.dialog.fileField.currentIndex(),
|
||||||
self.fieldModels[self.dialog.deckField.currentIndex()].id)
|
self.fieldModels[self.dialog.colField.currentIndex()].id)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class ImportDialog(QDialog):
|
class ImportDialog(QDialog):
|
||||||
|
@ -95,10 +95,10 @@ class ImportDialog(QDialog):
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def setupOptions(self):
|
def setupOptions(self):
|
||||||
self.model = self.parent.deck.currentModel
|
self.model = self.parent.col.currentModel
|
||||||
self.modelChooser = ui.modelchooser.ModelChooser(self,
|
self.modelChooser = ui.modelchooser.ModelChooser(self,
|
||||||
self.parent,
|
self.parent,
|
||||||
self.parent.deck,
|
self.parent.col,
|
||||||
self.modelChanged)
|
self.modelChanged)
|
||||||
self.dialog.modelArea.setLayout(self.modelChooser)
|
self.dialog.modelArea.setLayout(self.modelChooser)
|
||||||
self.connect(self.dialog.importButton, SIGNAL("clicked()"),
|
self.connect(self.dialog.importButton, SIGNAL("clicked()"),
|
||||||
|
@ -197,7 +197,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||||
self.importer.mapping = self.mapping
|
self.importer.mapping = self.mapping
|
||||||
try:
|
try:
|
||||||
n = _("Import")
|
n = _("Import")
|
||||||
self.parent.deck.setUndoStart(n)
|
self.parent.col.setUndoStart(n)
|
||||||
try:
|
try:
|
||||||
self.importer.doImport()
|
self.importer.doImport()
|
||||||
except ImportFormatError, e:
|
except ImportFormatError, e:
|
||||||
|
@ -211,8 +211,8 @@ you can enter it here. Use \\t to represent tab."""),
|
||||||
self.dialog.status.setText(msg)
|
self.dialog.status.setText(msg)
|
||||||
return
|
return
|
||||||
finally:
|
finally:
|
||||||
self.parent.deck.finishProgress()
|
self.parent.col.finishProgress()
|
||||||
self.parent.deck.setUndoEnd(n)
|
self.parent.col.setUndoEnd(n)
|
||||||
txt = (
|
txt = (
|
||||||
_("Importing complete. %(num)d notes imported from %(file)s.\n") %
|
_("Importing complete. %(num)d notes imported from %(file)s.\n") %
|
||||||
{"num": self.importer.total, "file": os.path.basename(self.file)})
|
{"num": self.importer.total, "file": os.path.basename(self.file)})
|
||||||
|
@ -223,7 +223,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||||
self.dialog.status.setText(txt)
|
self.dialog.status.setText(txt)
|
||||||
self.file = None
|
self.file = None
|
||||||
self.maybePreview()
|
self.maybePreview()
|
||||||
self.parent.deck.db.flush()
|
self.parent.col.db.flush()
|
||||||
self.parent.reset()
|
self.parent.reset()
|
||||||
self.modelChooser.deinit()
|
self.modelChooser.deinit()
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ you can enter it here. Use \\t to represent tab."""),
|
||||||
def showMapping(self, keepMapping=False, hook=None):
|
def showMapping(self, keepMapping=False, hook=None):
|
||||||
# first, check that we can read the file
|
# first, check that we can read the file
|
||||||
try:
|
try:
|
||||||
self.importer = self.importerFunc(self.parent.deck, self.file)
|
self.importer = self.importerFunc(self.parent.col, self.file)
|
||||||
if hook:
|
if hook:
|
||||||
hook()
|
hook()
|
||||||
if not keepMapping:
|
if not keepMapping:
|
||||||
|
|
514
aqt/main.py
514
aqt/main.py
|
@ -9,7 +9,7 @@ from operator import itemgetter
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
QtConfig = pyqtconfig.Configuration()
|
QtConfig = pyqtconfig.Configuration()
|
||||||
|
|
||||||
from anki import Deck
|
from anki import Collection
|
||||||
from anki.sound import playFromText, clearAudioQueue, stripSounds
|
from anki.sound import playFromText, clearAudioQueue, stripSounds
|
||||||
from anki.utils import stripHTML, checksum, isWin, isMac
|
from anki.utils import stripHTML, checksum, isWin, isMac
|
||||||
from anki.hooks import runHook, addHook, removeHook
|
from anki.hooks import runHook, addHook, removeHook
|
||||||
|
@ -27,40 +27,28 @@ config = aqt.config
|
||||||
## models remembering the previous group
|
## models remembering the previous group
|
||||||
|
|
||||||
class AnkiQt(QMainWindow):
|
class AnkiQt(QMainWindow):
|
||||||
def __init__(self, app, config, args, splash):
|
def __init__(self, app, config, args):
|
||||||
QMainWindow.__init__(self)
|
QMainWindow.__init__(self)
|
||||||
aqt.mw = self
|
aqt.mw = self
|
||||||
self.splash = splash
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.config = config
|
self.config = config
|
||||||
try:
|
try:
|
||||||
# initialize everything
|
# initialize everything
|
||||||
self.setup()
|
self.setup()
|
||||||
splash.update()
|
|
||||||
# load plugins
|
# load plugins
|
||||||
self.setupAddons()
|
self.setupAddons()
|
||||||
splash.update()
|
|
||||||
# show main window
|
# show main window
|
||||||
splash.finish(self)
|
|
||||||
self.show()
|
self.show()
|
||||||
# raise window for osx
|
# raise window for osx
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
self.raise_()
|
self.raise_()
|
||||||
# sync on program open?
|
#
|
||||||
# if self.config['syncOnProgramOpen']:
|
|
||||||
# if self.syncDeck(interactive=False):
|
|
||||||
# return
|
|
||||||
|
|
||||||
# delay load so deck errors don't cause program to close
|
|
||||||
self.progress.timer(10, lambda a=args: \
|
|
||||||
self.maybeLoadLastDeck(a),
|
|
||||||
False)
|
|
||||||
except:
|
except:
|
||||||
showInfo("Error during startup:\n%s" % traceback.format_exc())
|
showInfo("Error during startup:\n%s" % traceback.format_exc())
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.deck = None
|
self.col = None
|
||||||
self.state = None
|
self.state = None
|
||||||
self.setupThreads()
|
self.setupThreads()
|
||||||
self.setupLang()
|
self.setupLang()
|
||||||
|
@ -80,7 +68,7 @@ class AnkiQt(QMainWindow):
|
||||||
self.setupSchema()
|
self.setupSchema()
|
||||||
self.updateTitleBar()
|
self.updateTitleBar()
|
||||||
# screens
|
# screens
|
||||||
self.setupDeckBrowser()
|
#self.setupColBrowser()
|
||||||
self.setupOverview()
|
self.setupOverview()
|
||||||
self.setupReviewer()
|
self.setupReviewer()
|
||||||
|
|
||||||
|
@ -98,16 +86,16 @@ class AnkiQt(QMainWindow):
|
||||||
|
|
||||||
def _deckBrowserState(self, oldState):
|
def _deckBrowserState(self, oldState):
|
||||||
# shouldn't call this directly; call close
|
# shouldn't call this directly; call close
|
||||||
self.disableDeckMenuItems()
|
self.disableColMenuItems()
|
||||||
self.closeAllDeckWindows()
|
self.closeAllColWindows()
|
||||||
self.deckBrowser.show()
|
self.deckBrowser.show()
|
||||||
|
|
||||||
def _deckLoadingState(self, oldState):
|
def _colLoadingState(self, oldState):
|
||||||
"Run once, when deck is loaded."
|
"Run once, when col is loaded."
|
||||||
self.enableDeckMenuItems()
|
self.enableColMenuItems()
|
||||||
# ensure cwd is set if media dir exists
|
# ensure cwd is set if media dir exists
|
||||||
self.deck.media.dir()
|
self.col.media.dir()
|
||||||
runHook("deckLoading", self.deck)
|
runHook("colLoading", self.col)
|
||||||
self.moveToState("overview")
|
self.moveToState("overview")
|
||||||
|
|
||||||
def _overviewState(self, oldState):
|
def _overviewState(self, oldState):
|
||||||
|
@ -132,8 +120,8 @@ class AnkiQt(QMainWindow):
|
||||||
|
|
||||||
def reset(self, type="all", *args):
|
def reset(self, type="all", *args):
|
||||||
"Called for non-trivial edits. Rebuilds queue and updates UI."
|
"Called for non-trivial edits. Rebuilds queue and updates UI."
|
||||||
if self.deck:
|
if self.col:
|
||||||
self.deck.reset()
|
self.col.reset()
|
||||||
runHook("reset")
|
runHook("reset")
|
||||||
self.moveToState(self.state)
|
self.moveToState(self.state)
|
||||||
|
|
||||||
|
@ -216,7 +204,7 @@ title="%s">%s</button>''' % (
|
||||||
else:
|
else:
|
||||||
self.resize(500, 400)
|
self.resize(500, 400)
|
||||||
|
|
||||||
def closeAllDeckWindows(self):
|
def closeAllColWindows(self):
|
||||||
aqt.dialogs.closeAll()
|
aqt.dialogs.closeAll()
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
|
@ -281,14 +269,14 @@ title="%s">%s</button>''' % (
|
||||||
self.progress.setupDB(db)
|
self.progress.setupDB(db)
|
||||||
self.progress.start(label=_("Upgrading. Please be patient..."))
|
self.progress.start(label=_("Upgrading. Please be patient..."))
|
||||||
|
|
||||||
# Deck loading
|
# Collection loading
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def loadDeck(self, deckPath, showErrors=True):
|
def loadDeck(self, deckPath, showErrors=True):
|
||||||
"Load a deck and update the user interface."
|
"Load a deck and update the user interface."
|
||||||
self.upgrading = False
|
self.upgrading = False
|
||||||
try:
|
try:
|
||||||
self.deck = Deck(deckPath, queue=False)
|
self.col = Deck(deckPath, queue=False)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if not showErrors:
|
if not showErrors:
|
||||||
return 0
|
return 0
|
||||||
|
@ -304,117 +292,13 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
finally:
|
finally:
|
||||||
# we may have a progress window open if we were upgrading
|
# we may have a progress window open if we were upgrading
|
||||||
self.progress.finish()
|
self.progress.finish()
|
||||||
self.config.addRecentDeck(self.deck.path)
|
self.config.addRecentDeck(self.col.path)
|
||||||
self.setupMedia(self.deck)
|
self.setupMedia(self.col)
|
||||||
if not self.upgrading:
|
if not self.upgrading:
|
||||||
self.progress.setupDB(self.deck.db)
|
self.progress.setupDB(self.col.db)
|
||||||
self.moveToState("deckLoading")
|
self.moveToState("deckLoading")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def onOpen(self):
|
|
||||||
self.raiseMain()
|
|
||||||
filter = _("Deck files (*.anki)")
|
|
||||||
if self.deck:
|
|
||||||
dir = os.path.dirname(self.deck.path)
|
|
||||||
else:
|
|
||||||
dir = self.config['documentDir']
|
|
||||||
def accept(file):
|
|
||||||
ret = self.loadDeck(file)
|
|
||||||
if not ret:
|
|
||||||
showWarning(_("Unable to load file."))
|
|
||||||
self.deck = None
|
|
||||||
getFile(self, _("Open deck"), accept, filter, dir)
|
|
||||||
|
|
||||||
def maybeLoadLastDeck(self, args):
|
|
||||||
"Open the last deck if possible."
|
|
||||||
# try a command line argument if available
|
|
||||||
if args:
|
|
||||||
f = unicode(args[0], sys.getfilesystemencoding())
|
|
||||||
if os.path.exists(f):
|
|
||||||
return self.loadDeck(f)
|
|
||||||
# try recent deck paths
|
|
||||||
for path in self.config.recentDecks():
|
|
||||||
r = self.loadDeck(path, showErrors=False)
|
|
||||||
if r:
|
|
||||||
return r
|
|
||||||
self.moveToState("deckBrowser")
|
|
||||||
|
|
||||||
# Open recent
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def onSwitchToDeck(self):
|
|
||||||
diag = QDialog(self)
|
|
||||||
diag.setWindowTitle(_("Open Recent Deck"))
|
|
||||||
vbox = QVBoxLayout()
|
|
||||||
combo = QComboBox()
|
|
||||||
self.switchDecks = (
|
|
||||||
[(os.path.basename(x).replace(".anki", ""), x)
|
|
||||||
for x in self.config.recentDecks()
|
|
||||||
if not self.deck or self.deck.path != x and
|
|
||||||
os.path.exists(x)])
|
|
||||||
self.switchDecks.sort()
|
|
||||||
combo.addItems([x[0] for x in self.switchDecks])
|
|
||||||
self.connect(combo, SIGNAL("activated(int)"),
|
|
||||||
self.onSwitchActivated)
|
|
||||||
vbox.addWidget(combo)
|
|
||||||
bbox = QDialogButtonBox(QDialogButtonBox.Cancel)
|
|
||||||
self.connect(bbox, SIGNAL("rejected()"),
|
|
||||||
lambda: self.switchDeckDiag.close())
|
|
||||||
vbox.addWidget(bbox)
|
|
||||||
diag.setLayout(vbox)
|
|
||||||
diag.show()
|
|
||||||
self.app.processEvents()
|
|
||||||
combo.setFocus()
|
|
||||||
combo.showPopup()
|
|
||||||
self.switchDeckDiag = diag
|
|
||||||
diag.exec_()
|
|
||||||
|
|
||||||
def onSwitchActivated(self, idx):
|
|
||||||
self.switchDeckDiag.close()
|
|
||||||
self.loadDeck(self.switchDecks[idx][1])
|
|
||||||
|
|
||||||
# New deck
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def onNew(self, path=None, prompt=None):
|
|
||||||
self.raiseMain()
|
|
||||||
self.close()
|
|
||||||
register = not path
|
|
||||||
bad = ":/\\"
|
|
||||||
name = _("mydeck")
|
|
||||||
if not path:
|
|
||||||
if not prompt:
|
|
||||||
prompt = _("Please give your deck a name:")
|
|
||||||
while 1:
|
|
||||||
name = getOnlyText(
|
|
||||||
prompt, default=name, title=_("New Deck"))
|
|
||||||
if not name:
|
|
||||||
self.moveToState("deckBrowser")
|
|
||||||
return
|
|
||||||
found = False
|
|
||||||
for c in bad:
|
|
||||||
if c in name:
|
|
||||||
showInfo(
|
|
||||||
_("Sorry, '%s' can't be used in deck names.") % c)
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if found:
|
|
||||||
continue
|
|
||||||
if not name.endswith(".anki"):
|
|
||||||
name += ".anki"
|
|
||||||
break
|
|
||||||
path = os.path.join(self.config['documentDir'], name)
|
|
||||||
if os.path.exists(path):
|
|
||||||
if askUser(_("That deck already exists. Overwrite?"),
|
|
||||||
defaultno=True):
|
|
||||||
os.unlink(path)
|
|
||||||
else:
|
|
||||||
self.moveToState("deckBrowser")
|
|
||||||
return
|
|
||||||
self.loadDeck(path)
|
|
||||||
if register:
|
|
||||||
self.config.addRecentDeck(self.deck.path)
|
|
||||||
|
|
||||||
# Closing
|
# Closing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -428,53 +312,19 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
|
|
||||||
def close(self, showBrowser=True):
|
def close(self, showBrowser=True):
|
||||||
"Close current deck."
|
"Close current deck."
|
||||||
if not self.deck:
|
if not self.col:
|
||||||
return
|
return
|
||||||
# if we were cramming, restore the standard scheduler
|
# if we were cramming, restore the standard scheduler
|
||||||
if self.deck.stdSched():
|
if self.col.stdSched():
|
||||||
self.deck.reset()
|
self.col.reset()
|
||||||
runHook("deckClosing")
|
runHook("deckClosing")
|
||||||
print "focusOut() should be handled with deckClosing now"
|
print "focusOut() should be handled with deckClosing now"
|
||||||
self.closeAllDeckWindows()
|
self.closeAllDeckWindows()
|
||||||
self.deck.close()
|
self.col.close()
|
||||||
self.deck = None
|
self.col = None
|
||||||
if showBrowser:
|
if showBrowser:
|
||||||
self.moveToState("deckBrowser")
|
self.moveToState("deckBrowser")
|
||||||
|
|
||||||
# Downloading
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def onOpenOnline(self):
|
|
||||||
return showInfo("not yet implemented")
|
|
||||||
self.raiseMain()
|
|
||||||
self.ensureSyncParams()
|
|
||||||
self.close()
|
|
||||||
# we need a disk-backed file for syncing
|
|
||||||
path = namedtmp(u"untitled.anki")
|
|
||||||
self.onNew(path=path)
|
|
||||||
# ensure all changes come to us
|
|
||||||
self.deck.modified = 0
|
|
||||||
self.deck.db.commit()
|
|
||||||
self.deck.syncName = u"something"
|
|
||||||
self.deck.lastLoaded = self.deck.modified
|
|
||||||
if self.config['syncUsername'] and self.config['syncPassword']:
|
|
||||||
if self.syncDeck(onlyMerge=True, reload=2, interactive=False):
|
|
||||||
return
|
|
||||||
self.deck = None
|
|
||||||
self.browserLastRefreshed = 0
|
|
||||||
self.moveToState("initial")
|
|
||||||
|
|
||||||
def onGetSharedDeck(self):
|
|
||||||
return showInfo("not yet implemented")
|
|
||||||
self.raiseMain()
|
|
||||||
aqt.getshared.GetShared(self, 0)
|
|
||||||
self.browserLastRefreshed = 0
|
|
||||||
|
|
||||||
def onGetSharedPlugin(self):
|
|
||||||
return showInfo("not yet implemented")
|
|
||||||
self.raiseMain()
|
|
||||||
aqt.getshared.GetShared(self, 1)
|
|
||||||
|
|
||||||
# Syncing
|
# Syncing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -496,33 +346,6 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
def setupStyle(self):
|
def setupStyle(self):
|
||||||
applyStyles(self)
|
applyStyles(self)
|
||||||
|
|
||||||
# Renaming
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
def onRename(self):
|
|
||||||
"Rename deck."
|
|
||||||
dir = os.path.dirname(self.deck.path)
|
|
||||||
path = QFileDialog.getSaveFileName(self, _("Rename Deck"),
|
|
||||||
dir,
|
|
||||||
_("Deck files (*.anki)"),
|
|
||||||
options=QFileDialog.DontConfirmOverwrite)
|
|
||||||
path = unicode(path)
|
|
||||||
if not path:
|
|
||||||
return
|
|
||||||
if not path.lower().endswith(".anki"):
|
|
||||||
path += ".anki"
|
|
||||||
if os.path.abspath(path) == os.path.abspath(self.deck.path):
|
|
||||||
return
|
|
||||||
if os.path.exists(path):
|
|
||||||
if not askUser(
|
|
||||||
"Selected file exists. Overwrite it?"):
|
|
||||||
return
|
|
||||||
old = self.deck.path
|
|
||||||
self.deck.rename(path)
|
|
||||||
self.config.addRecentDeck(path)
|
|
||||||
self.config.delRecentDeck(old)
|
|
||||||
return path
|
|
||||||
|
|
||||||
# App exit
|
# App exit
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -556,6 +379,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def setupToolbar(self):
|
def setupToolbar(self):
|
||||||
|
print "setup toolbar"
|
||||||
|
return
|
||||||
frm = self.form
|
frm = self.form
|
||||||
tb = frm.toolBar
|
tb = frm.toolBar
|
||||||
tb.addAction(frm.actionAddcards)
|
tb.addAction(frm.actionAddcards)
|
||||||
|
@ -615,31 +440,31 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
|
|
||||||
def onSuspend(self):
|
def onSuspend(self):
|
||||||
self.checkpoint(_("Suspend"))
|
self.checkpoint(_("Suspend"))
|
||||||
self.deck.sched.suspendCards([self.reviewer.card.id])
|
self.col.sched.suspendCards([self.reviewer.card.id])
|
||||||
self.reviewer.nextCard()
|
self.reviewer.nextCard()
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self):
|
||||||
self.checkpoint(_("Delete"))
|
self.checkpoint(_("Delete"))
|
||||||
self.deck.remCards([self.reviewer.card.id])
|
self.col.remCards([self.reviewer.card.id])
|
||||||
self.reviewer.nextCard()
|
self.reviewer.nextCard()
|
||||||
|
|
||||||
def onBuryNote(self):
|
def onBuryNote(self):
|
||||||
self.checkpoint(_("Bury"))
|
self.checkpoint(_("Bury"))
|
||||||
self.deck.sched.buryNote(self.reviewer.card.nid)
|
self.col.sched.buryNote(self.reviewer.card.nid)
|
||||||
self.reviewer.nextCard()
|
self.reviewer.nextCard()
|
||||||
|
|
||||||
# Undo & autosave
|
# Undo & autosave
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def onUndo(self):
|
def onUndo(self):
|
||||||
self.deck.undo()
|
self.col.undo()
|
||||||
self.reset()
|
self.reset()
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
|
||||||
def maybeEnableUndo(self):
|
def maybeEnableUndo(self):
|
||||||
if self.deck and self.deck.undoName():
|
if self.col and self.col.undoName():
|
||||||
self.form.actionUndo.setText(_("Undo %s") %
|
self.form.actionUndo.setText(_("Undo %s") %
|
||||||
self.deck.undoName())
|
self.col.undoName())
|
||||||
self.form.actionUndo.setEnabled(True)
|
self.form.actionUndo.setEnabled(True)
|
||||||
runHook("undoState", True)
|
runHook("undoState", True)
|
||||||
else:
|
else:
|
||||||
|
@ -647,11 +472,11 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
runHook("undoState", False)
|
runHook("undoState", False)
|
||||||
|
|
||||||
def checkpoint(self, name):
|
def checkpoint(self, name):
|
||||||
self.deck.save(name)
|
self.col.save(name)
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
|
||||||
def autosave(self):
|
def autosave(self):
|
||||||
self.deck.autosave()
|
self.col.autosave()
|
||||||
self.maybeEnableUndo()
|
self.maybeEnableUndo()
|
||||||
|
|
||||||
# Other menu operations
|
# Other menu operations
|
||||||
|
@ -660,7 +485,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
def onAddCard(self):
|
def onAddCard(self):
|
||||||
aqt.dialogs.open("AddCards", self)
|
aqt.dialogs.open("AddCards", self)
|
||||||
|
|
||||||
def onEditDeck(self):
|
def onBrowse(self):
|
||||||
aqt.dialogs.open("Browser", self)
|
aqt.dialogs.open("Browser", self)
|
||||||
|
|
||||||
def onEditCurrent(self):
|
def onEditCurrent(self):
|
||||||
|
@ -720,16 +545,16 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
|
||||||
|
|
||||||
def onImport(self):
|
def onImport(self):
|
||||||
return showInfo("not yet implemented")
|
return showInfo("not yet implemented")
|
||||||
if self.deck is None:
|
if self.col is None:
|
||||||
self.onNew(prompt=_("""\
|
self.onNew(prompt=_("""\
|
||||||
Importing copies cards to the current deck,
|
Importing copies cards to the current deck,
|
||||||
and since you have no deck open, we need to
|
and since you have no deck open, we need to
|
||||||
create a new deck first.
|
create a new deck first.
|
||||||
|
|
||||||
Please choose a new deck name:"""))
|
Please choose a new deck name:"""))
|
||||||
if not self.deck:
|
if not self.col:
|
||||||
return
|
return
|
||||||
if self.deck.path:
|
if self.col.path:
|
||||||
aqt.importing.ImportDialog(self)
|
aqt.importing.ImportDialog(self)
|
||||||
|
|
||||||
def onExport(self):
|
def onExport(self):
|
||||||
|
@ -779,7 +604,6 @@ Please choose a new deck name:"""))
|
||||||
"Close",
|
"Close",
|
||||||
"Addcards",
|
"Addcards",
|
||||||
"Editdeck",
|
"Editdeck",
|
||||||
"DeckProperties",
|
|
||||||
"Undo",
|
"Undo",
|
||||||
"Export",
|
"Export",
|
||||||
"Stats",
|
"Stats",
|
||||||
|
@ -793,20 +617,12 @@ Please choose a new deck name:"""))
|
||||||
def setupMenus(self):
|
def setupMenus(self):
|
||||||
m = self.form
|
m = self.form
|
||||||
s = SIGNAL("triggered()")
|
s = SIGNAL("triggered()")
|
||||||
self.connect(m.actionNew, s, self.onNew)
|
#self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin)
|
||||||
self.connect(m.actionOpenOnline, s, self.onOpenOnline)
|
|
||||||
self.connect(m.actionDownloadSharedDeck, s, self.onGetSharedDeck)
|
|
||||||
self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin)
|
|
||||||
self.connect(m.actionOpenRecent, s, self.onSwitchToDeck)
|
|
||||||
self.connect(m.actionOpen, s, self.onOpen)
|
|
||||||
self.connect(m.actionRename, s, self.onRename)
|
|
||||||
self.connect(m.actionClose, s, self.onClose)
|
|
||||||
self.connect(m.actionExit, s, self, SLOT("close()"))
|
self.connect(m.actionExit, s, self, SLOT("close()"))
|
||||||
self.connect(m.actionSyncdeck, s, self.onSync)
|
self.connect(m.actionSync, s, self.onSync)
|
||||||
self.connect(m.actionDeckProperties, s, self.onDeckOpts)
|
|
||||||
self.connect(m.actionModels, s, self.onModels)
|
self.connect(m.actionModels, s, self.onModels)
|
||||||
self.connect(m.actionAddcards, s, self.onAddCard)
|
self.connect(m.actionAdd, s, self.onAddCard)
|
||||||
self.connect(m.actionEditdeck, s, self.onEditDeck)
|
self.connect(m.actionBrowse, s, self.onBrowse)
|
||||||
self.connect(m.actionEditCurrent, s, self.onEditCurrent)
|
self.connect(m.actionEditCurrent, s, self.onEditCurrent)
|
||||||
self.connect(m.actionPreferences, s, self.onPrefs)
|
self.connect(m.actionPreferences, s, self.onPrefs)
|
||||||
self.connect(m.actionStats, s, self.onStats)
|
self.connect(m.actionStats, s, self.onStats)
|
||||||
|
@ -822,8 +638,6 @@ Please choose a new deck name:"""))
|
||||||
self.connect(m.actionUndo, s, self.onUndo)
|
self.connect(m.actionUndo, s, self.onUndo)
|
||||||
self.connect(m.actionFullDatabaseCheck, s, self.onCheckDB)
|
self.connect(m.actionFullDatabaseCheck, s, self.onCheckDB)
|
||||||
self.connect(m.actionCheckMediaDatabase, s, self.onCheckMediaDB)
|
self.connect(m.actionCheckMediaDatabase, s, self.onCheckMediaDB)
|
||||||
self.connect(m.actionDownloadMissingMedia, s, self.onDownloadMissingMedia)
|
|
||||||
self.connect(m.actionLocalizeMedia, s, self.onLocalizeMedia)
|
|
||||||
self.connect(m.actionStudyOptions, s, self.onStudyOptions)
|
self.connect(m.actionStudyOptions, s, self.onStudyOptions)
|
||||||
self.connect(m.actionOverview, s, self.onOverview)
|
self.connect(m.actionOverview, s, self.onOverview)
|
||||||
self.connect(m.actionGroups, s, self.onGroups)
|
self.connect(m.actionGroups, s, self.onGroups)
|
||||||
|
@ -833,7 +647,7 @@ Please choose a new deck name:"""))
|
||||||
|
|
||||||
def enableDeckMenuItems(self, enabled=True):
|
def enableDeckMenuItems(self, enabled=True):
|
||||||
"setEnabled deck-related items."
|
"setEnabled deck-related items."
|
||||||
for item in self.deckRelatedMenuItems:
|
for item in self.colRelatedMenuItems:
|
||||||
getattr(self.form, "action" + item).setEnabled(enabled)
|
getattr(self.form, "action" + item).setEnabled(enabled)
|
||||||
self.form.menuAdvanced.setEnabled(enabled)
|
self.form.menuAdvanced.setEnabled(enabled)
|
||||||
if not enabled:
|
if not enabled:
|
||||||
|
@ -923,111 +737,111 @@ haven't been synced here yet. Continue?"""))
|
||||||
# Media locations
|
# Media locations
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def setupMedia(self, deck):
|
# def setupMedia(self, deck):
|
||||||
print "setup media"
|
# print "setup media"
|
||||||
return
|
# return
|
||||||
prefix = self.config['mediaLocation']
|
# prefix = self.config['mediaLocation']
|
||||||
prev = deck.getVar("mediaLocation") or ""
|
# prev = deck.getVar("mediaLocation") or ""
|
||||||
# set the media prefix
|
# # set the media prefix
|
||||||
if not prefix:
|
# if not prefix:
|
||||||
next = ""
|
# next = ""
|
||||||
elif prefix == "dropbox":
|
# elif prefix == "dropbox":
|
||||||
p = self.dropboxFolder()
|
# p = self.dropboxFolder()
|
||||||
next = os.path.join(p, "Public", "Anki")
|
# next = os.path.join(p, "Public", "Anki")
|
||||||
else:
|
# else:
|
||||||
next = prefix
|
# next = prefix
|
||||||
# check if the media has moved
|
# # check if the media has moved
|
||||||
migrateFrom = None
|
# migrateFrom = None
|
||||||
if prev != next:
|
# if prev != next:
|
||||||
# check if they were using plugin
|
# # check if they were using plugin
|
||||||
if not prev:
|
# if not prev:
|
||||||
p = self.dropboxFolder()
|
# p = self.dropboxFolder()
|
||||||
p = os.path.join(p, "Public")
|
# p = os.path.join(p, "Public")
|
||||||
deck.mediaPrefix = p
|
# deck.mediaPrefix = p
|
||||||
migrateFrom = deck.mediaDir()
|
# migrateFrom = deck.mediaDir()
|
||||||
if not migrateFrom:
|
# if not migrateFrom:
|
||||||
# find the old location
|
# # find the old location
|
||||||
deck.mediaPrefix = prev
|
# deck.mediaPrefix = prev
|
||||||
dir = deck.mediaDir()
|
# dir = deck.mediaDir()
|
||||||
if dir and os.listdir(dir):
|
# if dir and os.listdir(dir):
|
||||||
# it contains files; we'll need to migrate
|
# # it contains files; we'll need to migrate
|
||||||
migrateFrom = dir
|
# migrateFrom = dir
|
||||||
# setup new folder
|
# # setup new folder
|
||||||
deck.mediaPrefix = next
|
# deck.mediaPrefix = next
|
||||||
if migrateFrom:
|
# if migrateFrom:
|
||||||
# force creation of new folder
|
# # force creation of new folder
|
||||||
dir = deck.mediaDir(create=True)
|
# dir = deck.mediaDir(create=True)
|
||||||
# migrate old files
|
# # migrate old files
|
||||||
self.migrateMedia(migrateFrom, dir)
|
# self.migrateMedia(migrateFrom, dir)
|
||||||
else:
|
# else:
|
||||||
# chdir if dir exists
|
# # chdir if dir exists
|
||||||
dir = deck.mediaDir()
|
# dir = deck.mediaDir()
|
||||||
# update location
|
# # update location
|
||||||
deck.setVar("mediaLocation", next, mod=False)
|
# deck.setVar("mediaLocation", next, mod=False)
|
||||||
if dir and prefix == "dropbox":
|
# if dir and prefix == "dropbox":
|
||||||
self.setupDropbox(deck)
|
# self.setupDropbox(deck)
|
||||||
|
|
||||||
def migrateMedia(self, from_, to):
|
# def migrateMedia(self, from_, to):
|
||||||
if from_ == to:
|
# if from_ == to:
|
||||||
return
|
# return
|
||||||
files = os.listdir(from_)
|
# files = os.listdir(from_)
|
||||||
skipped = False
|
# skipped = False
|
||||||
for f in files:
|
# for f in files:
|
||||||
src = os.path.join(from_, f)
|
# src = os.path.join(from_, f)
|
||||||
dst = os.path.join(to, f)
|
# dst = os.path.join(to, f)
|
||||||
if not os.path.isfile(src):
|
# if not os.path.isfile(src):
|
||||||
skipped = True
|
# skipped = True
|
||||||
continue
|
# continue
|
||||||
if not os.path.exists(dst):
|
# if not os.path.exists(dst):
|
||||||
shutil.copy2(src, dst)
|
# shutil.copy2(src, dst)
|
||||||
if not skipped:
|
# if not skipped:
|
||||||
# everything copied, we can remove old folder
|
# # everything copied, we can remove old folder
|
||||||
shutil.rmtree(from_, ignore_errors=True)
|
# shutil.rmtree(from_, ignore_errors=True)
|
||||||
|
|
||||||
def dropboxFolder(self):
|
# def dropboxFolder(self):
|
||||||
try:
|
# try:
|
||||||
import aqt.dropbox as db
|
# import aqt.dropbox as db
|
||||||
p = db.getPath()
|
# p = db.getPath()
|
||||||
except:
|
# except:
|
||||||
if isWin:
|
# if isWin:
|
||||||
s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
|
# s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
|
||||||
s.beginGroup("CurrentVersion/Explorer/Shell Folders")
|
# s.beginGroup("CurrentVersion/Explorer/Shell Folders")
|
||||||
p = os.path.join(s.value("Personal"), "My Dropbox")
|
# p = os.path.join(s.value("Personal"), "My Dropbox")
|
||||||
else:
|
# else:
|
||||||
p = os.path.expanduser("~/Dropbox")
|
# p = os.path.expanduser("~/Dropbox")
|
||||||
return p
|
# return p
|
||||||
|
|
||||||
def setupDropbox(self, deck):
|
# def setupDropbox(self, deck):
|
||||||
if not self.config['dropboxPublicFolder']:
|
# if not self.config['dropboxPublicFolder']:
|
||||||
# put a file in the folder
|
# # put a file in the folder
|
||||||
open(os.path.join(
|
# open(os.path.join(
|
||||||
deck.mediaPrefix, "right-click-me.txt"), "w").write("")
|
# deck.mediaPrefix, "right-click-me.txt"), "w").write("")
|
||||||
# tell user what to do
|
# # tell user what to do
|
||||||
showInfo(_("""\
|
# showInfo(_("""\
|
||||||
A file called right-click-me.txt has been placed in DropBox's public folder. \
|
# A file called right-click-me.txt has been placed in DropBox's public folder. \
|
||||||
After clicking OK, this folder will appear. Please right click on the file (\
|
# After clicking OK, this folder will appear. Please right click on the file (\
|
||||||
command+click on a Mac), choose DropBox>Copy Public Link, and paste the \
|
# command+click on a Mac), choose DropBox>Copy Public Link, and paste the \
|
||||||
link into Anki."""))
|
# link into Anki."""))
|
||||||
# open folder and text prompt
|
# # open folder and text prompt
|
||||||
self.onOpenPluginFolder(deck.mediaPrefix)
|
# self.onOpenPluginFolder(deck.mediaPrefix)
|
||||||
txt = getText(_("Paste path here:"), parent=self)
|
# txt = getText(_("Paste path here:"), parent=self)
|
||||||
if txt[0]:
|
# if txt[0]:
|
||||||
fail = False
|
# fail = False
|
||||||
if not txt[0].lower().startswith("http"):
|
# if not txt[0].lower().startswith("http"):
|
||||||
fail = True
|
# fail = True
|
||||||
if not txt[0].lower().endswith("right-click-me.txt"):
|
# if not txt[0].lower().endswith("right-click-me.txt"):
|
||||||
fail = True
|
# fail = True
|
||||||
if fail:
|
# if fail:
|
||||||
showInfo(_("""\
|
# showInfo(_("""\
|
||||||
That doesn't appear to be a public link. You'll be asked again when the deck \
|
# That doesn't appear to be a public link. You'll be asked again when the deck \
|
||||||
is next loaded."""))
|
# is next loaded."""))
|
||||||
else:
|
# else:
|
||||||
self.config['dropboxPublicFolder'] = os.path.dirname(txt[0])
|
# self.config['dropboxPublicFolder'] = os.path.dirname(txt[0])
|
||||||
if self.config['dropboxPublicFolder']:
|
# if self.config['dropboxPublicFolder']:
|
||||||
# update media url
|
# # update media url
|
||||||
deck.setVar(
|
# deck.setVar(
|
||||||
"mediaURL", self.config['dropboxPublicFolder'] + "/" +
|
# "mediaURL", self.config['dropboxPublicFolder'] + "/" +
|
||||||
os.path.basename(deck.mediaDir()) + "/")
|
# os.path.basename(deck.mediaDir()) + "/")
|
||||||
|
|
||||||
# Advanced features
|
# Advanced features
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1041,7 +855,7 @@ Any changes on the server since your last sync will be lost.<br><br>
|
||||||
<b>This operation is not undoable.</b> Proceed?""")):
|
<b>This operation is not undoable.</b> Proceed?""")):
|
||||||
return
|
return
|
||||||
self.progress.start(immediate=True)
|
self.progress.start(immediate=True)
|
||||||
ret = self.deck.fixIntegrity()
|
ret = self.col.fixIntegrity()
|
||||||
self.progress.finish()
|
self.progress.finish()
|
||||||
showText(ret)
|
showText(ret)
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -1074,7 +888,7 @@ doubt."""))
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.progress.start(immediate=True)
|
self.progress.start(immediate=True)
|
||||||
(nohave, unused) = self.deck.media.check(delete)
|
(nohave, unused) = self.col.media.check(delete)
|
||||||
self.progress.finish()
|
self.progress.finish()
|
||||||
# generate report
|
# generate report
|
||||||
report = ""
|
report = ""
|
||||||
|
@ -1095,36 +909,6 @@ doubt."""))
|
||||||
report = _("No unused or missing files found.")
|
report = _("No unused or missing files found.")
|
||||||
showText(report, parent=self, type="text")
|
showText(report, parent=self, type="text")
|
||||||
|
|
||||||
def onDownloadMissingMedia(self):
|
|
||||||
res = downloadMissing(self.deck)
|
|
||||||
if res is None:
|
|
||||||
showInfo(_("No media URL defined for this deck."),
|
|
||||||
help="MediaSupport")
|
|
||||||
return
|
|
||||||
if res[0] == True:
|
|
||||||
# success
|
|
||||||
(grabbed, missing) = res[1:]
|
|
||||||
msg = _("%d successfully retrieved.") % grabbed
|
|
||||||
if missing:
|
|
||||||
msg += "\n" + ngettext("%d missing.", "%d missing.", missing) % missing
|
|
||||||
else:
|
|
||||||
msg = _("Unable to download %s\nDownload aborted.") % res[1]
|
|
||||||
showInfo(msg)
|
|
||||||
|
|
||||||
def onLocalizeMedia(self):
|
|
||||||
if not askUser(_("""\
|
|
||||||
This will look for remote images and sounds on your cards, download them to \
|
|
||||||
your media folder, and convert the links to local ones. \
|
|
||||||
It can take a long time. Proceed?""")):
|
|
||||||
return
|
|
||||||
res = downloadRemote(self.deck)
|
|
||||||
count = len(res[0])
|
|
||||||
msg = ngettext("%d successfully downloaded.",
|
|
||||||
"%d successfully downloaded.", count) % count
|
|
||||||
if len(res[1]):
|
|
||||||
msg += "\n\n" + _("Couldn't find:") + "\n" + "\n".join(res[1])
|
|
||||||
aqt.utils.showText(msg, parent=self, type="text")
|
|
||||||
|
|
||||||
# System specific code
|
# System specific code
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1133,7 +917,7 @@ It can take a long time. Proceed?""")):
|
||||||
addHook("macLoadEvent", self.onMacLoad)
|
addHook("macLoadEvent", self.onMacLoad)
|
||||||
if isMac:
|
if isMac:
|
||||||
qt_mac_set_menubar_icons(False)
|
qt_mac_set_menubar_icons(False)
|
||||||
self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
|
#self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
|
||||||
# mac users expect a minimize option
|
# mac users expect a minimize option
|
||||||
self.minimizeShortcut = QShortcut("Ctrl+m", self)
|
self.minimizeShortcut = QShortcut("Ctrl+m", self)
|
||||||
self.connect(self.minimizeShortcut, SIGNAL("activated()"),
|
self.connect(self.minimizeShortcut, SIGNAL("activated()"),
|
||||||
|
|
151
aqt/profiles.py
Normal file
151
aqt/profiles.py
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
# Profile handling
|
||||||
|
##########################################################################
|
||||||
|
# - Saves in pickles rather than json to easily store Qt window state.
|
||||||
|
# - Saves in sqlite rather than a flat file so the config can't be corrupted
|
||||||
|
|
||||||
|
from aqt.qt import *
|
||||||
|
import os, sys, time, random, cPickle, re
|
||||||
|
from anki.db import DB
|
||||||
|
from anki.utils import isMac, isWin, intTime
|
||||||
|
|
||||||
|
metaConf = dict(
|
||||||
|
ver=0,
|
||||||
|
updates=True,
|
||||||
|
created=intTime(),
|
||||||
|
id=random.randrange(0, 2**63),
|
||||||
|
lastMsg=-1,
|
||||||
|
suppressUpdate=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
profileConf = dict(
|
||||||
|
# profile
|
||||||
|
key=None,
|
||||||
|
mainWindowGeom=None,
|
||||||
|
mainWindowState=None,
|
||||||
|
numBackups=30,
|
||||||
|
lang="en",
|
||||||
|
|
||||||
|
# editing
|
||||||
|
fullSearch=False,
|
||||||
|
searchHistory=[],
|
||||||
|
recentColours=["#000000", "#0000ff"],
|
||||||
|
stripHTML=True,
|
||||||
|
editFontFamily='Arial',
|
||||||
|
editFontSize=12,
|
||||||
|
editLineSize=20,
|
||||||
|
deleteMedia=False,
|
||||||
|
preserveKeyboard=True,
|
||||||
|
|
||||||
|
# reviewing
|
||||||
|
autoplay=True,
|
||||||
|
showDueTimes=True,
|
||||||
|
showProgress=True,
|
||||||
|
|
||||||
|
# syncing
|
||||||
|
syncKey=None,
|
||||||
|
proxyHost='',
|
||||||
|
proxyPass='',
|
||||||
|
proxyPort=8080,
|
||||||
|
proxyUser='',
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProfileManager(object):
|
||||||
|
|
||||||
|
def __init__(self, base=None, profile=None):
|
||||||
|
self.name = None
|
||||||
|
# instantiate base folder
|
||||||
|
if not base:
|
||||||
|
base = self._defaultBase()
|
||||||
|
if not os.path.exists(base):
|
||||||
|
try:
|
||||||
|
os.makedirs(base)
|
||||||
|
except:
|
||||||
|
QMessageBox.critical(
|
||||||
|
None, "Error", """\
|
||||||
|
Anki can't write to the harddisk. Please see the \
|
||||||
|
documentation for information on using a flash drive.""")
|
||||||
|
raise
|
||||||
|
self.base = base
|
||||||
|
# load database and cmdline-provided profile
|
||||||
|
self._load()
|
||||||
|
if profile:
|
||||||
|
try:
|
||||||
|
self.load(profile)
|
||||||
|
except TypeError:
|
||||||
|
raise Exception("Provided profile does not exist.")
|
||||||
|
|
||||||
|
# Profile load/save
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def profiles(self):
|
||||||
|
return [x for x in self.db.scalar("select name from profiles")
|
||||||
|
if x != "_global"]
|
||||||
|
|
||||||
|
def load(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.idself.profile = cPickle.loads(
|
||||||
|
self.db.scalar("select oid, data from profiles where name = ?", name))
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
sql = "update profiles set data = ? where name = ?"
|
||||||
|
self.db.execute(sql, cPickle.dumps(self.profile), self.name)
|
||||||
|
self.db.execute(sql, cPickle.dumps(self.meta, "_global"))
|
||||||
|
|
||||||
|
def create(self, name):
|
||||||
|
assert re.match("^[A-Za-z0-9 ]+$", name)
|
||||||
|
self.db.execute("insert into profiles values (?, ?)",
|
||||||
|
name, cPickle.dumps(profileConf))
|
||||||
|
|
||||||
|
# Folder handling
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def profileFolder(self):
|
||||||
|
return self._ensureExists(os.path.join(self.base, self.name))
|
||||||
|
|
||||||
|
def addonFolder(self):
|
||||||
|
return self._ensureExists(os.path.join(self.base, "addons"))
|
||||||
|
|
||||||
|
def backupFolder(self):
|
||||||
|
return self._ensureExists(
|
||||||
|
os.path.join(self.profileFolder(), "backups"))
|
||||||
|
|
||||||
|
def collectionPath(self):
|
||||||
|
return os.path.join(self.profileFolder(), "collection.anki2")
|
||||||
|
|
||||||
|
# Helpers
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
def _ensureExists(self, path):
|
||||||
|
if not exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def _defaultBase(self):
|
||||||
|
if isWin:
|
||||||
|
s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
|
||||||
|
s.beginGroup("CurrentVersion/Explorer/Shell Folders")
|
||||||
|
d = s.value("Personal")
|
||||||
|
return os.path.join(d, "Anki")
|
||||||
|
elif isMac:
|
||||||
|
return os.path.expanduser("~/Documents/Anki")
|
||||||
|
else:
|
||||||
|
return os.path.expanduser("~/Anki")
|
||||||
|
|
||||||
|
def _load(self):
|
||||||
|
path = os.path.join(self.base, "prefs.db")
|
||||||
|
self.db = DB(path, text=str)
|
||||||
|
self.db.execute("""
|
||||||
|
create table if not exists profiles
|
||||||
|
(name text primary key, data text not null);""")
|
||||||
|
try:
|
||||||
|
self.meta = self.loadProfile("_global")
|
||||||
|
except:
|
||||||
|
# create a default global profile
|
||||||
|
self.meta = metaConf.copy()
|
||||||
|
self.db.execute("insert into profiles values ('_global', ?)",
|
||||||
|
cPickle.dumps(metaConf))
|
||||||
|
# and save a default user profile for later (commits)
|
||||||
|
self.create("User 1")
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>436</width>
|
<width>612</width>
|
||||||
<height>333</height>
|
<height>455</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -185,7 +185,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>436</width>
|
<width>612</width>
|
||||||
<height>22</height>
|
<height>22</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -195,9 +195,8 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionSelectAll"/>
|
<addaction name="actionSelectAll"/>
|
||||||
<addaction name="actionSelectFacts"/>
|
<addaction name="actionSelectNotes"/>
|
||||||
<addaction name="actionInvertSelection"/>
|
<addaction name="actionInvertSelection"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionOptions"/>
|
<addaction name="actionOptions"/>
|
||||||
|
@ -206,7 +205,7 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Cards</string>
|
<string>Cards</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionSetGroup"/>
|
<addaction name="actionSetDeck"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionReposition"/>
|
<addaction name="actionReposition"/>
|
||||||
<addaction name="actionReschedule"/>
|
<addaction name="actionReschedule"/>
|
||||||
|
@ -221,7 +220,7 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionFind"/>
|
<addaction name="actionFind"/>
|
||||||
<addaction name="actionTags"/>
|
<addaction name="actionTags"/>
|
||||||
<addaction name="actionFact"/>
|
<addaction name="actionNote"/>
|
||||||
<addaction name="actionCardList"/>
|
<addaction name="actionCardList"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionPreviousCard"/>
|
<addaction name="actionPreviousCard"/>
|
||||||
|
@ -233,12 +232,11 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionGuide"/>
|
<addaction name="actionGuide"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuFacts">
|
<widget class="QMenu" name="menuNotes">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Facts</string>
|
<string>Notes</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionAddItems"/>
|
<addaction name="actionAddItems"/>
|
||||||
<addaction name="actionAddCards"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionToggleMark"/>
|
<addaction name="actionToggleMark"/>
|
||||||
<addaction name="actionAddTag"/>
|
<addaction name="actionAddTag"/>
|
||||||
|
@ -249,10 +247,10 @@
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionChangeModel"/>
|
<addaction name="actionChangeModel"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionDeleteFacts"/>
|
<addaction name="actionDeleteNotes"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="menuCards"/>
|
<addaction name="menuCards"/>
|
||||||
<addaction name="menuFacts"/>
|
<addaction name="menuNotes"/>
|
||||||
<addaction name="menuEdit"/>
|
<addaction name="menuEdit"/>
|
||||||
<addaction name="menuJump"/>
|
<addaction name="menuJump"/>
|
||||||
<addaction name="menu_Help"/>
|
<addaction name="menu_Help"/>
|
||||||
|
@ -263,10 +261,13 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
<size>
|
<size>
|
||||||
<width>32</width>
|
<width>24</width>
|
||||||
<height>32</height>
|
<height>24</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonIconOnly</enum>
|
||||||
|
</property>
|
||||||
<attribute name="toolBarArea">
|
<attribute name="toolBarArea">
|
||||||
<enum>TopToolBarArea</enum>
|
<enum>TopToolBarArea</enum>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
@ -274,9 +275,9 @@
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="actionAddItems"/>
|
<addaction name="actionAddItems"/>
|
||||||
<addaction name="actionAddCards"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSetGroup"/>
|
<addaction name="actionSetDeck"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionAddTag"/>
|
<addaction name="actionAddTag"/>
|
||||||
<addaction name="actionDeleteTag"/>
|
<addaction name="actionDeleteTag"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
@ -315,15 +316,6 @@
|
||||||
<string>&Delete Tags...</string>
|
<string>&Delete Tags...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAddCards">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/Anki_Card.png</normaloff>:/icons/Anki_Card.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Generate Cards...</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionReschedule">
|
<action name="actionReschedule">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
|
@ -373,7 +365,7 @@
|
||||||
<string>Ctrl+F</string>
|
<string>Ctrl+F</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionFact">
|
<action name="actionNote">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/Anki_Fact.png</normaloff>:/icons/Anki_Fact.png</iconset>
|
<normaloff>:/icons/Anki_Fact.png</normaloff>:/icons/Anki_Fact.png</iconset>
|
||||||
|
@ -433,15 +425,19 @@
|
||||||
<string>Ctrl+Shift+M</string>
|
<string>Ctrl+Shift+M</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionSelectFacts">
|
<action name="actionSelectNotes">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Select &Facts</string>
|
<string>Select &Notes</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>Ctrl+Shift+A</string>
|
<string>Ctrl+Shift+A</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionFindReplace">
|
<action name="actionFindReplace">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="icons.qrc">
|
||||||
|
<normaloff>:/icons/edit-find-replace.png</normaloff>:/icons/edit-find-replace.png</iconset>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Find and Re&place...</string>
|
<string>Find and Re&place...</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -554,17 +550,21 @@
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionFindDuplicates">
|
<action name="actionFindDuplicates">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="icons.qrc">
|
||||||
|
<normaloff>:/icons/edit-find 2.png</normaloff>:/icons/edit-find 2.png</iconset>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Find &Duplicates...</string>
|
<string>Find &Duplicates...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionSetGroup">
|
<action name="actionSetDeck">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/stock_group.png</normaloff>:/icons/stock_group.png</iconset>
|
<normaloff>:/icons/graphite_smooth_folder_noncommercial.png</normaloff>:/icons/graphite_smooth_folder_noncommercial.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Set Group...</string>
|
<string>Move to Deck...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>Ctrl+G</string>
|
<string>Ctrl+G</string>
|
||||||
|
@ -579,7 +579,7 @@
|
||||||
<string>Reposition...</string>
|
<string>Reposition...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDeleteFacts">
|
<action name="actionDeleteNotes">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/editdelete.png</normaloff>:/icons/editdelete.png</iconset>
|
<normaloff>:/icons/editdelete.png</normaloff>:/icons/editdelete.png</iconset>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
|
<file>icons/edit-find 2.png</file>
|
||||||
|
<file>icons/edit-find-replace.png</file>
|
||||||
|
<file>icons/graphite_smooth_folder_noncommercial.png</file>
|
||||||
|
<file>icons/user-identity.png</file>
|
||||||
<file>icons/layout.png</file>
|
<file>icons/layout.png</file>
|
||||||
<file>icons/generate_07.png</file>
|
<file>icons/generate_07.png</file>
|
||||||
<file>icons/view-sort-descending.png</file>
|
<file>icons/view-sort-descending.png</file>
|
||||||
|
|
BIN
designer/icons/edit-find 2.png
Normal file
BIN
designer/icons/edit-find 2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
designer/icons/edit-find-replace.png
Normal file
BIN
designer/icons/edit-find-replace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
designer/icons/graphite_smooth_folder_noncommercial.png
Normal file
BIN
designer/icons/graphite_smooth_folder_noncommercial.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
BIN
designer/icons/user-identity.png
Normal file
BIN
designer/icons/user-identity.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -41,7 +41,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="deckField"/>
|
<widget class="QComboBox" name="colField"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="fileField"/>
|
<widget class="QComboBox" name="fileField"/>
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>fileField</tabstop>
|
<tabstop>fileField</tabstop>
|
||||||
<tabstop>deckField</tabstop>
|
<tabstop>colField</tabstop>
|
||||||
<tabstop>buttonBox</tabstop>
|
<tabstop>buttonBox</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|
246
designer/main.ui
246
designer/main.ui
|
@ -58,31 +58,25 @@
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionAddcards"/>
|
<addaction name="actionAdd"/>
|
||||||
<addaction name="actionEditCurrent"/>
|
<addaction name="actionEditCurrent"/>
|
||||||
<addaction name="actionEditdeck"/>
|
<addaction name="actionBrowse"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionMarkCard"/>
|
<addaction name="actionMarkCard"/>
|
||||||
<addaction name="actionBuryFact"/>
|
<addaction name="actionBuryNote"/>
|
||||||
<addaction name="actionSuspendCard"/>
|
<addaction name="actionSuspendCard"/>
|
||||||
<addaction name="actionDelete"/>
|
<addaction name="actionDelete"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuDeck">
|
<widget class="QMenu" name="menuCol">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&File</string>
|
<string>&File</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionNew"/>
|
<addaction name="actionSwitchProfile"/>
|
||||||
<addaction name="actionOpen"/>
|
|
||||||
<addaction name="actionOpenRecent"/>
|
|
||||||
<addaction name="actionOpenOnline"/>
|
|
||||||
<addaction name="actionDownloadSharedDeck"/>
|
|
||||||
<addaction name="actionImport"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionRename"/>
|
<addaction name="actionImport"/>
|
||||||
<addaction name="actionSyncdeck"/>
|
|
||||||
<addaction name="actionExport"/>
|
<addaction name="actionExport"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionClose"/>
|
<addaction name="actionSync"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionExit"/>
|
<addaction name="actionExit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -90,24 +84,15 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Tools</string>
|
<string>&Tools</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuAdvanced">
|
<addaction name="actionDecks"/>
|
||||||
<property name="title">
|
|
||||||
<string>Ad&vanced</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionFullDatabaseCheck"/>
|
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionCheckMediaDatabase"/>
|
|
||||||
<addaction name="actionDownloadMissingMedia"/>
|
|
||||||
<addaction name="actionLocalizeMedia"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
</widget>
|
|
||||||
<addaction name="actionStats"/>
|
<addaction name="actionStats"/>
|
||||||
<addaction name="actionCstats"/>
|
<addaction name="actionCstats"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionRepeatAudio"/>
|
<addaction name="actionRepeatAudio"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menuAdvanced"/>
|
<addaction name="actionFullDatabaseCheck"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="actionCheckMediaDatabase"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menu_Settings">
|
<widget class="QMenu" name="menu_Settings">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -141,12 +126,12 @@
|
||||||
<addaction name="actionModels"/>
|
<addaction name="actionModels"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionStudyOptions"/>
|
<addaction name="actionStudyOptions"/>
|
||||||
<addaction name="actionDeckProperties"/>
|
<addaction name="actionColProperties"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menuPlugins"/>
|
<addaction name="menuPlugins"/>
|
||||||
<addaction name="actionPreferences"/>
|
<addaction name="actionPreferences"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="menuDeck"/>
|
<addaction name="menuCol"/>
|
||||||
<addaction name="menuEdit"/>
|
<addaction name="menuEdit"/>
|
||||||
<addaction name="menuTools"/>
|
<addaction name="menuTools"/>
|
||||||
<addaction name="menu_Settings"/>
|
<addaction name="menu_Settings"/>
|
||||||
|
@ -157,26 +142,6 @@
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QToolBar" name="toolBar">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>32</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<attribute name="toolBarArea">
|
|
||||||
<enum>TopToolBarArea</enum>
|
|
||||||
</attribute>
|
|
||||||
<attribute name="toolBarBreak">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
<action name="actionExit">
|
<action name="actionExit">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
|
@ -189,67 +154,7 @@
|
||||||
<string>Ctrl+Q</string>
|
<string>Ctrl+Q</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionNew">
|
<action name="actionSync">
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-new.png</normaloff>:/icons/document-new.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&New</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+N</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOpen">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-open.png</normaloff>:/icons/document-open.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Open...</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+O</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionClose">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/view_text.png</normaloff>:/icons/view_text.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Close Deck</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+W</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcutContext">
|
|
||||||
<enum>Qt::ApplicationShortcut</enum>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionSave">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-save.png</normaloff>:/icons/document-save.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>&Save</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Save this deck now</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+S</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcutContext">
|
|
||||||
<enum>Qt::ApplicationShortcut</enum>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionSyncdeck">
|
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/multisynk.png</normaloff>:/icons/multisynk.png</iconset>
|
<normaloff>:/icons/multisynk.png</normaloff>:/icons/multisynk.png</iconset>
|
||||||
|
@ -264,13 +169,13 @@
|
||||||
<string>Ctrl+S</string>
|
<string>Ctrl+S</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionAddcards">
|
<action name="actionAdd">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
|
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Add...</string>
|
<string>&Add Note...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string/>
|
<string/>
|
||||||
|
@ -279,7 +184,7 @@
|
||||||
<string>A</string>
|
<string>A</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEditdeck">
|
<action name="actionBrowse">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/find.png</normaloff>:/icons/find.png</iconset>
|
<normaloff>:/icons/find.png</normaloff>:/icons/find.png</iconset>
|
||||||
|
@ -339,13 +244,13 @@
|
||||||
<string>Shift+C</string>
|
<string>Shift+C</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDeckProperties">
|
<action name="actionColProperties">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/contents.png</normaloff>:/icons/contents.png</iconset>
|
<normaloff>:/icons/contents.png</normaloff>:/icons/contents.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Deck Options...</string>
|
<string>&Col Options...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Customize models, syncing and scheduling</string>
|
<string>Customize models, syncing and scheduling</string>
|
||||||
|
@ -360,7 +265,7 @@
|
||||||
<string>&Import...</string>
|
<string>&Import...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Import cards into the current deck</string>
|
<string>Import cards into collection</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>Ctrl+I</string>
|
<string>Ctrl+I</string>
|
||||||
|
@ -390,7 +295,7 @@
|
||||||
<string>Expor&t...</string>
|
<string>Expor&t...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Save cards in a new deck or text file for sharing with others</string>
|
<string>Export data to a text file or deck</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>Ctrl+T</string>
|
<string>Ctrl+T</string>
|
||||||
|
@ -405,7 +310,7 @@
|
||||||
<normaloff>:/icons/rating.png</normaloff>:/icons/rating.png</iconset>
|
<normaloff>:/icons/rating.png</normaloff>:/icons/rating.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Mark Fact</string>
|
<string>&Mark Note</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string/>
|
<string/>
|
||||||
|
@ -462,25 +367,13 @@
|
||||||
<string>Ctrl+Z</string>
|
<string>Ctrl+Z</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionRename">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-save-as.png</normaloff>:/icons/document-save-as.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Rena&me...</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Save this deck, giving it a new name</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionCheckMediaDatabase">
|
<action name="actionCheckMediaDatabase">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/text-speak.png</normaloff>:/icons/text-speak.png</iconset>
|
<normaloff>:/icons/text-speak.png</normaloff>:/icons/text-speak.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Check &Media Database...</string>
|
<string>Check &Media...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Check the files in the media directory</string>
|
<string>Check the files in the media directory</string>
|
||||||
|
@ -507,7 +400,7 @@
|
||||||
<normaloff>:/icons/edit-rename.png</normaloff>:/icons/edit-rename.png</iconset>
|
<normaloff>:/icons/edit-rename.png</normaloff>:/icons/edit-rename.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Edit Current...</string>
|
<string>&Edit Note...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string/>
|
<string/>
|
||||||
|
@ -545,37 +438,7 @@
|
||||||
<normaloff>:/icons/emblem-favorite.png</normaloff>:/icons/emblem-favorite.png</iconset>
|
<normaloff>:/icons/emblem-favorite.png</normaloff>:/icons/emblem-favorite.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Donate...</string>
|
<string>&Support Anki...</string>
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOpenOnline">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-open-remote.png</normaloff>:/icons/document-open-remote.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Open Synced...</string>
|
|
||||||
</property>
|
|
||||||
<property name="iconText">
|
|
||||||
<string>AnkiWeb</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Download a deck that you synced from another computer</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDownloadSharedDeck">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/download.png</normaloff>:/icons/download.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Open Shared...</string>
|
|
||||||
</property>
|
|
||||||
<property name="iconText">
|
|
||||||
<string>Download</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Download a deck that people have shared publicly</string>
|
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDownloadSharedPlugin">
|
<action name="actionDownloadSharedPlugin">
|
||||||
|
@ -586,16 +449,16 @@
|
||||||
<string>Download a plugin to add new features or change Anki's behaviour</string>
|
<string>Download a plugin to add new features or change Anki's behaviour</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionBuryFact">
|
<action name="actionBuryNote">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
<normaloff>:/icons/khtml_kget.png</normaloff>:/icons/khtml_kget.png</iconset>
|
<normaloff>:/icons/khtml_kget.png</normaloff>:/icons/khtml_kget.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Bury Fact</string>
|
<string>&Bury Note</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="statusTip">
|
<property name="statusTip">
|
||||||
<string>Suspend the current fact until the deck is closed and opened again</string>
|
<string>Suspend the current note until Anki is reopened</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>Shift+B</string>
|
<string>Shift+B</string>
|
||||||
|
@ -607,28 +470,7 @@
|
||||||
<normaloff>:/icons/sqlitebrowser.png</normaloff>:/icons/sqlitebrowser.png</iconset>
|
<normaloff>:/icons/sqlitebrowser.png</normaloff>:/icons/sqlitebrowser.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Check Database...</string>
|
<string>&Optimize...</string>
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOpenRecent">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/document-open-recent.png</normaloff>:/icons/document-open-recent.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Open &Recent...</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+R</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDownloadMissingMedia">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="icons.qrc">
|
|
||||||
<normaloff>:/icons/download.png</normaloff>:/icons/download.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Download Missing Media</string>
|
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionEditLayout">
|
<action name="actionEditLayout">
|
||||||
|
@ -643,11 +485,6 @@
|
||||||
<string>L</string>
|
<string>L</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionLocalizeMedia">
|
|
||||||
<property name="text">
|
|
||||||
<string>Localize Media</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOverview">
|
<action name="actionOverview">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="icons.qrc">
|
<iconset resource="icons.qrc">
|
||||||
|
@ -681,13 +518,38 @@
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionDocumentation">
|
<action name="actionDocumentation">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="icons.qrc">
|
||||||
|
<normaloff>:/icons/help-hint.png</normaloff>:/icons/help-hint.png</iconset>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Documentation...</string>
|
<string>&Guide...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string>F1</string>
|
<string>F1</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionSwitchProfile">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="icons.qrc">
|
||||||
|
<normaloff>:/icons/user-identity.png</normaloff>:/icons/user-identity.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Profile...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionDecks">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="icons.qrc">
|
||||||
|
<normaloff>:/icons/graphite_smooth_folder_noncommercial.png</normaloff>:/icons/graphite_smooth_folder_noncommercial.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Decks...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="icons.qrc"/>
|
<include location="icons.qrc"/>
|
||||||
|
|
Loading…
Reference in a new issue