2012-12-21 08:51:59 +01:00
|
|
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
|
|
|
import os, sys, optparse, atexit, tempfile, __builtin__
|
|
|
|
from aqt.qt import *
|
|
|
|
import locale, gettext
|
|
|
|
import anki.lang
|
2012-12-21 10:04:26 +01:00
|
|
|
from anki.consts import HELP_SITE
|
|
|
|
from anki.lang import langDir
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
appVersion="2.0.3"
|
|
|
|
appWebsite="http://ankisrs.net/"
|
|
|
|
appChanges="http://ankisrs.net/docs/changes.html"
|
|
|
|
appDonate="http://ankisrs.net/support/"
|
|
|
|
appShared="https://ankiweb.net/shared/"
|
|
|
|
appUpdate="https://ankiweb.net/update/desktop"
|
2012-12-21 10:04:26 +01:00
|
|
|
appHelpSite=HELP_SITE
|
2012-12-21 08:51:59 +01:00
|
|
|
mw = None # set on init
|
|
|
|
|
|
|
|
moduleDir = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
|
|
|
|
|
|
|
|
try:
|
|
|
|
import aqt.forms
|
|
|
|
except ImportError, e:
|
|
|
|
if "forms" in str(e):
|
|
|
|
print "If you're running from git, did you run build_ui.sh?"
|
|
|
|
print
|
|
|
|
raise
|
|
|
|
|
|
|
|
# Dialog manager - manages modeless windows
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
class DialogManager(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
from aqt import addcards, browser, editcurrent
|
|
|
|
self._dialogs = {
|
|
|
|
"AddCards": [addcards.AddCards, None],
|
|
|
|
"Browser": [browser.Browser, None],
|
|
|
|
"EditCurrent": [editcurrent.EditCurrent, None],
|
|
|
|
}
|
|
|
|
|
|
|
|
def open(self, name, *args):
|
|
|
|
(creator, instance) = self._dialogs[name]
|
|
|
|
if instance:
|
|
|
|
instance.activateWindow()
|
|
|
|
instance.raise_()
|
|
|
|
return instance
|
|
|
|
else:
|
|
|
|
instance = creator(*args)
|
|
|
|
self._dialogs[name][1] = instance
|
|
|
|
return instance
|
|
|
|
|
|
|
|
def close(self, name):
|
|
|
|
self._dialogs[name] = [self._dialogs[name][0], None]
|
|
|
|
|
|
|
|
def closeAll(self):
|
|
|
|
for (n, (creator, instance)) in self._dialogs.items():
|
|
|
|
if instance:
|
|
|
|
instance.forceClose = True
|
|
|
|
instance.close()
|
|
|
|
self.close(n)
|
|
|
|
|
|
|
|
dialogs = DialogManager()
|
|
|
|
|
|
|
|
# Language handling
|
|
|
|
##########################################################################
|
|
|
|
# Qt requires its translator to be installed before any GUI widgets are
|
|
|
|
# loaded, and we need the Qt language to match the gettext language or
|
|
|
|
# translated shortcuts will not work.
|
|
|
|
|
|
|
|
_gtrans = None
|
|
|
|
_qtrans = None
|
|
|
|
|
|
|
|
def setupLang(pm, app, force=None):
|
|
|
|
global _gtrans, _qtrans
|
|
|
|
try:
|
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
lang = force or pm.meta["defaultLang"]
|
|
|
|
dir = langDir()
|
|
|
|
# gettext
|
|
|
|
_gtrans = gettext.translation(
|
2012-12-21 10:04:26 +01:00
|
|
|
'anki', dir, languages=[lang], fallback=True)
|
2012-12-21 08:51:59 +01:00
|
|
|
__builtin__.__dict__['_'] = _gtrans.ugettext
|
|
|
|
__builtin__.__dict__['ngettext'] = _gtrans.ungettext
|
|
|
|
anki.lang.setLang(lang, local=False)
|
|
|
|
if lang in ("he","ar","fa"):
|
|
|
|
app.setLayoutDirection(Qt.RightToLeft)
|
|
|
|
else:
|
|
|
|
app.setLayoutDirection(Qt.LeftToRight)
|
|
|
|
# qt
|
|
|
|
_qtrans = QTranslator()
|
|
|
|
if _qtrans.load("qt_" + lang, dir):
|
|
|
|
app.installTranslator(_qtrans)
|
|
|
|
|
|
|
|
# App initialisation
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
class AnkiApp(QApplication):
|
|
|
|
|
|
|
|
# Single instance support on Win32/Linux
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
KEY = "anki"
|
|
|
|
TMOUT = 5000
|
|
|
|
|
|
|
|
def __init__(self, argv):
|
|
|
|
QApplication.__init__(self, argv)
|
|
|
|
self._argv = argv
|
|
|
|
self._shmem = QSharedMemory(self.KEY)
|
|
|
|
self.alreadyRunning = self._shmem.attach()
|
|
|
|
|
|
|
|
def secondInstance(self):
|
|
|
|
if not self.alreadyRunning:
|
|
|
|
# use a 1 byte shared memory instance to signal we exist
|
|
|
|
if not self._shmem.create(1):
|
|
|
|
raise Exception("shared memory not supported")
|
|
|
|
atexit.register(self._shmem.detach)
|
|
|
|
# and a named pipe/unix domain socket for ipc
|
|
|
|
QLocalServer.removeServer(self.KEY)
|
|
|
|
self._srv = QLocalServer(self)
|
|
|
|
self.connect(self._srv, SIGNAL("newConnection()"), self.onRecv)
|
|
|
|
self._srv.listen(self.KEY)
|
|
|
|
else:
|
|
|
|
# we accept only one command line argument. if it's missing, send
|
|
|
|
# a blank screen to just raise the existing window
|
|
|
|
opts, args = parseArgs(self._argv)
|
|
|
|
buf = "raise"
|
|
|
|
if args and args[0]:
|
|
|
|
buf = os.path.abspath(args[0])
|
|
|
|
self.sendMsg(buf)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def sendMsg(self, txt):
|
|
|
|
sock = QLocalSocket(self)
|
|
|
|
sock.connectToServer(self.KEY, QIODevice.WriteOnly)
|
|
|
|
if not sock.waitForConnected(self.TMOUT):
|
|
|
|
raise Exception("existing instance not responding")
|
|
|
|
sock.write(txt)
|
|
|
|
if not sock.waitForBytesWritten(self.TMOUT):
|
|
|
|
raise Exception("existing instance not emptying")
|
|
|
|
sock.disconnectFromServer()
|
|
|
|
|
|
|
|
def onRecv(self):
|
|
|
|
sock = self._srv.nextPendingConnection()
|
|
|
|
if not sock.waitForReadyRead(self.TMOUT):
|
|
|
|
sys.stderr.write(sock.errorString())
|
|
|
|
return
|
|
|
|
buf = sock.readAll()
|
|
|
|
self.emit(SIGNAL("appMsg"), buf)
|
|
|
|
sock.disconnectFromServer()
|
|
|
|
|
|
|
|
# OS X file/url handler
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
def event(self, evt):
|
|
|
|
if evt.type() == QEvent.FileOpen:
|
|
|
|
self.emit(SIGNAL("appMsg"), evt.file() or "raise")
|
|
|
|
return True
|
|
|
|
return QApplication.event(self, evt)
|
|
|
|
|
|
|
|
def parseArgs(argv):
|
|
|
|
"Returns (opts, args)."
|
|
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.usage = "%prog [OPTIONS] [file to import]"
|
|
|
|
parser.add_option("-b", "--base", help="path to base folder")
|
|
|
|
parser.add_option("-p", "--profile", help="profile name to load")
|
|
|
|
parser.add_option("-l", "--lang", help="interface language (en, de, etc)")
|
|
|
|
return parser.parse_args(argv[1:])
|
|
|
|
|
|
|
|
def run():
|
|
|
|
global mw
|
|
|
|
from anki.utils import isWin, isMac
|
|
|
|
|
|
|
|
# on osx we'll need to add the qt plugins to the search path
|
|
|
|
if isMac and getattr(sys, 'frozen', None):
|
|
|
|
rd = os.path.abspath(moduleDir + "/../../..")
|
|
|
|
QCoreApplication.setLibraryPaths([rd])
|
|
|
|
|
|
|
|
# create the app
|
|
|
|
app = AnkiApp(sys.argv)
|
|
|
|
QCoreApplication.setApplicationName("Anki")
|
|
|
|
if app.secondInstance():
|
|
|
|
# we've signaled the primary instance, so we should close
|
|
|
|
return
|
|
|
|
|
|
|
|
# we must have a usable temp dir
|
|
|
|
try:
|
|
|
|
tempfile.gettempdir()
|
|
|
|
except:
|
|
|
|
QMessageBox.critical(
|
|
|
|
None, "Error", """\
|
|
|
|
No usable temporary folder found. Make sure C:\\temp exists or TEMP in your \
|
|
|
|
environment points to a valid, writable folder.""")
|
|
|
|
return
|
|
|
|
|
|
|
|
# qt version must be up to date
|
|
|
|
if qtmajor <= 4 and qtminor <= 6:
|
|
|
|
QMessageBox.critical(
|
|
|
|
None, "Error", "Anki requires Qt4.7 or later.")
|
|
|
|
return
|
|
|
|
|
|
|
|
# parse args
|
|
|
|
opts, args = parseArgs(sys.argv)
|
|
|
|
opts.base = unicode(opts.base or "", sys.getfilesystemencoding())
|
|
|
|
opts.profile = unicode(opts.profile or "", sys.getfilesystemencoding())
|
|
|
|
|
|
|
|
# profile manager
|
|
|
|
from aqt.profiles import ProfileManager
|
|
|
|
pm = ProfileManager(opts.base, opts.profile)
|
|
|
|
|
|
|
|
# i18n
|
|
|
|
setupLang(pm, app, opts.lang)
|
|
|
|
|
|
|
|
# remaining pm init
|
|
|
|
pm.ensureProfile()
|
|
|
|
|
|
|
|
# load the main window
|
|
|
|
import aqt.main
|
|
|
|
mw = aqt.main.AnkiQt(app, pm, args)
|
|
|
|
app.exec_()
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
run()
|