diff --git a/aqt/__init__.py b/aqt/__init__.py
index d2f011f2e..30e0dd967 100644
--- a/aqt/__init__.py
+++ b/aqt/__init__.py
@@ -5,7 +5,7 @@ import os, sys
from aqt.qt import *
appName="Anki"
-appVersion="1.99"
+appVersion="2.0-alpha2"
appWebsite="http://ankisrs.net/"
appHelpSite="http://ankisrs.net/docs/dev/"
appDonate="http://ankisrs.net/support/"
@@ -17,14 +17,6 @@ moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
# if hasattr(sys, "frozen"):
# sys.path.append(moduleDir)
-def openHelp(name):
- if "#" in name:
- name = name.split("#")
- name = name[0] + ".html#" + name[1]
- else:
- name = name + ".html"
- QDesktopServices.openUrl(QUrl(appHelpSite + name))
-
# Dialog manager - manages modeless windows
##########################################################################
diff --git a/aqt/addons.py b/aqt/addons.py
index 38fc0f592..9248bea65 100644
--- a/aqt/addons.py
+++ b/aqt/addons.py
@@ -10,6 +10,8 @@ from anki.hooks import runHook
class AddonManager(object):
def __init__(self, mw):
+ print "addons"
+ return
self.mw = mw
f = self.mw.form; s = SIGNAL("triggered()")
self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenPluginFolder)
diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py
index fdeb562e0..b62374db6 100644
--- a/aqt/deckbrowser.py
+++ b/aqt/deckbrowser.py
@@ -2,75 +2,20 @@
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-import time, os, stat, shutil, re
-from operator import itemgetter
from aqt.qt import *
-from anki import Deck
-from anki.utils import fmtTimeSpan
-from anki.hooks import addHook
-import aqt
+from aqt.utils import askUser
class DeckBrowser(object):
- "Display a list of remembered decks."
def __init__(self, mw):
self.mw = mw
self.web = mw.web
- self._browserLastRefreshed = 0
- self._decks = []
- addHook("deckClosing", self._onClose)
def show(self, _init=True):
if _init:
self.web.setLinkHandler(self._linkHandler)
- self.web.setKeyHandler(self._keyHandler)
- self._setupToolbar()
- # refresh or reorder
- self._checkDecks()
- # show
self._renderPage()
- def _onClose(self):
- # update counts
- deck = self.mw.deck
- def add(d):
- counts = deck.sched.counts()
- d['due'] = counts[1]+counts[2]
- d['new'] = counts[0]
- d['mod'] = deck.mod
- d['time'] = deck.sched.timeToday()
- d['reps'] = deck.sched.repsToday()
- d['name'] = deck.name()
- for d in self._decks:
- if d['path'] == deck.path:
- add(d)
- return
- # not found; add new
- d = {'path': deck.path, 'state': 'ok'}
- add(d)
- self._decks.append(d)
-
- # Toolbar
- ##########################################################################
-
- # we don't use the top toolbar
- def _setupToolbar(self):
- self.mw.form.toolBar.hide()
-
- # instead we have a html one under the deck list
- def _toolbar(self):
- items = [
- ("download", _("Download")),
- ("new", _("Create")),
-# ("import", _("Import")),
- ("opensel", _("Open")),
-# ("synced", _("Synced")),
- ("refresh", _("Refresh")),
- ]
- h = "".join([self.mw.button(
- link=row[0], name=row[1]) for row in items])
- return h
-
# Event handlers
##########################################################################
@@ -81,7 +26,7 @@ class DeckBrowser(object):
cmd = url
if cmd == "open":
deck = self._decks[int(arg)]
- self._loadDeck(deck)
+ self._selDeck(deck)
elif cmd == "opts":
self._optsForRow(int(arg))
elif cmd == "download":
@@ -97,177 +42,87 @@ class DeckBrowser(object):
elif cmd == "refresh":
self.refresh()
- def _keyHandler(self, evt):
- txt = evt.text()
- if ((txt >= "0" and txt <= "9") or
- (txt >= "a" and txt <= "z")):
- self._openAccel(txt)
- return True
-
- def _openAccel(self, txt):
- for d in self._decks:
- if d['accel'] == txt:
- self._loadDeck(d)
-
- def _loadDeck(self, rec):
- if rec['state'] == 'ok':
- self.mw.loadDeck(rec['path'])
+ def _selDeck(self, rec):
+ print rec
# HTML generation
##########################################################################
_css = """
.sub { color: #555; }
-a.deck { color: #000; text-decoration: none; font-size: 100%; }
+a.deck { color: #000; text-decoration: none; font-size: 16px; }
.num { text-align: right; padding: 0 5 0 5; }
-td.opts { text-align: right; white-space: nowrap; }
-td.menu { text-align: center; }
+td.opts { white-space: nowrap; }
+td.deck { width: 90% }
a { font-size: 80%; }
.extra { font-size: 90%; }
+.due { vertical-align: text-bottom; }
+table { margin: 1em; }
"""
_body = """
%(title)s
-%(tb)s
-
-
-%(rows)s
+
-
"""
def _renderPage(self):
css = self.mw.sharedCSS + self._css
- if self._decks:
- buf = ""
- max=len(self._decks)-1
- buf += " | %s | " % _("Due")
- buf += "%s | |
" % _("New")
- for c, deck in enumerate(self._decks):
- buf += self._deckRow(c, max, deck)
- self.web.stdHtml(self._body%dict(
+ tree = self._renderDeckTree(self.mw.col.sched.deckDueTree())
+ self.web.stdHtml(self._body%dict(
title=_("Decks"),
- rows=buf,
- tb=self._toolbar(),
- extra="%s
%s" % (
- self._summary(),
- _("Click a deck to open it, or type a number."))),
- css)
- else:
- self.web.stdHtml(self._body%dict(
- title=_("Welcome!"),
- rows="
%s |
"%_(
- "Click Download to get started."),
- extra="",
- tb=self._toolbar()),
- css)
+ tree=tree), css=css)
- def _deckRow(self, c, max, deck):
- buf = ""
- ok = deck['state'] == 'ok'
- def accelName(deck):
- if deck['accel']:
- return "%s. " % deck['accel']
+ def _renderDeckTree(self, nodes, depth=0):
+ if not nodes:
return ""
- if ok:
- # name/link
- buf += "%s%s | " % (
- accelName(deck),
- "%s"%(c, deck['name']))
- # due
- col = '%s | '
- if deck['due'] > 0:
- s = col % str(deck['due'])
- else:
- s = col % ""
- buf += s
- # new
- if deck['new']:
- s = str(deck['new'])
- else:
- s = ""
- buf += "%s | " % s
- else:
- # name/error
- if deck['state'] == 'missing':
- sub = _("(moved or removed)")
- elif deck['state'] == 'corrupt':
- sub = _("(corrupt)")
- elif deck['state'] == 'in use':
- sub = _("(already open)")
- else:
- sub = "unknown"
- buf += "%s%s %s | " % (
- accelName(deck),
- deck['name'],
- sub)
- # no counts
- buf += " | "
- # options
- buf += "%s | " % (
- self.mw.button(link="opts:%d"%c, name=_("Options")+'▼'))
- buf += "
"
+ buf = ""
+ for node in nodes:
+ buf += self._deckRow(node, depth)
return buf
- def _summary(self):
- # summarize
- reps = 0
- mins = 0
- revC = 0
- newC = 0
- for d in self._decks:
- if d['state']=='ok':
- reps += d['reps']
- mins += d['time']
- revC += d['due']
- newC += d['new']
- line1 = ngettext(
- "Studied %(reps)d card in %(time)s today.",
- "Studied %(reps)d cards in %(time)s today.",
- reps) % {
- 'reps': reps,
- 'time': fmtTimeSpan(mins, point=2),
- }
- rev = ngettext(
- "%d review",
- "%d reviews",
- revC) % revC
- new = ngettext("%d new card", "%d new cards", newC) % newC
- line2 = _("Due: %(rev)s, %(new)s") % {
- 'rev': rev, 'new': new}
- return line1+'
'+line2
+ def _deckRow(self, node, depth):
+ name, did, due, new, children = node
+ # due image
+ buf = "" + self._dueImg(due, new)
+ # deck link
+ buf += " %s | "% (did, name)
+ # options
+ buf += "%s |
" % self.mw.button(
+ link="opts:%d"%did, name=_("Options")+'▼')
+ # children
+ buf += self._renderDeckTree(children, depth+1)
+ return buf
+
+ def _dueImg(self, due, new):
+ if due and new:
+ i = "both"
+ elif due:
+ i = "green"
+ elif new:
+ i = "blue"
+ else:
+ i = "none"
+ return '
' % i
# Options
##########################################################################
def _optsForRow(self, n):
m = QMenu(self.mw)
- # hide
- a = m.addAction(QIcon(":/icons/edit-undo.png"), _("Hide From List"))
- a.connect(a, SIGNAL("triggered()"), lambda n=n: self._hideRow(n))
# delete
a = m.addAction(QIcon(":/icons/editdelete.png"), _("Delete"))
a.connect(a, SIGNAL("triggered()"), lambda n=n: self._deleteRow(n))
m.exec_(QCursor.pos())
- def _hideRow(self, c):
- d = self._decks[c]
- if d['state'] == "missing" or aqt.utils.askUser(_("""\
-Hide %s from the list? You can File>Open it again later.""") %
- d['name']):
- self.mw.config.delRecentDeck(d['path'])
- del self._decks[c]
- self.refresh()
-
def _deleteRow(self, c):
d = self._decks[c]
if d['state'] == 'missing':
return self._hideRow(c)
- if aqt.utils.askUser(_("""\
+ if askUser(_("""\
Delete %s? If this deck is synchronized the online version will \
not be touched.""") % d['name']):
deck = d['path']
@@ -278,71 +133,3 @@ not be touched.""") % d['name']):
pass
self.mw.config.delRecentDeck(deck)
self.refresh()
-
- # Data gathering
- ##########################################################################
-
- def _checkDecks(self):
- self._decks = []
- decks = self.mw.config.recentDecks()
- if not decks:
- return
- tx = time.time()
- self.mw.progress.start(max=len(decks))
- for c, d in enumerate(decks):
- self.mw.progress.update(_("Checking deck %(x)d of %(y)d...") % {
- 'x': c+1, 'y': len(decks)})
- base = os.path.basename(d)
- if not os.path.exists(d):
- self._decks.append({'name': base, 'state': 'missing', 'path':d})
- continue
- try:
- mod = os.stat(d)[stat.ST_MTIME]
- t = time.time()
- deck = Deck(d, queue=False, lock=False)
- counts = deck.sched.selCounts()
- dtime = deck.sched.timeToday()
- dreps = deck.sched.repsToday()
- self._decks.append({
- 'path': d,
- 'state': 'ok',
- 'name': deck.name(),
- 'due': counts[1]+counts[2],
- 'new': counts[0],
- 'mod': deck.mod,
- # these multiply deck check time by a factor of 6
- 'time': dtime,
- 'reps': dreps
- })
- deck.close(save=False)
- # reset modification time for the sake of backup systems
- try:
- os.utime(d, (mod, mod))
- except:
- # some misbehaving filesystems may fail here
- pass
- except Exception, e:
- if "locked" in unicode(e):
- state = "in use"
- else:
- state = "corrupt"
- self._decks.append({'name': base, 'state':state, 'path':d})
- self.mw.progress.finish()
- self._browserLastRefreshed = time.time()
- self._reorderDecks()
-
- def _reorderDecks(self):
- # for now, sort by deck name
- self._decks.sort(key=itemgetter('name'))
- # after the decks are sorted, assign shortcut keys to them
- for c, d in enumerate(self._decks):
- if c > 35:
- d['accel'] = None
- elif c < 9:
- d['accel'] = str(c+1)
- else:
- d['accel'] = ord('a')+(c-10)
-
- def refresh(self):
- self._browserLastRefreshed = 0
- self.show(_init=False)
diff --git a/aqt/main.py b/aqt/main.py
index 23c464abc..3616caa27 100755
--- a/aqt/main.py
+++ b/aqt/main.py
@@ -20,38 +20,29 @@ from aqt.utils import saveGeom, restoreGeom, showInfo, showWarning, \
saveState, restoreState, getOnlyText, askUser, GetTextDialog, \
askUserDialog, applyStyles, getText, showText, showCritical, getFile
-config = aqt.config
-
## fixme: open plugin folder broken on win32?
## models remembering the previous group
class AnkiQt(QMainWindow):
- def __init__(self, app, config, args):
+ def __init__(self, app, profileManager):
QMainWindow.__init__(self)
aqt.mw = self
self.app = app
- self.config = config
+ self.pm = profileManager
try:
- # initialize everything
- self.setup()
- # load plugins
+ self.setupUI()
self.setupAddons()
- # show main window
- self.show()
- # raise window for osx
- self.activateWindow()
- self.raise_()
- #
+ self.setupProfile()
except:
showInfo("Error during startup:\n%s" % traceback.format_exc())
sys.exit(1)
- def setup(self):
+ def setupUI(self):
self.col = None
self.state = None
+ self.setupLang("en") # bootstrap with english; profile will adjust
self.setupThreads()
- self.setupLang()
self.setupMainWindow()
self.setupStyle()
self.setupProxy()
@@ -61,17 +52,136 @@ class AnkiQt(QMainWindow):
self.setupErrorHandler()
self.setupSystemSpecific()
self.setupSignals()
- self.setupVersion()
self.setupAutoUpdate()
self.setupUpgrade()
self.setupCardStats()
self.setupSchema()
self.updateTitleBar()
# screens
- #self.setupColBrowser()
+ self.setupDeckBrowser()
self.setupOverview()
self.setupReviewer()
+ # Profiles
+ ##########################################################################
+
+ def setupProfile(self):
+ # profile not provided on command line?
+ if False: # not self.pm.name:
+ # if there's a single profile, load it automatically
+ profs = self.pm.profiles()
+ if len(profs) == 1:
+ try:
+ self.pm.load(profs[0])
+ except:
+ # password protected
+ pass
+ if not self.pm.name:
+ self.showProfileManager()
+ else:
+ self.loadProfile()
+
+ def showProfileManager(self):
+ d = self.profileDiag = QDialog()
+ f = self.profileForm = aqt.forms.profiles.Ui_Dialog()
+ f.setupUi(d)
+ d.connect(f.login, SIGNAL("clicked()"), self.onOpenProfile)
+ d.connect(f.quit, SIGNAL("clicked()"), lambda: sys.exit(0))
+ d.connect(f.add, SIGNAL("clicked()"), self.onAddProfile)
+ d.connect(f.delete_2, SIGNAL("clicked()"), self.onRemProfile)
+ d.connect(d, SIGNAL("rejected()"), lambda: d.close())
+ d.connect(f.profiles, SIGNAL("currentRowChanged(int)"),
+ self.onProfileRowChange)
+ self.refreshProfilesList()
+ # raise first, for osx testing
+ d.show()
+ d.activateWindow()
+ d.raise_()
+ d.exec_()
+
+ def refreshProfilesList(self):
+ f = self.profileForm
+ f.profiles.clear()
+ f.profiles.addItems(self.pm.profiles())
+ f.profiles.setCurrentRow(0)
+
+ def onProfileRowChange(self, n):
+ if n < 0:
+ # called on .clear()
+ return
+ name = self.pm.profiles()[n]
+ f = self.profileForm
+ passwd = False
+ try:
+ self.pm.load(name)
+ except:
+ passwd = True
+ f.passEdit.setShown(passwd)
+ f.passLabel.setShown(passwd)
+
+ def openProfile(self):
+ name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
+ passwd = self.profileForm.passEdit.text()
+ try:
+ self.pm.load(name, passwd)
+ except:
+ showWarning(_("Invalid password."))
+ return
+ return True
+
+ def onOpenProfile(self):
+ self.openProfile()
+ self.profileDiag.close()
+ self.loadProfile()
+ return True
+
+ def onAddProfile(self):
+ name = getOnlyText("Name:")
+ if name:
+ if name in self.pm.profiles():
+ return showWarning("Name exists.")
+ if not re.match("^[A-Za-z0-9 ]+$", name):
+ return showWarning(
+ "Only numbers, letters and spaces can be used.")
+ self.pm.create(name)
+ self.refreshProfilesList()
+
+ def onRemProfile(self):
+ profs = self.pm.profiles()
+ if len(profs) < 2:
+ return showWarning("There must be at least one profile.")
+ # password correct?
+ if not self.openProfile():
+ return
+ # sure?
+ if not askUser("""\
+All cards, notes, and media for this profile will be deleted. \
+Are you sure?"""):
+ return
+ self.pm.remove(self.pm.name)
+ self.refreshProfilesList()
+
+ def loadProfile(self):
+ self.setupLang()
+ # show main window
+ if self.pm.profile['mainWindowState']:
+ restoreGeom(self, "mainWindow")
+ restoreState(self, "mainWindow")
+ else:
+ self.resize(500, 400)
+ self.show()
+ # raise window for osx
+ self.activateWindow()
+ self.raise_()
+ # maybe sync
+ self.onSync()
+ # then load collection and launch into the deck browser
+ self.col = Collection(self.pm.collectionPath())
+ self.moveToState("deckBrowser")
+
+ def unloadProfile(self):
+ self.col = None
+
# State machine
##########################################################################
@@ -85,9 +195,8 @@ class AnkiQt(QMainWindow):
getattr(self, "_"+state+"State")(oldState, *args)
def _deckBrowserState(self, oldState):
- # shouldn't call this directly; call close
- self.disableColMenuItems()
- self.closeAllColWindows()
+ self.disableDeckMenuItems()
+ self.closeAllWindows()
self.deckBrowser.show()
def _colLoadingState(self, oldState):
@@ -163,8 +272,7 @@ class AnkiQt(QMainWindow):
sharedCSS = """
body {
-background: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#bbb));
-/*background: #eee;*/
+background: #eee;
margin: 2em;
}
a:hover { background-color: #aaa; }
@@ -198,13 +306,8 @@ title="%s">%s''' % (
self.mainLayout.setContentsMargins(0,0,0,0)
self.form.centralwidget.setLayout(self.mainLayout)
addHook("undoEnd", self.maybeEnableUndo)
- if self.config['mainWindowState']:
- restoreGeom(self, "mainWindow")
- restoreState(self, "mainWindow")
- else:
- self.resize(500, 400)
- def closeAllColWindows(self):
+ def closeAllWindows(self):
aqt.dialogs.closeAll()
# Components
@@ -223,19 +326,6 @@ title="%s">%s''' % (
import aqt.errors
self.errorHandler = aqt.errors.ErrorHandler(self)
- def setupVersion(self):
- # check if we've been updated
- if "version" not in self.config:
- # could be new user, or upgrade from older version
- # which didn't have version variable
- self.appUpdated = "first"
- elif self.config['version'] != aqt.appVersion:
- self.appUpdated = self.config['version']
- else:
- self.appUpdated = False
- if self.appUpdated:
- self.config['version'] = aqt.appVersion
-
def setupAddons(self):
import aqt.addons
self.addonManager = aqt.addons.AddonManager(self)
@@ -292,7 +382,7 @@ 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.col.path)
+ self.pm.profile.addRecentDeck(self.col.path)
self.setupMedia(self.col)
if not self.upgrading:
self.progress.setupDB(self.col.db)
@@ -319,7 +409,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
self.col.reset()
runHook("deckClosing")
print "focusOut() should be handled with deckClosing now"
- self.closeAllDeckWindows()
+ self.closeAllWindows()
self.col.close()
self.col = None
if showBrowser:
@@ -329,7 +419,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
##########################################################################
def onSync(self):
- return showInfo("not yet implemented")
+ return
+ return showInfo("sync not yet implemented")
# Tools
##########################################################################
@@ -344,7 +435,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
self.form.statusbar.showMessage(text, timeout)
def setupStyle(self):
- applyStyles(self)
+ print "applystyles"
+ #applyStyles(self)
# App exit
##########################################################################
@@ -352,11 +444,11 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
def prepareForExit(self):
"Save config and window geometry."
runHook("quit")
- self.config['mainWindowGeom'] = self.saveGeometry()
- self.config['mainWindowState'] = self.saveState()
+ self.pm.profile['mainWindowGeom'] = self.saveGeometry()
+ self.pm.profile['mainWindowState'] = self.saveState()
# save config
try:
- self.config.save()
+ self.pm.save()
except (IOError, OSError), e:
showWarning(_("Anki was unable to save your "
"configuration file:\n%s" % e))
@@ -368,7 +460,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
event.ignore()
return self.moveToState("saveEdit")
self.close(showBrowser=False)
- # if self.config['syncOnProgramOpen']:
+ # if self.pm.profile['syncOnProgramOpen']:
# self.showBrowser = False
# self.syncDeck(interactive=False)
self.prepareForExit()
@@ -392,18 +484,18 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors")
tb.addAction(frm.actionStats)
tb.addAction(frm.actionMarkCard)
tb.addAction(frm.actionRepeatAudio)
- tb.setIconSize(QSize(self.config['iconSize'],
- self.config['iconSize']))
+ tb.setIconSize(QSize(self.pm.profile['iconSize'],
+ self.pm.profile['iconSize']))
toggle = tb.toggleViewAction()
toggle.setText(_("Toggle Toolbar"))
self.connect(toggle, SIGNAL("triggered()"),
self.onToolbarToggle)
- if not self.config['showToolbar']:
+ if not self.pm.profile['showToolbar']:
tb.hide()
def onToolbarToggle(self):
tb = self.form.toolBar
- self.config['showToolbar'] = tb.isVisible()
+ self.pm.profile['showToolbar'] = tb.isVisible()
# Dockable widgets
##########################################################################
@@ -564,7 +656,7 @@ Please choose a new deck name:"""))
# Language handling
##########################################################################
- def setupLang(self):
+ def setupLang(self, force=None):
"Set the user interface language."
import locale, gettext
import anki.lang
@@ -572,15 +664,16 @@ Please choose a new deck name:"""))
locale.setlocale(locale.LC_ALL, '')
except:
pass
- languageDir=os.path.join(aqt.modDir, "locale")
+ lang = force if force else self.pm.profile["lang"]
+ languageDir=os.path.join(aqt.moduleDir, "locale")
self.languageTrans = gettext.translation('aqt', languageDir,
- languages=[self.config["interfaceLang"]],
+ languages=[lang],
fallback=True)
self.installTranslation()
if getattr(self, 'form', None):
self.form.retranslateUi(self)
- anki.lang.setLang(self.config["interfaceLang"], local=False)
- if self.config['interfaceLang'] in ("he","ar","fa"):
+ anki.lang.setLang(lang, local=False)
+ if lang in ("he","ar","fa"):
self.app.setLayoutDirection(Qt.RightToLeft)
else:
self.app.setLayoutDirection(Qt.LeftToRight)
@@ -600,10 +693,8 @@ Please choose a new deck name:"""))
##########################################################################
deckRelatedMenuItems = (
- "Rename",
- "Close",
- "Addcards",
- "Editdeck",
+ "Add",
+ "Browse",
"Undo",
"Export",
"Stats",
@@ -647,9 +738,8 @@ Please choose a new deck name:"""))
def enableDeckMenuItems(self, enabled=True):
"setEnabled deck-related items."
- for item in self.colRelatedMenuItems:
+ for item in self.deckRelatedMenuItems:
getattr(self.form, "action" + item).setEnabled(enabled)
- self.form.menuAdvanced.setEnabled(enabled)
if not enabled:
self.disableCardMenuItems()
self.maybeEnableUndo()
@@ -697,7 +787,7 @@ Please choose a new deck name:"""))
self.autoUpdate.start()
def newVerAvail(self, data):
- if self.config['suppressUpdate'] < data['latestVersion']:
+ if self.pm.profile['suppressUpdate'] < data['latestVersion']:
aqt.update.askAndUpdate(self, data)
def newMsg(self, data):
@@ -740,7 +830,7 @@ haven't been synced here yet. Continue?"""))
# def setupMedia(self, deck):
# print "setup media"
# return
-# prefix = self.config['mediaLocation']
+# prefix = self.pm.profile['mediaLocation']
# prev = deck.getVar("mediaLocation") or ""
# # set the media prefix
# if not prefix:
@@ -812,7 +902,7 @@ haven't been synced here yet. Continue?"""))
# return p
# def setupDropbox(self, deck):
-# if not self.config['dropboxPublicFolder']:
+# if not self.pm.profile['dropboxPublicFolder']:
# # put a file in the folder
# open(os.path.join(
# deck.mediaPrefix, "right-click-me.txt"), "w").write("")
@@ -836,11 +926,11 @@ haven't been synced here yet. Continue?"""))
# 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']:
+# self.pm.profile['dropboxPublicFolder'] = os.path.dirname(txt[0])
+# if self.pm.profile['dropboxPublicFolder']:
# # update media url
# deck.setVar(
-# "mediaURL", self.config['dropboxPublicFolder'] + "/" +
+# "mediaURL", self.pm.profile['dropboxPublicFolder'] + "/" +
# os.path.basename(deck.mediaDir()) + "/")
# Advanced features
@@ -913,11 +1003,10 @@ doubt."""))
##########################################################################
def setupSystemSpecific(self):
- self.setupDocumentDir()
addHook("macLoadEvent", self.onMacLoad)
if isMac:
qt_mac_set_menubar_icons(False)
- #self.setUnifiedTitleAndToolBarOnMac(self.config['showToolbar'])
+ #self.setUnifiedTitleAndToolBarOnMac(self.pm.profile['showToolbar'])
# mac users expect a minimize option
self.minimizeShortcut = QShortcut("Ctrl+m", self)
self.connect(self.minimizeShortcut, SIGNAL("activated()"),
@@ -945,40 +1034,20 @@ doubt."""))
def onMacLoad(self, fname):
self.loadDeck(fname)
- def setupDocumentDir(self):
- if self.config['documentDir']:
- return
- if isWin:
- s = QSettings(QSettings.UserScope, "Microsoft", "Windows")
- s.beginGroup("CurrentVersion/Explorer/Shell Folders")
- d = s.value("Personal")
- if os.path.exists(d):
- d = os.path.join(d, "Anki")
- else:
- d = os.path.expanduser("~/.anki/decks")
- elif isMac:
- d = os.path.expanduser("~/Documents/Anki")
- else:
- d = os.path.expanduser("~/.anki/decks")
- try:
- os.mkdir(d)
- except (OSError, IOError):
- # already exists
- pass
- self.config['documentDir'] = d
-
# Proxy support
##########################################################################
def setupProxy(self):
+ print "proxy"
+ return
import urllib2
- if self.config['proxyHost']:
+ if self.pm.profile['proxyHost']:
proxy = "http://"
- if self.config['proxyUser']:
- proxy += (self.config['proxyUser'] + ":" +
- self.config['proxyPass'] + "@")
- proxy += (self.config['proxyHost'] + ":" +
- str(self.config['proxyPort']))
+ if self.pm.profile['proxyUser']:
+ proxy += (self.pm.profile['proxyUser'] + ":" +
+ self.pm.profile['proxyPass'] + "@")
+ proxy += (self.pm.profile['proxyHost'] + ":" +
+ str(self.pm.profile['proxyPort']))
os.environ["http_proxy"] = proxy
proxy_handler = urllib2.ProxyHandler()
opener = urllib2.build_opener(proxy_handler)
diff --git a/aqt/profiles.py b/aqt/profiles.py
index a36933168..41850254a 100644
--- a/aqt/profiles.py
+++ b/aqt/profiles.py
@@ -7,9 +7,9 @@
# - 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
+import os, sys, time, random, cPickle, shutil
from anki.db import DB
-from anki.utils import isMac, isWin, intTime
+from anki.utils import isMac, isWin, intTime, checksum
metaConf = dict(
ver=0,
@@ -18,6 +18,7 @@ metaConf = dict(
id=random.randrange(0, 2**63),
lastMsg=-1,
suppressUpdate=False,
+ firstRun=True,
)
profileConf = dict(
@@ -81,23 +82,35 @@ documentation for information on using a flash drive.""")
######################################################################
def profiles(self):
- return [x for x in self.db.scalar("select name from profiles")
- if x != "_global"]
+ return sorted(
+ x for x in self.db.list("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 load(self, name, passwd=None):
+ prof = cPickle.loads(
+ self.db.scalar("select data from profiles where name = ?", name))
+ if prof['key'] and prof['key'] != self._pwhash(passwd):
+ self.name = None
+ raise Exception("Invalid password")
+ if name != "_global":
+ self.name = name
+ self.profile = prof
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"))
+ self.db.execute(sql, cPickle.dumps(self.meta), "_global")
+ self.db.commit()
def create(self, name):
- assert re.match("^[A-Za-z0-9 ]+$", name)
self.db.execute("insert into profiles values (?, ?)",
name, cPickle.dumps(profileConf))
+ self.db.commit()
+
+ def remove(self, name):
+ shutil.rmtree(self.profileFolder())
+ self.db.execute("delete from profiles where name = ?", name)
+ self.db.commit()
# Folder handling
######################################################################
@@ -119,7 +132,7 @@ documentation for information on using a flash drive.""")
######################################################################
def _ensureExists(self, path):
- if not exists(path):
+ if not os.path.exists(path):
os.makedirs(path)
return path
@@ -136,16 +149,23 @@ documentation for information on using a flash drive.""")
def _load(self):
path = os.path.join(self.base, "prefs.db")
+ new = not os.path.exists(path)
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:
+ if new:
# 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")
+ else:
+ # load previously created
+ self.meta = cPickle.loads(
+ self.db.scalar(
+ "select data from profiles where name = '_global'"))
+
+ def _pwhash(self, passwd):
+ return checksum(unicode(self.meta['id'])+unicode(passwd))
diff --git a/aqt/update.py b/aqt/update.py
index ea10825f0..a0e5a51c9 100644
--- a/aqt/update.py
+++ b/aqt/update.py
@@ -14,6 +14,8 @@ class LatestVersionFinder(QThread):
def __init__(self, main):
QThread.__init__(self)
+ print "autoupdate"
+ return
self.main = main
self.config = main.config
plat=sys.platform
@@ -27,6 +29,7 @@ class LatestVersionFinder(QThread):
self.stats = d
def run(self):
+ return
if not self.config['checkForUpdates']:
return
d = self.stats
diff --git a/aqt/utils.py b/aqt/utils.py
index db678059a..9c72c054a 100644
--- a/aqt/utils.py
+++ b/aqt/utils.py
@@ -7,6 +7,14 @@ import aqt
from anki.sound import playFromText, stripSounds
from anki.utils import call, isWin, isMac
+def openHelp(name):
+ if "#" in name:
+ name = name.split("#")
+ name = name[0] + ".html#" + name[1]
+ else:
+ name = name + ".html"
+ QDesktopServices.openUrl(QUrl(appHelpSite + name))
+
def openLink(link):
QDesktopServices.openUrl(QUrl(link))
@@ -210,7 +218,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
assert not dir or not key
if not dir:
dirkey = key+"Directory"
- dir = aqt.mw.config.get(dirkey, "")
+ dir = aqt.mw.pm.profile.get(dirkey, "")
else:
dirkey = None
d = QFileDialog(parent)
@@ -226,7 +234,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
file = unicode(list(d.selectedFiles())[0])
if dirkey:
dir = os.path.dirname(file)
- aqt.mw.config[dirkey] = dir
+ aqt.mw.pm.profile[dirkey] = dir
cb(file)
d.connect(d, SIGNAL("accepted()"), accept)
@@ -234,7 +242,7 @@ def getSaveFile(parent, title, dir, key, ext):
"Ask the user for a file to save. Use DIR as config variable."
dirkey = dir+"Directory"
file = unicode(QFileDialog.getSaveFileName(
- parent, title, aqt.mw.config.get(dirkey, ""), key,
+ parent, title, aqt.mw.pm.profile.get(dirkey, ""), key,
None, QFileDialog.DontConfirmOverwrite))
if file:
# add extension
@@ -242,7 +250,7 @@ def getSaveFile(parent, title, dir, key, ext):
file += ext
# save new default
dir = os.path.dirname(file)
- aqt.mw.config[dirkey] = dir
+ aqt.mw.pm.profile[dirkey] = dir
# check if it exists
if os.path.exists(file):
if not askUser(
@@ -253,12 +261,12 @@ def getSaveFile(parent, title, dir, key, ext):
def saveGeom(widget, key):
key += "Geom"
- aqt.mw.config[key] = widget.saveGeometry()
+ aqt.mw.pm.profile[key] = widget.saveGeometry()
def restoreGeom(widget, key, offset=None):
key += "Geom"
- if aqt.mw.config.get(key):
- widget.restoreGeometry(aqt.mw.config[key])
+ if aqt.mw.pm.profile.get(key):
+ widget.restoreGeometry(aqt.mw.pm.profile[key])
if isMac and offset:
from aqt.main import QtConfig as q
minor = (q.qt_version & 0x00ff00) >> 8
@@ -269,30 +277,30 @@ def restoreGeom(widget, key, offset=None):
def saveState(widget, key):
key += "State"
- aqt.mw.config[key] = widget.saveState()
+ aqt.mw.pm.profile[key] = widget.saveState()
def restoreState(widget, key):
key += "State"
- if aqt.mw.config.get(key):
- widget.restoreState(aqt.mw.config[key])
+ if aqt.mw.pm.profile.get(key):
+ widget.restoreState(aqt.mw.pm.profile[key])
def saveSplitter(widget, key):
key += "Splitter"
- aqt.mw.config[key] = widget.saveState()
+ aqt.mw.pm.profile[key] = widget.saveState()
def restoreSplitter(widget, key):
key += "Splitter"
- if aqt.mw.config.get(key):
- widget.restoreState(aqt.mw.config[key])
+ if aqt.mw.pm.profile.get(key):
+ widget.restoreState(aqt.mw.pm.profile[key])
def saveHeader(widget, key):
key += "Header"
- aqt.mw.config[key] = widget.saveState()
+ aqt.mw.pm.profile[key] = widget.saveState()
def restoreHeader(widget, key):
key += "Header"
- if aqt.mw.config.get(key):
- widget.restoreState(aqt.mw.config[key])
+ if aqt.mw.pm.profile.get(key):
+ widget.restoreState(aqt.mw.pm.profile[key])
def mungeQA(txt):
txt = stripSounds(txt)
@@ -302,7 +310,7 @@ def mungeQA(txt):
def applyStyles(widget):
try:
- styleFile = open(os.path.join(aqt.mw.config.confDir,
+ styleFile = open(os.path.join(aqt.mw.pm.profile.confDir,
"style.css"))
widget.setStyleSheet(styleFile.read())
except (IOError, OSError):
diff --git a/designer/icons.qrc b/designer/icons.qrc
index cc751f34d..078c24e3b 100644
--- a/designer/icons.qrc
+++ b/designer/icons.qrc
@@ -1,5 +1,9 @@
+ icons/blue.png
+ icons/both.png
+ icons/green.png
+ icons/none.png
icons/edit-find 2.png
icons/edit-find-replace.png
icons/graphite_smooth_folder_noncommercial.png
diff --git a/designer/icons/none.png b/designer/icons/none.png
new file mode 100644
index 000000000..86d14a7a7
Binary files /dev/null and b/designer/icons/none.png differ
diff --git a/designer/profiles.ui b/designer/profiles.ui
new file mode 100644
index 000000000..59c68f1ee
--- /dev/null
+++ b/designer/profiles.ui
@@ -0,0 +1,98 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 352
+ 283
+
+
+
+ Profiles
+
+
+ -
+
+
+ Profile:
+
+
+
+ -
+
+
-
+
+
-
+
+
+ -
+
+
+ Password:
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+
+
+ -
+
+
-
+
+
+ Open
+
+
+
+ -
+
+
+ Add
+
+
+
+ -
+
+
+ Delete
+
+
+
+ -
+
+
+ Quit
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+