refactor; add profile support

This commit is contained in:
Damien Elmes 2011-11-24 12:48:58 +09:00
parent 87da9f48b1
commit f4150a5df4
25 changed files with 532 additions and 1066 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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>&amp;Delete Tags...</string> <string>&amp;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>&amp;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 &amp;Facts</string> <string>Select &amp;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&amp;place...</string> <string>Find and Re&amp;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 &amp;Duplicates...</string> <string>Find &amp;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>

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

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

View file

@ -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>&amp;File</string> <string>&amp;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>&amp;Tools</string> <string>&amp;Tools</string>
</property> </property>
<widget class="QMenu" name="menuAdvanced"> <addaction name="actionDecks"/>
<property name="title">
<string>Ad&amp;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>&amp;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>&amp;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>&amp;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>&amp;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>&amp;Add...</string> <string>&amp;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>&amp;Deck Options...</string> <string>&amp;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>&amp;Import...</string> <string>&amp;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&amp;t...</string> <string>Expor&amp;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>&amp;Mark Fact</string> <string>&amp;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&amp;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 &amp;Media Database...</string> <string>Check &amp;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>&amp;Edit Current...</string> <string>&amp;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>&amp;Donate...</string> <string>&amp;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>&amp;Bury Fact</string> <string>&amp;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>&amp;Check Database...</string> <string>&amp;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 &amp;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>&amp;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>&amp;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>&amp;Decks...</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="icons.qrc"/> <include location="icons.qrc"/>