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/"
appHelpSite="http://ankisrs.net/docs/dev/"
appDonate="http://ankisrs.net/support/"
modDir=os.path.dirname(os.path.abspath(__file__))
runningDir=os.path.split(modDir)[0]
mw = None # set on init
moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
# py2exe
if hasattr(sys, "frozen"):
sys.path.append(modDir)
modDir = os.path.dirname(sys.argv[0])
# if hasattr(sys, "frozen"):
# sys.path.append(moduleDir)
def openHelp(name):
if "#" in name:
@ -60,46 +60,6 @@ class DialogManager(object):
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
##########################################################################
@ -116,52 +76,26 @@ def run():
global mw
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
rd = runningDir
if isMac and getattr(sys, 'frozen', None):
rd = os.path.abspath(runningDir + "/../../..")
rd = os.path.abspath(moduleDir + "/../../..")
QCoreApplication.setLibraryPaths([rd])
# create the app
app = AnkiApp(sys.argv)
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
import optparse
parser = optparse.OptionParser()
parser.usage = "%prog [<deck.anki>]"
parser.add_option("-c", "--config", help="path to config dir",
default=os.path.expanduser("~/.anki"))
parser.usage = "%prog [OPTIONS]"
parser.add_option("-b", "--base", help="Path to base folder")
parser.add_option("-p", "--profile", help="Profile name to load")
(opts, args) = parser.parse_args(sys.argv[1:])
# setup config
import aqt.config
conf = aqt.config.Config(
unicode(os.path.abspath(opts.config), sys.getfilesystemencoding()))
# profile manager
from aqt.profiles import ProfileManager
pm = ProfileManager(opts.base, opts.profile)
# qt translations
translationPath = ''
@ -175,10 +109,8 @@ def run():
qtTranslator.load("qt_" + short, translationPath):
app.installTranslator(qtTranslator)
# load main window
splash.update()
import aqt.main
mw = aqt.main.AnkiQt(app, conf, args, splash)
mw = aqt.main.AnkiQt(app, pm)
app.exec_()
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,\
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
Wuerges, Emmanuel Jarri, Frank Harper, H. Mijail, Ian Lewis, Iroiro, Jin
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
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
def setupNewNote(self, set=True):
f = self.mw.deck.newNote()
f = self.mw.col.newNote()
f.tags = f.model()['tags']
if set:
self.editor.setNote(f)
@ -104,7 +104,7 @@ class AddCards(QDialog):
if not note or not note.id:
return
# 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):
txt = stripHTMLMedia(",".join(note.fields))[:30]
@ -131,7 +131,7 @@ class AddCards(QDialog):
"Some fields are missing or not unique."),
help="AddItems#AddError")
return
cards = self.mw.deck.addNote(note)
cards = self.mw.col.addNote(note)
if not cards:
showWarning(_("""\
The input you have provided would make an empty
@ -151,7 +151,7 @@ question or answer on all cards."""), help="AddItems")
# stop anything playing
clearAudioQueue()
self.onReset(keep=True)
self.mw.deck.autosave()
self.mw.col.autosave()
def keyPressEvent(self, evt):
"Show answer on RET or register answer."

View file

@ -28,14 +28,14 @@ COLOUR_MARKED2 = "#aaaaff"
# Data model
##########################################################################
class DeckModel(QAbstractTableModel):
class DataModel(QAbstractTableModel):
def __init__(self, browser):
QAbstractTableModel.__init__(self)
self.browser = browser
self.deck = browser.deck
self.col = browser.col
self.sortKey = None
self.activeCols = self.deck.conf.get(
self.activeCols = self.col.conf.get(
"activeCols", ["noteFld", "template", "cardDue", "cardEase"])
self.cards = []
self.cardObjs = {}
@ -43,7 +43,7 @@ class DeckModel(QAbstractTableModel):
def getCard(self, index):
id = self.cards[index.row()]
if not id in self.cardObjs:
self.cardObjs[id] = self.deck.getCard(id)
self.cardObjs[id] = self.col.getCard(id)
return self.cardObjs[id]
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
# old data first
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)
if reset:
self.endReset()
@ -206,7 +206,7 @@ class DeckModel(QAbstractTableModel):
return self.answer()
elif type == "noteFld":
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":
return c.template()['name']
elif type == "cardDue":
@ -230,9 +230,9 @@ class DeckModel(QAbstractTableModel):
return _("(new)")
return "%d%%" % (c.factor/10)
elif type == "cardGroup":
return self.browser.mw.deck.groups.name(c.gid)
return self.browser.mw.col.groups.name(c.gid)
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):
return self.formatQA(c.a())
@ -255,7 +255,7 @@ class DeckModel(QAbstractTableModel):
elif c.queue == 1:
date = c.due
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:
return _("(susp.)")
return time.strftime("%Y-%m-%d", time.localtime(date))
@ -301,7 +301,7 @@ class Browser(QMainWindow):
QMainWindow.__init__(self, None)
#applyStyles(self)
self.mw = mw
self.deck = self.mw.deck
self.col = self.mw.col
self.currentRow = None
self.lastFilter = ""
self.form = aqt.forms.browser.Ui_Dialog()
@ -385,7 +385,7 @@ class Browser(QMainWindow):
saveGeom(self, "editor")
saveState(self, "editor")
saveHeader(self.form.tableView.horizontalHeader(), "editor")
self.deck.conf['activeCols'] = self.model.activeCols
self.col.conf['activeCols'] = self.model.activeCols
self.hide()
aqt.dialogs.close("Browser")
self.teardownHooks()
@ -467,7 +467,7 @@ class Browser(QMainWindow):
cur) % {
"cur": cur,
"sel": ngettext("%d selected", "%d selected", selected) % selected
} + " - " + self.deck.name())
} + " - " + self.col.name())
return selected
def onReset(self):
@ -478,7 +478,7 @@ class Browser(QMainWindow):
######################################################################
def setupTable(self):
self.model = DeckModel(self)
self.model = DataModel(self)
self.form.tableView.setSortingEnabled(True)
self.form.tableView.setShowGrid(False)
self.form.tableView.setModel(self.model)
@ -537,28 +537,28 @@ class Browser(QMainWindow):
if type in noSort:
showInfo(_("Sorting on this column is not supported. Please "
"choose another."))
type = self.deck.conf['sortType']
if self.deck.conf['sortType'] != type:
self.deck.conf['sortType'] = type
type = self.col.conf['sortType']
if self.col.conf['sortType'] != type:
self.col.conf['sortType'] = type
# default to descending for non-text fields
if type == "noteFld":
ord = not ord
self.deck.conf['sortBackwards'] = ord
self.col.conf['sortBackwards'] = ord
self.onSearch()
else:
if self.deck.conf['sortBackwards'] != ord:
self.deck.conf['sortBackwards'] = ord
if self.col.conf['sortBackwards'] != ord:
self.col.conf['sortBackwards'] = ord
self.model.reverse()
self.setSortIndicator()
def setSortIndicator(self):
hh = self.form.tableView.horizontalHeader()
type = self.deck.conf['sortType']
type = self.col.conf['sortType']
if type not in self.model.activeCols:
hh.setSortIndicatorShown(False)
return
idx = self.model.activeCols.index(type)
if self.deck.conf['sortBackwards']:
if self.col.conf['sortBackwards']:
ord = Qt.DescendingOrder
else:
ord = Qt.AscendingOrder
@ -649,7 +649,7 @@ class Browser(QMainWindow):
self.onSearch()
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(
m['name'], lambda m=m: self.setFilter("model", m['name']))
mitem.setIcon(0, QIcon(":/icons/product_design.png"))
@ -662,7 +662,7 @@ class Browser(QMainWindow):
mitem.addChild(titem)
def _groupTree(self, root):
grps = self.deck.sched.groupTree()
grps = self.col.sched.groupTree()
def fillGroups(root, grps, head=""):
for g in grps:
item = self.CallbackItem(
@ -694,7 +694,7 @@ class Browser(QMainWindow):
return root
def _userTagTree(self, root):
for t in sorted(self.deck.tags.all()):
for t in sorted(self.col.tags.all()):
item = self.CallbackItem(
t, lambda t=t: self.setFilter("tag", t))
item.setIcon(0, QIcon(":/icons/anki-tag.png"))
@ -706,7 +706,7 @@ class Browser(QMainWindow):
def setupCardInfo(self):
from anki.stats import CardStats
self.card = None
self.cardStats = CardStats(self.deck, None)
self.cardStats = CardStats(self.col, None)
self.connect(self.form.cardLabel,
SIGNAL("linkActivated(const QString&)"),
self.onCardLink)
@ -717,7 +717,7 @@ class Browser(QMainWindow):
rep = "<style>table * { font-size: 12px; }</style>" + rep
m = self.card.model()
# 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(
_("Sort Field"), "<a href=sort>%s</a>" % sortf)
# and revlog
@ -767,7 +767,7 @@ class Browser(QMainWindow):
s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date")
s += ("<th align=right>%s</th>" * 5) % (
_("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 "
"from revlog where cid = ?", self.card.id):
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()]
def selectedNotes(self):
return self.deck.db.list("""
return self.col.db.list("""
select distinct nid from cards
where id in %s""" % ids2str(
[self.model.cards[idx.row()] for idx in
self.form.tableView.selectionModel().selectedRows()]))
def selectedNotesAsCards(self):
return self.deck.db.list(
return self.col.db.list(
"select id from cards where nid in (%s)" %
",".join([str(s) for s in self.selectedNotes()]))
@ -825,7 +825,7 @@ where id in %s""" % ids2str(
sf = self.selectedNotes()
if not sf:
return
mods = self.deck.db.scalar("""
mods = self.col.db.scalar("""
select count(distinct mid) from notes
where id in %s""" % ids2str(sf))
if mods > 1:
@ -861,7 +861,7 @@ where id in %s""" % ids2str(sf))
self.mw.checkpoint(_("Delete Cards"))
self.model.beginReset()
oldRow = self.form.tableView.selectionModel().currentIndex().row()
self.deck.remCards(self.selectedCards())
self.col.remCards(self.selectedCards())
self.onSearch(reset=False)
if len(self.model.cards):
new = min(oldRow, len(self.model.cards) - 1)
@ -880,7 +880,7 @@ where id in %s""" % ids2str(sf))
from aqt.tagedit import TagEdit
te = TagEdit(d, type=1)
frm.groupBox.layout().insertWidget(0, te)
te.setDeck(self.deck)
te.setCol(self.col)
d.connect(d, SIGNAL("accepted()"), lambda: self.onSetGroup(frm, te))
self.setTabOrder(frm.setCur, te)
self.setTabOrder(te, frm.setInitial)
@ -894,16 +894,16 @@ where id in %s""" % ids2str(sf))
self.mw.checkpoint(_("Set Group"))
mod = intTime()
if frm.setCur.isChecked():
gid = self.deck.groups.id(unicode(te.text()))
self.deck.db.execute(
gid = self.col.groups.id(unicode(te.text()))
self.col.db.execute(
"update cards set mod=?, gid=? where id in " + ids2str(
self.selectedCards()), mod, gid)
if frm.setInitial.isChecked():
self.deck.db.execute(
self.col.db.execute(
"update notes set mod=?, gid=? where id in " + ids2str(
self.selectedNotes()), mod, gid)
else:
self.deck.db.execute("""
self.col.db.execute("""
update cards set mod=?, gid=(select gid from notes where id = cards.nid)
where id in %s""" % ids2str(self.selectedCards()), mod)
self.onSearch(reset=False)
@ -917,13 +917,13 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
if prompt is None:
prompt = _("Enter tags to add:")
if tags is None:
(tags, r) = getTag(self, self.deck, prompt)
(tags, r) = getTag(self, self.col, prompt)
else:
r = True
if not r:
return
if func is None:
func = self.deck.tags.bulkAdd
func = self.col.tags.bulkAdd
self.model.beginReset()
if label is None:
label = _("Add Tags")
@ -938,7 +938,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
if label is None:
label = _("Delete Tags")
self.addTags(tags, label, _("Enter tags to delete:"),
func=self.deck.tags.bulkRem)
func=self.col.tags.bulkRem)
# Suspending and marking
######################################################################
@ -955,9 +955,9 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
self.editor.saveNow()
c = self.selectedCards()
if sus:
self.deck.sched.suspendCards(c)
self.col.sched.suspendCards(c)
else:
self.deck.sched.unsuspendCards(c)
self.col.sched.unsuspendCards(c)
self.model.reset()
self.mw.requireReset()
@ -975,7 +975,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
def reposition(self):
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))
if not cids:
return showInfo(_("Only new cards can be repositioned."))
@ -983,7 +983,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
d.setWindowModality(Qt.WindowModal)
frm = aqt.forms.reposition.Ui_Dialog()
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")
txt = _("Queue top: %d") % pmin
txt += "\n" + _("Queue bottom: %d") % pmax
@ -992,7 +992,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
return
self.model.beginReset()
self.mw.checkpoint(_("Reposition"))
self.deck.sched.sortCards(
self.col.sched.sortCards(
cids, start=frm.start.value(), step=frm.step.value(),
shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked())
self.onSearch(reset=False)
@ -1012,9 +1012,9 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
self.model.beginReset()
self.mw.checkpoint(_("Reschedule"))
if frm.asNew.isChecked():
self.deck.sched.forgetCards(self.selectedCards())
self.col.sched.forgetCards(self.selectedCards())
else:
self.deck.sched.reschedCards(
self.col.sched.reschedCards(
self.selectedCards(), frm.min.value(), frm.max.value())
self.onSearch(reset=False)
self.mw.requireReset()
@ -1088,7 +1088,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
if not sf:
return
import anki.find
fields = sorted(anki.find.fieldNames(self.deck, downcase=False))
fields = sorted(anki.find.fieldNames(self.col, downcase=False))
d = QDialog(self)
frm = aqt.forms.findreplace.Ui_Dialog()
frm.setupUi(d)
@ -1106,7 +1106,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
self.mw.progress.start()
self.model.beginReset()
try:
changed = self.deck.findReplace(sf,
changed = self.col.findReplace(sf,
unicode(frm.find.text()),
unicode(frm.replace.text()),
frm.re.isChecked(),
@ -1143,12 +1143,12 @@ where id in %s""" % ids2str(self.selectedCards()), mod)
restoreGeom(win, "findDupes")
fields = sorted(self.card.note.model.fieldModels, key=attrgetter("name"))
# 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
where fm.modelId = m.id""")
data.sort(key=itemgetter(1))
# all-model data
data2 = self.deck.db.all("""
data2 = self.col.db.all("""
select fm.id, fm.name from fieldmodels fm""")
byName = {}
for d in data2:
@ -1187,9 +1187,9 @@ select fm.id, fm.name from fieldmodels fm""")
win.show()
def duplicatesReport(self, web, fmids):
self.deck.startProgress(2)
self.deck.updateProgress(_("Finding..."))
res = self.deck.findDuplicates(fmids)
self.col.startProgress(2)
self.col.updateProgress(_("Finding..."))
res = self.col.findDuplicates(fmids)
t = "<html><body>"
t += _("Duplicate Groups: %d") % len(res)
t += "<p><ol>"
@ -1202,7 +1202,7 @@ select fm.id, fm.name from fieldmodels fm""")
t += "</ol>"
t += "</body></html>"
web.setHtml(t)
self.deck.finishProgress()
self.col.finishProgress()
def dupeLinkClicked(self, link):
self.form.searchEdit.setText(link.toString())
@ -1262,7 +1262,7 @@ class GenCards(QDialog):
def getSelection(self):
# 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.items = []
for t in self.model.templates:
@ -1297,15 +1297,15 @@ class GenCards(QDialog):
mw.checkpoint(_("Generate Cards"))
mw.progress.start()
for c, nid in enumerate(self.nids):
f = mw.deck.getNote(nid)
mw.deck.genCards(f, tplates)
f = mw.col.getNote(nid)
mw.col.genCards(f, tplates)
if c % 100 == 0:
mw.progress.update()
if unused:
cids = mw.deck.db.list("""
cids = mw.col.db.list("""
select id from cards where nid in %s and ord in %s""" % (
ids2str(self.nids), ids2str(unused)))
mw.deck.remCards(cids)
mw.col.remCards(cids)
mw.progress.finish()
mw.requireReset()
self.browser.onSearch()
@ -1344,8 +1344,8 @@ class ChangeModel(QDialog):
self.form.templateMap.setLayout(self.tlayout)
# model chooser
import aqt.modelchooser
self.oldCurrentModel = self.browser.deck.conf['currentModelId']
self.browser.deck.conf['currentModelId'] = self.oldModel.id
self.oldCurrentModel = self.browser.col.conf['currentModelId']
self.browser.col.conf['currentModelId'] = self.oldModel.id
self.form.oldModelLabel.setText(self.oldModel.name)
self.modelChooser = aqt.modelchooser.ModelChooser(
self.browser.mw, self.form.modelChooserWidget, cards=False, label=False)
@ -1356,7 +1356,7 @@ class ChangeModel(QDialog):
self.pauseUpdate = False
def onReset(self):
self.modelChanged(self.browser.deck.currentModel())
self.modelChanged(self.browser.col.currentModel())
def modelChanged(self, model):
self.targetModel = model
@ -1446,7 +1446,7 @@ class ChangeModel(QDialog):
def cleanup(self):
removeHook("reset", self.onReset)
removeHook("currentModelChanged", self.onReset)
self.oldCurrentModel = self.browser.deck.conf['currentModelId']
self.oldCurrentModel = self.browser.col.conf['currentModelId']
self.modelChooser.cleanup()
saveGeom(self, "changeModel")

View file

@ -28,8 +28,8 @@ class CardLayout(QDialog):
self.note = note
self.type = type
self.ord = ord
self.deck = self.mw.deck
self.mm = self.mw.deck.models
self.col = self.mw.col
self.mm = self.mw.col.models
self.model = note.model()
self.form = aqt.forms.clayout.Ui_Dialog()
self.form.setupUi(self)
@ -50,7 +50,7 @@ class CardLayout(QDialog):
self.exec_()
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:
self.accept()
if first:
@ -220,7 +220,7 @@ class CardLayout(QDialog):
styles += "\n.cloze { font-weight: bold; color: blue; }"
self.form.preview.setHtml(
('<html><head>%s</head><body class="%s">' %
(getBase(self.deck), c.cssClass())) +
(getBase(self.col), c.cssClass())) +
"<style>" + styles + "</style>" +
mungeQA(c.q(reload=True)) +
self.maybeTextInput() +
@ -251,22 +251,22 @@ class CardLayout(QDialog):
modified = False
self.mw.startProgress()
self.deck.updateProgress(_("Applying changes..."))
self.col.updateProgress(_("Applying changes..."))
reset=True
if len(self.fieldOrdinalUpdatedIds) > 0:
self.deck.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds)
self.col.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds)
modified = True
if self.needFieldRebuild:
modified = True
if modified:
self.note.model.setModified()
self.deck.flushMod()
self.col.flushMod()
if self.noteedit and self.noteedit.onChange:
self.noteedit.onChange("all")
reset=False
if reset:
self.mw.reset()
self.deck.finishProgress()
self.col.finishProgress()
QDialog.reject(self)
def onHelp(self):
@ -358,7 +358,7 @@ class CardLayout(QDialog):
if fld['name'] != name:
self.mm.renameField(self.model, fld, name)
# 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.renderPreview()
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
from aqt.utils import maybeHideClose
class DeckOptions(QDialog):
class ColOptions(QDialog):
def __init__(self, mw):
QDialog.__init__(self, mw, Qt.Window)
self.mw = mw
self.d = mw.deck
self.form = aqt.forms.deckopts.Ui_Dialog()
self.d = mw.col
self.form = aqt.forms.colopts.Ui_Dialog()
self.form.setupUi(self)
self.setup()
self.exec_()
@ -28,7 +28,7 @@ class DeckOptions(QDialog):
self.form.mediaURL.setText(self.d.conf['mediaURL'])
def helpRequested(self):
aqt.openHelp("DeckOptions")
aqt.openHelp("ColOptions")
def reject(self):
needSync = False
@ -49,4 +49,4 @@ class DeckOptions(QDialog):
self.d.conf['mediaURL'] = url
QDialog.reject(self)
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.card.load()
r.keep = True
# we don't need to reset the deck, but there may be new groups
self.mw.deck.sched._resetConf()
# we don't need to reset the col, but there may be new groups
self.mw.col.sched._resetConf()
self.mw.moveToState("review")
saveGeom(self, "editcurrent")
self.close()

View file

@ -371,7 +371,7 @@ class Editor(object):
self.note = note
# change timer
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")),
loadCB=self._loadFinished)
self.updateTagsAndGroup()
@ -490,28 +490,28 @@ class Editor(object):
self.outerLayout.addWidget(g)
def updateTagsAndGroup(self):
if self.tags.deck != self.mw.deck:
self.tags.setDeck(self.mw.deck)
if self.tags.col != self.mw.col:
self.tags.setCol(self.mw.col)
if self.addMode:
self.group.setDeck(self.mw.deck)
self.group.setCol(self.mw.col)
self.tags.setText(self.note.stringTags().strip())
if getattr(self.note, 'gid', None):
gid = self.note.gid
else:
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):
if not self.note:
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:
# 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['gid'] = self.note.gid
m['tags'] = self.note.tags
self.mw.deck.models.save(m)
self.mw.col.models.save(m)
self.note.flush()
runHook("tagsAndGroupUpdated", self.note)
@ -696,7 +696,7 @@ class Editor(object):
def _addMedia(self, path, canDelete=False):
"Add to media folder and return basename."
# copy to media folder
name = self.mw.deck.media.addFile(path)
name = self.mw.col.media.addFile(path)
# remove original?
if canDelete and self.mw.config['deleteMedia']:
if os.path.abspath(name) != os.path.abspath(path):

View file

@ -40,7 +40,7 @@ Error was:<pre>%s</pre>""")
self.setupTable()
self.onChangeType(type)
if type == 0:
self.setWindowTitle(_("Download Shared Deck"))
self.setWindowTitle(_("Download Shared Col"))
else:
self.setWindowTitle(_("Download Shared Plugin"))
if self.ok:
@ -221,7 +221,7 @@ Error was:<pre>%s</pre>""")
tit = re.sub("[^][A-Za-z0-9 ()\-]", "", tit)
tit = tit[0:40]
if self.type == 0:
# deck
# col
dd = self.parent.config['documentDir']
p = os.path.join(dd, tit + ".anki")
if os.path.exists(p):
@ -237,7 +237,7 @@ Error was:<pre>%s</pre>""")
pass
open(os.path.join(dd, tit + ".media",
os.path.basename(l)),"wb").write(z.read(l))
self.parent.loadDeck(dpath)
self.parent.loadCol(dpath)
else:
pd = self.parent.pluginsFolder()
if z:

View file

@ -17,7 +17,7 @@ class GroupConf(QDialog):
self.gcid = gcid
self.form = aqt.forms.groupconf.Ui_Dialog()
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)
self.conf = simplejson.loads(self.conf)
self.setWindowTitle(self.name)
@ -131,7 +131,7 @@ class GroupConf(QDialog):
c['maxTaken'] = f.maxTaken.value()
# update db
self.mw.checkpoint(_("Group Options"))
self.mw.deck.db.execute(
self.mw.col.db.execute(
"update gconf set conf = ? where id = ?",
simplejson.dumps(self.conf), self.gcid)
@ -147,7 +147,7 @@ class GroupConfSelector(QDialog):
self.form.setupUi(self)
self.connect(self.form.list, SIGNAL("itemChanged(QListWidgetItem*)"),
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])
self.reload()
self.addButtons()
@ -155,7 +155,7 @@ class GroupConfSelector(QDialog):
def accept(self):
# save
self.mw.deck.db.execute(
self.mw.col.db.execute(
"update groups set gcid = ? where id in "+ids2str(self.gids),
self.gcid())
QDialog.accept(self)
@ -164,7 +164,7 @@ class GroupConfSelector(QDialog):
self.accept()
def reload(self):
self.confs = self.mw.deck.groupConfs()
self.confs = self.mw.col.groupConfs()
self.form.list.clear()
deflt = None
for c in self.confs:
@ -190,7 +190,7 @@ class GroupConfSelector(QDialog):
def onRename(self, item):
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)
def onEdit(self):
@ -198,10 +198,10 @@ class GroupConfSelector(QDialog):
def onCopy(self):
gcid = self.gcid()
gc = list(self.mw.deck.db.first("select * from gconf where id = ?", gcid))
gc[0] = self.mw.deck.nextID("gcid")
gc = list(self.mw.col.db.first("select * from gconf where id = ?", gcid))
gc[0] = self.mw.col.nextID("gcid")
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()
def onDelete(self):
@ -210,8 +210,8 @@ class GroupConfSelector(QDialog):
showInfo(_("The default configuration can't be removed."), self)
else:
self.mw.checkpoint(_("Delete Group Config"))
self.mw.deck.db.execute(
self.mw.col.db.execute(
"update groups set gcid = 1 where gcid = ?", gcid)
self.mw.deck.db.execute(
self.mw.col.db.execute(
"delete from gconf where id = ?", gcid)
self.reload()

View file

@ -27,7 +27,7 @@ class Groups(QDialog):
def reload(self):
self.mw.progress.start()
grps = self.mw.deck.sched.groupCountTree()
grps = self.mw.col.sched.groupCountTree()
self.mw.progress.finish()
self.groupMap = {}
self.fullNames = {}
@ -107,7 +107,7 @@ class Groups(QDialog):
err.append(
_("The default group can't be deleted."))
continue
self.mw.deck.delGroup(gid)
self.mw.col.delGroup(gid)
self.reload()
if err:
showInfo("\n".join(err))
@ -127,7 +127,7 @@ class Groups(QDialog):
gid = self.groupMap[cold]
cnew = self.fullNames[cold].replace(old, txt)
if gid:
self.mw.deck.db.execute(
self.mw.col.db.execute(
"update groups set name = ? where id = ?",
cnew, gid)
for i in range(item.childCount()):
@ -167,15 +167,15 @@ class Groups(QDialog):
if len(gids) == self.gidCount:
# all enabled is same as empty
gids = []
# if gids != self.mw.deck.conf['groups']:
# self.mw.deck.conf['groups'] = gids
# if gids != self.mw.col.conf['groups']:
# self.mw.col.conf['groups'] = gids
# self.mw.reset()
QDialog.accept(self)
def _makeItems(self, grps):
self.gidCount = 0
on = {}
a = self.mw.deck.groups.active()
a = self.mw.col.groups.active()
if not a:
on = None
else:
@ -196,7 +196,7 @@ class Groups(QDialog):
branch.setCheckState(COLCHECK, Qt.Unchecked)
branch.setText(COLNAME, grp[0])
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(COLDUE, str(grp[2]))
branch.setText(COLNEW, str(grp[3]))

View file

@ -61,7 +61,7 @@ class UpdateMap(QDialog):
for i in range(numFields):
self.dialog.fileField.addItem("Field %d" % (i+1))
for m in fieldModels:
self.dialog.deckField.addItem(m.name)
self.dialog.colField.addItem(m.name)
self.exec_()
def helpRequested(self):
@ -70,7 +70,7 @@ class UpdateMap(QDialog):
def accept(self):
self.updateKey = (
self.dialog.fileField.currentIndex(),
self.fieldModels[self.dialog.deckField.currentIndex()].id)
self.fieldModels[self.dialog.colField.currentIndex()].id)
QDialog.accept(self)
class ImportDialog(QDialog):
@ -95,10 +95,10 @@ class ImportDialog(QDialog):
self.exec_()
def setupOptions(self):
self.model = self.parent.deck.currentModel
self.model = self.parent.col.currentModel
self.modelChooser = ui.modelchooser.ModelChooser(self,
self.parent,
self.parent.deck,
self.parent.col,
self.modelChanged)
self.dialog.modelArea.setLayout(self.modelChooser)
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
try:
n = _("Import")
self.parent.deck.setUndoStart(n)
self.parent.col.setUndoStart(n)
try:
self.importer.doImport()
except ImportFormatError, e:
@ -211,8 +211,8 @@ you can enter it here. Use \\t to represent tab."""),
self.dialog.status.setText(msg)
return
finally:
self.parent.deck.finishProgress()
self.parent.deck.setUndoEnd(n)
self.parent.col.finishProgress()
self.parent.col.setUndoEnd(n)
txt = (
_("Importing complete. %(num)d notes imported from %(file)s.\n") %
{"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.file = None
self.maybePreview()
self.parent.deck.db.flush()
self.parent.col.db.flush()
self.parent.reset()
self.modelChooser.deinit()
@ -242,7 +242,7 @@ you can enter it here. Use \\t to represent tab."""),
def showMapping(self, keepMapping=False, hook=None):
# first, check that we can read the file
try:
self.importer = self.importerFunc(self.parent.deck, self.file)
self.importer = self.importerFunc(self.parent.col, self.file)
if hook:
hook()
if not keepMapping:

View file

@ -9,7 +9,7 @@ from operator import itemgetter
from aqt.qt import *
QtConfig = pyqtconfig.Configuration()
from anki import Deck
from anki import Collection
from anki.sound import playFromText, clearAudioQueue, stripSounds
from anki.utils import stripHTML, checksum, isWin, isMac
from anki.hooks import runHook, addHook, removeHook
@ -27,40 +27,28 @@ config = aqt.config
## models remembering the previous group
class AnkiQt(QMainWindow):
def __init__(self, app, config, args, splash):
def __init__(self, app, config, args):
QMainWindow.__init__(self)
aqt.mw = self
self.splash = splash
self.app = app
self.config = config
try:
# initialize everything
self.setup()
splash.update()
# load plugins
self.setupAddons()
splash.update()
# show main window
splash.finish(self)
self.show()
# raise window for osx
self.activateWindow()
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:
showInfo("Error during startup:\n%s" % traceback.format_exc())
sys.exit(1)
def setup(self):
self.deck = None
self.col = None
self.state = None
self.setupThreads()
self.setupLang()
@ -80,7 +68,7 @@ class AnkiQt(QMainWindow):
self.setupSchema()
self.updateTitleBar()
# screens
self.setupDeckBrowser()
#self.setupColBrowser()
self.setupOverview()
self.setupReviewer()
@ -98,16 +86,16 @@ class AnkiQt(QMainWindow):
def _deckBrowserState(self, oldState):
# shouldn't call this directly; call close
self.disableDeckMenuItems()
self.closeAllDeckWindows()
self.disableColMenuItems()
self.closeAllColWindows()
self.deckBrowser.show()
def _deckLoadingState(self, oldState):
"Run once, when deck is loaded."
self.enableDeckMenuItems()
def _colLoadingState(self, oldState):
"Run once, when col is loaded."
self.enableColMenuItems()
# ensure cwd is set if media dir exists
self.deck.media.dir()
runHook("deckLoading", self.deck)
self.col.media.dir()
runHook("colLoading", self.col)
self.moveToState("overview")
def _overviewState(self, oldState):
@ -132,8 +120,8 @@ class AnkiQt(QMainWindow):
def reset(self, type="all", *args):
"Called for non-trivial edits. Rebuilds queue and updates UI."
if self.deck:
self.deck.reset()
if self.col:
self.col.reset()
runHook("reset")
self.moveToState(self.state)
@ -216,7 +204,7 @@ title="%s">%s</button>''' % (
else:
self.resize(500, 400)
def closeAllDeckWindows(self):
def closeAllColWindows(self):
aqt.dialogs.closeAll()
# Components
@ -281,14 +269,14 @@ title="%s">%s</button>''' % (
self.progress.setupDB(db)
self.progress.start(label=_("Upgrading. Please be patient..."))
# Deck loading
# Collection loading
##########################################################################
def loadDeck(self, deckPath, showErrors=True):
"Load a deck and update the user interface."
self.upgrading = False
try:
self.deck = Deck(deckPath, queue=False)
self.col = Deck(deckPath, queue=False)
except Exception, e:
if not showErrors:
return 0
@ -304,117 +292,13 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
finally:
# we may have a progress window open if we were upgrading
self.progress.finish()
self.config.addRecentDeck(self.deck.path)
self.setupMedia(self.deck)
self.config.addRecentDeck(self.col.path)
self.setupMedia(self.col)
if not self.upgrading:
self.progress.setupDB(self.deck.db)
self.progress.setupDB(self.col.db)
self.moveToState("deckLoading")
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
##########################################################################
@ -428,53 +312,19 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def close(self, showBrowser=True):
"Close current deck."
if not self.deck:
if not self.col:
return
# if we were cramming, restore the standard scheduler
if self.deck.stdSched():
self.deck.reset()
if self.col.stdSched():
self.col.reset()
runHook("deckClosing")
print "focusOut() should be handled with deckClosing now"
self.closeAllDeckWindows()
self.deck.close()
self.deck = None
self.col.close()
self.col = None
if showBrowser:
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
##########################################################################
@ -496,33 +346,6 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def setupStyle(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
##########################################################################
@ -556,6 +379,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
##########################################################################
def setupToolbar(self):
print "setup toolbar"
return
frm = self.form
tb = frm.toolBar
tb.addAction(frm.actionAddcards)
@ -615,31 +440,31 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onSuspend(self):
self.checkpoint(_("Suspend"))
self.deck.sched.suspendCards([self.reviewer.card.id])
self.col.sched.suspendCards([self.reviewer.card.id])
self.reviewer.nextCard()
def onDelete(self):
self.checkpoint(_("Delete"))
self.deck.remCards([self.reviewer.card.id])
self.col.remCards([self.reviewer.card.id])
self.reviewer.nextCard()
def onBuryNote(self):
self.checkpoint(_("Bury"))
self.deck.sched.buryNote(self.reviewer.card.nid)
self.col.sched.buryNote(self.reviewer.card.nid)
self.reviewer.nextCard()
# Undo & autosave
##########################################################################
def onUndo(self):
self.deck.undo()
self.col.undo()
self.reset()
self.maybeEnableUndo()
def maybeEnableUndo(self):
if self.deck and self.deck.undoName():
if self.col and self.col.undoName():
self.form.actionUndo.setText(_("Undo %s") %
self.deck.undoName())
self.col.undoName())
self.form.actionUndo.setEnabled(True)
runHook("undoState", True)
else:
@ -647,11 +472,11 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
runHook("undoState", False)
def checkpoint(self, name):
self.deck.save(name)
self.col.save(name)
self.maybeEnableUndo()
def autosave(self):
self.deck.autosave()
self.col.autosave()
self.maybeEnableUndo()
# Other menu operations
@ -660,7 +485,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onAddCard(self):
aqt.dialogs.open("AddCards", self)
def onEditDeck(self):
def onBrowse(self):
aqt.dialogs.open("Browser", self)
def onEditCurrent(self):
@ -720,16 +545,16 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def onImport(self):
return showInfo("not yet implemented")
if self.deck is None:
if self.col is None:
self.onNew(prompt=_("""\
Importing copies cards to the current deck,
and since you have no deck open, we need to
create a new deck first.
Please choose a new deck name:"""))
if not self.deck:
if not self.col:
return
if self.deck.path:
if self.col.path:
aqt.importing.ImportDialog(self)
def onExport(self):
@ -779,7 +604,6 @@ Please choose a new deck name:"""))
"Close",
"Addcards",
"Editdeck",
"DeckProperties",
"Undo",
"Export",
"Stats",
@ -793,20 +617,12 @@ Please choose a new deck name:"""))
def setupMenus(self):
m = self.form
s = SIGNAL("triggered()")
self.connect(m.actionNew, s, self.onNew)
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.actionDownloadSharedPlugin, s, self.onGetSharedPlugin)
self.connect(m.actionExit, s, self, SLOT("close()"))
self.connect(m.actionSyncdeck, s, self.onSync)
self.connect(m.actionDeckProperties, s, self.onDeckOpts)
self.connect(m.actionSync, s, self.onSync)
self.connect(m.actionModels, s, self.onModels)
self.connect(m.actionAddcards, s, self.onAddCard)
self.connect(m.actionEditdeck, s, self.onEditDeck)
self.connect(m.actionAdd, s, self.onAddCard)
self.connect(m.actionBrowse, s, self.onBrowse)
self.connect(m.actionEditCurrent, s, self.onEditCurrent)
self.connect(m.actionPreferences, s, self.onPrefs)
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.actionFullDatabaseCheck, s, self.onCheckDB)
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.actionOverview, s, self.onOverview)
self.connect(m.actionGroups, s, self.onGroups)
@ -833,7 +647,7 @@ Please choose a new deck name:"""))
def enableDeckMenuItems(self, enabled=True):
"setEnabled deck-related items."
for item in self.deckRelatedMenuItems:
for item in self.colRelatedMenuItems:
getattr(self.form, "action" + item).setEnabled(enabled)
self.form.menuAdvanced.setEnabled(enabled)
if not enabled:
@ -923,111 +737,111 @@ haven't been synced here yet. Continue?"""))
# Media locations
##########################################################################
def setupMedia(self, deck):
print "setup media"
return
prefix = self.config['mediaLocation']
prev = deck.getVar("mediaLocation") or ""
# set the media prefix
if not prefix:
next = ""
elif prefix == "dropbox":
p = self.dropboxFolder()
next = os.path.join(p, "Public", "Anki")
else:
next = prefix
# check if the media has moved
migrateFrom = None
if prev != next:
# check if they were using plugin
if not prev:
p = self.dropboxFolder()
p = os.path.join(p, "Public")
deck.mediaPrefix = p
migrateFrom = deck.mediaDir()
if not migrateFrom:
# find the old location
deck.mediaPrefix = prev
dir = deck.mediaDir()
if dir and os.listdir(dir):
# it contains files; we'll need to migrate
migrateFrom = dir
# setup new folder
deck.mediaPrefix = next
if migrateFrom:
# force creation of new folder
dir = deck.mediaDir(create=True)
# migrate old files
self.migrateMedia(migrateFrom, dir)
else:
# chdir if dir exists
dir = deck.mediaDir()
# update location
deck.setVar("mediaLocation", next, mod=False)
if dir and prefix == "dropbox":
self.setupDropbox(deck)
# def setupMedia(self, deck):
# print "setup media"
# return
# prefix = self.config['mediaLocation']
# prev = deck.getVar("mediaLocation") or ""
# # set the media prefix
# if not prefix:
# next = ""
# elif prefix == "dropbox":
# p = self.dropboxFolder()
# next = os.path.join(p, "Public", "Anki")
# else:
# next = prefix
# # check if the media has moved
# migrateFrom = None
# if prev != next:
# # check if they were using plugin
# if not prev:
# p = self.dropboxFolder()
# p = os.path.join(p, "Public")
# deck.mediaPrefix = p
# migrateFrom = deck.mediaDir()
# if not migrateFrom:
# # find the old location
# deck.mediaPrefix = prev
# dir = deck.mediaDir()
# if dir and os.listdir(dir):
# # it contains files; we'll need to migrate
# migrateFrom = dir
# # setup new folder
# deck.mediaPrefix = next
# if migrateFrom:
# # force creation of new folder
# dir = deck.mediaDir(create=True)
# # migrate old files
# self.migrateMedia(migrateFrom, dir)
# else:
# # chdir if dir exists
# dir = deck.mediaDir()
# # update location
# deck.setVar("mediaLocation", next, mod=False)
# if dir and prefix == "dropbox":
# self.setupDropbox(deck)
def migrateMedia(self, from_, to):
if from_ == to:
return
files = os.listdir(from_)
skipped = False
for f in files:
src = os.path.join(from_, f)
dst = os.path.join(to, f)
if not os.path.isfile(src):
skipped = True
continue
if not os.path.exists(dst):
shutil.copy2(src, dst)
if not skipped:
# everything copied, we can remove old folder
shutil.rmtree(from_, ignore_errors=True)
# def migrateMedia(self, from_, to):
# if from_ == to:
# return
# files = os.listdir(from_)
# skipped = False
# for f in files:
# src = os.path.join(from_, f)
# dst = os.path.join(to, f)
# if not os.path.isfile(src):
# skipped = True
# continue
# if not os.path.exists(dst):
# shutil.copy2(src, dst)
# if not skipped:
# # everything copied, we can remove old folder
# shutil.rmtree(from_, ignore_errors=True)
def dropboxFolder(self):
try:
import aqt.dropbox as db
p = db.getPath()
except:
if isWin:
s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
s.beginGroup("CurrentVersion/Explorer/Shell Folders")
p = os.path.join(s.value("Personal"), "My Dropbox")
else:
p = os.path.expanduser("~/Dropbox")
return p
# def dropboxFolder(self):
# try:
# import aqt.dropbox as db
# p = db.getPath()
# except:
# if isWin:
# s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
# s.beginGroup("CurrentVersion/Explorer/Shell Folders")
# p = os.path.join(s.value("Personal"), "My Dropbox")
# else:
# p = os.path.expanduser("~/Dropbox")
# return p
def setupDropbox(self, deck):
if not self.config['dropboxPublicFolder']:
# put a file in the folder
open(os.path.join(
deck.mediaPrefix, "right-click-me.txt"), "w").write("")
# tell user what to do
showInfo(_("""\
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 (\
command+click on a Mac), choose DropBox>Copy Public Link, and paste the \
link into Anki."""))
# open folder and text prompt
self.onOpenPluginFolder(deck.mediaPrefix)
txt = getText(_("Paste path here:"), parent=self)
if txt[0]:
fail = False
if not txt[0].lower().startswith("http"):
fail = True
if not txt[0].lower().endswith("right-click-me.txt"):
fail = True
if fail:
showInfo(_("""\
That doesn't appear to be a public link. You'll be asked again when the deck \
is next loaded."""))
else:
self.config['dropboxPublicFolder'] = os.path.dirname(txt[0])
if self.config['dropboxPublicFolder']:
# update media url
deck.setVar(
"mediaURL", self.config['dropboxPublicFolder'] + "/" +
os.path.basename(deck.mediaDir()) + "/")
# def setupDropbox(self, deck):
# if not self.config['dropboxPublicFolder']:
# # put a file in the folder
# open(os.path.join(
# deck.mediaPrefix, "right-click-me.txt"), "w").write("")
# # tell user what to do
# showInfo(_("""\
# 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 (\
# command+click on a Mac), choose DropBox>Copy Public Link, and paste the \
# link into Anki."""))
# # open folder and text prompt
# self.onOpenPluginFolder(deck.mediaPrefix)
# txt = getText(_("Paste path here:"), parent=self)
# if txt[0]:
# fail = False
# if not txt[0].lower().startswith("http"):
# fail = True
# if not txt[0].lower().endswith("right-click-me.txt"):
# fail = True
# if fail:
# showInfo(_("""\
# That doesn't appear to be a public link. You'll be asked again when the deck \
# is next loaded."""))
# else:
# self.config['dropboxPublicFolder'] = os.path.dirname(txt[0])
# if self.config['dropboxPublicFolder']:
# # update media url
# deck.setVar(
# "mediaURL", self.config['dropboxPublicFolder'] + "/" +
# os.path.basename(deck.mediaDir()) + "/")
# 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?""")):
return
self.progress.start(immediate=True)
ret = self.deck.fixIntegrity()
ret = self.col.fixIntegrity()
self.progress.finish()
showText(ret)
self.reset()
@ -1074,7 +888,7 @@ doubt."""))
else:
return
self.progress.start(immediate=True)
(nohave, unused) = self.deck.media.check(delete)
(nohave, unused) = self.col.media.check(delete)
self.progress.finish()
# generate report
report = ""
@ -1095,36 +909,6 @@ doubt."""))
report = _("No unused or missing files found.")
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
##########################################################################
@ -1133,7 +917,7 @@ It can take a long time. Proceed?""")):
addHook("macLoadEvent", self.onMacLoad)
if isMac:
qt_mac_set_menubar_icons(False)
self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
#self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
# mac users expect a minimize option
self.minimizeShortcut = QShortcut("Ctrl+m", self)
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>
<x>0</x>
<y>0</y>
<width>436</width>
<height>333</height>
<width>612</width>
<height>455</height>
</rect>
</property>
<property name="windowTitle">
@ -185,7 +185,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>436</width>
<width>612</width>
<height>22</height>
</rect>
</property>
@ -195,9 +195,8 @@
</property>
<addaction name="actionUndo"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="actionSelectAll"/>
<addaction name="actionSelectFacts"/>
<addaction name="actionSelectNotes"/>
<addaction name="actionInvertSelection"/>
<addaction name="separator"/>
<addaction name="actionOptions"/>
@ -206,7 +205,7 @@
<property name="title">
<string>Cards</string>
</property>
<addaction name="actionSetGroup"/>
<addaction name="actionSetDeck"/>
<addaction name="separator"/>
<addaction name="actionReposition"/>
<addaction name="actionReschedule"/>
@ -221,7 +220,7 @@
</property>
<addaction name="actionFind"/>
<addaction name="actionTags"/>
<addaction name="actionFact"/>
<addaction name="actionNote"/>
<addaction name="actionCardList"/>
<addaction name="separator"/>
<addaction name="actionPreviousCard"/>
@ -233,12 +232,11 @@
</property>
<addaction name="actionGuide"/>
</widget>
<widget class="QMenu" name="menuFacts">
<widget class="QMenu" name="menuNotes">
<property name="title">
<string>Facts</string>
<string>Notes</string>
</property>
<addaction name="actionAddItems"/>
<addaction name="actionAddCards"/>
<addaction name="separator"/>
<addaction name="actionToggleMark"/>
<addaction name="actionAddTag"/>
@ -249,10 +247,10 @@
<addaction name="separator"/>
<addaction name="actionChangeModel"/>
<addaction name="separator"/>
<addaction name="actionDeleteFacts"/>
<addaction name="actionDeleteNotes"/>
</widget>
<addaction name="menuCards"/>
<addaction name="menuFacts"/>
<addaction name="menuNotes"/>
<addaction name="menuEdit"/>
<addaction name="menuJump"/>
<addaction name="menu_Help"/>
@ -263,10 +261,13 @@
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
@ -274,9 +275,9 @@
<bool>false</bool>
</attribute>
<addaction name="actionAddItems"/>
<addaction name="actionAddCards"/>
<addaction name="separator"/>
<addaction name="actionSetGroup"/>
<addaction name="actionSetDeck"/>
<addaction name="separator"/>
<addaction name="actionAddTag"/>
<addaction name="actionDeleteTag"/>
<addaction name="separator"/>
@ -315,15 +316,6 @@
<string>&amp;Delete Tags...</string>
</property>
</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">
<property name="icon">
<iconset resource="icons.qrc">
@ -373,7 +365,7 @@
<string>Ctrl+F</string>
</property>
</action>
<action name="actionFact">
<action name="actionNote">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/Anki_Fact.png</normaloff>:/icons/Anki_Fact.png</iconset>
@ -433,15 +425,19 @@
<string>Ctrl+Shift+M</string>
</property>
</action>
<action name="actionSelectFacts">
<action name="actionSelectNotes">
<property name="text">
<string>Select &amp;Facts</string>
<string>Select &amp;Notes</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+A</string>
</property>
</action>
<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">
<string>Find and Re&amp;place...</string>
</property>
@ -554,17 +550,21 @@
</property>
</action>
<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">
<string>Find &amp;Duplicates...</string>
</property>
</action>
<action name="actionSetGroup">
<action name="actionSetDeck">
<property name="icon">
<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 name="text">
<string>Set Group...</string>
<string>Move to Deck...</string>
</property>
<property name="shortcut">
<string>Ctrl+G</string>
@ -579,7 +579,7 @@
<string>Reposition...</string>
</property>
</action>
<action name="actionDeleteFacts">
<action name="actionDeleteNotes">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/editdelete.png</normaloff>:/icons/editdelete.png</iconset>

View file

@ -1,5 +1,9 @@
<RCC>
<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/generate_07.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>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="deckField"/>
<widget class="QComboBox" name="colField"/>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="fileField"/>
@ -75,7 +75,7 @@
</widget>
<tabstops>
<tabstop>fileField</tabstop>
<tabstop>deckField</tabstop>
<tabstop>colField</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>

View file

@ -58,31 +58,25 @@
</property>
<addaction name="actionUndo"/>
<addaction name="separator"/>
<addaction name="actionAddcards"/>
<addaction name="actionAdd"/>
<addaction name="actionEditCurrent"/>
<addaction name="actionEditdeck"/>
<addaction name="actionBrowse"/>
<addaction name="separator"/>
<addaction name="actionMarkCard"/>
<addaction name="actionBuryFact"/>
<addaction name="actionBuryNote"/>
<addaction name="actionSuspendCard"/>
<addaction name="actionDelete"/>
</widget>
<widget class="QMenu" name="menuDeck">
<widget class="QMenu" name="menuCol">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="actionNew"/>
<addaction name="actionOpen"/>
<addaction name="actionOpenRecent"/>
<addaction name="actionOpenOnline"/>
<addaction name="actionDownloadSharedDeck"/>
<addaction name="actionImport"/>
<addaction name="actionSwitchProfile"/>
<addaction name="separator"/>
<addaction name="actionRename"/>
<addaction name="actionSyncdeck"/>
<addaction name="actionImport"/>
<addaction name="actionExport"/>
<addaction name="separator"/>
<addaction name="actionClose"/>
<addaction name="actionSync"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
@ -90,24 +84,15 @@
<property name="title">
<string>&amp;Tools</string>
</property>
<widget class="QMenu" name="menuAdvanced">
<property name="title">
<string>Ad&amp;vanced</string>
</property>
<addaction name="actionFullDatabaseCheck"/>
<addaction name="actionDecks"/>
<addaction name="separator"/>
<addaction name="actionCheckMediaDatabase"/>
<addaction name="actionDownloadMissingMedia"/>
<addaction name="actionLocalizeMedia"/>
<addaction name="separator"/>
</widget>
<addaction name="actionStats"/>
<addaction name="actionCstats"/>
<addaction name="separator"/>
<addaction name="actionRepeatAudio"/>
<addaction name="separator"/>
<addaction name="menuAdvanced"/>
<addaction name="separator"/>
<addaction name="actionFullDatabaseCheck"/>
<addaction name="actionCheckMediaDatabase"/>
</widget>
<widget class="QMenu" name="menu_Settings">
<property name="title">
@ -141,12 +126,12 @@
<addaction name="actionModels"/>
<addaction name="separator"/>
<addaction name="actionStudyOptions"/>
<addaction name="actionDeckProperties"/>
<addaction name="actionColProperties"/>
<addaction name="separator"/>
<addaction name="menuPlugins"/>
<addaction name="actionPreferences"/>
</widget>
<addaction name="menuDeck"/>
<addaction name="menuCol"/>
<addaction name="menuEdit"/>
<addaction name="menuTools"/>
<addaction name="menu_Settings"/>
@ -157,26 +142,6 @@
<bool>true</bool>
</property>
</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">
<property name="icon">
<iconset resource="icons.qrc">
@ -189,67 +154,7 @@
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionNew">
<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">
<action name="actionSync">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/multisynk.png</normaloff>:/icons/multisynk.png</iconset>
@ -264,13 +169,13 @@
<string>Ctrl+S</string>
</property>
</action>
<action name="actionAddcards">
<action name="actionAdd">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
</property>
<property name="text">
<string>&amp;Add...</string>
<string>&amp;Add Note...</string>
</property>
<property name="statusTip">
<string/>
@ -279,7 +184,7 @@
<string>A</string>
</property>
</action>
<action name="actionEditdeck">
<action name="actionBrowse">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/find.png</normaloff>:/icons/find.png</iconset>
@ -339,13 +244,13 @@
<string>Shift+C</string>
</property>
</action>
<action name="actionDeckProperties">
<action name="actionColProperties">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/contents.png</normaloff>:/icons/contents.png</iconset>
</property>
<property name="text">
<string>&amp;Deck Options...</string>
<string>&amp;Col Options...</string>
</property>
<property name="statusTip">
<string>Customize models, syncing and scheduling</string>
@ -360,7 +265,7 @@
<string>&amp;Import...</string>
</property>
<property name="statusTip">
<string>Import cards into the current deck</string>
<string>Import cards into collection</string>
</property>
<property name="shortcut">
<string>Ctrl+I</string>
@ -390,7 +295,7 @@
<string>Expor&amp;t...</string>
</property>
<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 name="shortcut">
<string>Ctrl+T</string>
@ -405,7 +310,7 @@
<normaloff>:/icons/rating.png</normaloff>:/icons/rating.png</iconset>
</property>
<property name="text">
<string>&amp;Mark Fact</string>
<string>&amp;Mark Note</string>
</property>
<property name="statusTip">
<string/>
@ -462,25 +367,13 @@
<string>Ctrl+Z</string>
</property>
</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">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/text-speak.png</normaloff>:/icons/text-speak.png</iconset>
</property>
<property name="text">
<string>Check &amp;Media Database...</string>
<string>Check &amp;Media...</string>
</property>
<property name="statusTip">
<string>Check the files in the media directory</string>
@ -507,7 +400,7 @@
<normaloff>:/icons/edit-rename.png</normaloff>:/icons/edit-rename.png</iconset>
</property>
<property name="text">
<string>&amp;Edit Current...</string>
<string>&amp;Edit Note...</string>
</property>
<property name="statusTip">
<string/>
@ -545,37 +438,7 @@
<normaloff>:/icons/emblem-favorite.png</normaloff>:/icons/emblem-favorite.png</iconset>
</property>
<property name="text">
<string>&amp;Donate...</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>
<string>&amp;Support Anki...</string>
</property>
</action>
<action name="actionDownloadSharedPlugin">
@ -586,16 +449,16 @@
<string>Download a plugin to add new features or change Anki's behaviour</string>
</property>
</action>
<action name="actionBuryFact">
<action name="actionBuryNote">
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/khtml_kget.png</normaloff>:/icons/khtml_kget.png</iconset>
</property>
<property name="text">
<string>&amp;Bury Fact</string>
<string>&amp;Bury Note</string>
</property>
<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 name="shortcut">
<string>Shift+B</string>
@ -607,28 +470,7 @@
<normaloff>:/icons/sqlitebrowser.png</normaloff>:/icons/sqlitebrowser.png</iconset>
</property>
<property name="text">
<string>&amp;Check Database...</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>
<string>&amp;Optimize...</string>
</property>
</action>
<action name="actionEditLayout">
@ -643,11 +485,6 @@
<string>L</string>
</property>
</action>
<action name="actionLocalizeMedia">
<property name="text">
<string>Localize Media</string>
</property>
</action>
<action name="actionOverview">
<property name="icon">
<iconset resource="icons.qrc">
@ -681,13 +518,38 @@
</property>
</action>
<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">
<string>Documentation...</string>
<string>&amp;Guide...</string>
</property>
<property name="shortcut">
<string>F1</string>
</property>
</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>
<resources>
<include location="icons.qrc"/>