move various major functions into separate files

This commit is contained in:
Damien Elmes 2011-03-27 13:51:13 +09:00
parent 73edb5eb28
commit 698ba38e74
7 changed files with 937 additions and 1121 deletions

46
aqt/cram.py Normal file
View file

@ -0,0 +1,46 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# -*- coding: utf-8 -*-
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
def onCram(self, cardIds=[]):
te = aqt.tagedit.TagEdit(self)
te.setDeck(self.deck, "all")
diag = GetTextDialog(
self, _("Tags to cram:"), help="CramMode", edit=te)
l = diag.layout()
g = QGroupBox(_("Review Mode"))
l.insertWidget(2, g)
box = QVBoxLayout()
g.setLayout(box)
keep = QRadioButton(_("Show oldest modified first"))
box.addWidget(keep)
keep.setChecked(True)
diag.setTabOrder(diag.l, keep)
order = QRadioButton(_("Show in order added"))
box.addWidget(order)
random = QRadioButton(_("Show in random order"))
box.addWidget(random)
# hide tag list if we have ids
if cardIds:
diag.l.hide()
diag.qlabel.hide()
if diag.exec_():
if keep.isChecked():
order = "type, modified"
elif order.isChecked():
order = "created"
else:
order = "random()"
if cardIds:
active = cardIds
else:
active = unicode(diag.l.text())
self.deck.setupCramScheduler(active, order)
if self.state == "studyScreen":
self.onStartReview()
else:
self.deck.reset()
self.deck.getCard() # so scheduler will reset if empty
self.moveToState("initial")
if not self.deck.finishScheduler:
showInfo(_("No cards matched the provided tags."))

49
aqt/editcurrent.py Normal file
View file

@ -0,0 +1,49 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# -*- coding: utf-8 -*-
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
class EditCurrent(object):
def __init__(self, mw):
self.mw = mw
def _editCurrentState(self, oldState):
if self.lastState == "editCurrentFact":
return self.moveToState("saveEdit")
self.form.actionRepeatAudio.setEnabled(False)
self.deck.db.flush()
self.showEditor()
def _saveEditState(self, oldState):
self.form.actionRepeatAudio.setEnabled(True)
self.editor.saveFieldsNow()
self.form.buttonStack.show()
return self.reset()
# Edit current fact
##########################################################################
def setupEditor(self):
print "setupeditor"
return
self.editor = aqt.facteditor.FactEditor(
self, self.form.fieldsArea, self.deck)
self.editor.clayout.setShortcut("")
self.editor.onFactValid = self.onFactValid
self.editor.onFactInvalid = self.onFactInvalid
self.editor.resetOnEdit = False
# editor
self.connect(self.form.saveEditorButton, SIGNAL("clicked()"),
lambda: self.moveToState("saveEdit"))
def showEditor(self):
self.form.buttonStack.hide()
self.switchToEditScreen()
self.editor.setFact(self.currentCard.fact)
self.editor.card = self.currentCard
def onFactValid(self, fact):
self.form.saveEditorButton.setEnabled(True)
def onFactInvalid(self, fact):
self.form.saveEditorButton.setEnabled(False)

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@ class Reviewer(object):
self._answeredIds = []
self.state = None
self._setupStatus()
addHook("leech", self.onLeech)
def show(self):
self.web.setKeyHandler(self._keyHandler)
@ -579,3 +580,19 @@ div#filler {
p.setColor(QPalette.Highlight, QColor("#00ee00"))
self.progressBar.setPalette(p)
self.progressBar.setValue(perc)
# Leeches
##########################################################################
# fixme: update; clear on card transition
def onLeech(self, card):
print "leech"
return
txt = (_("""\
<b>%s</b>... is a <a href="http://ichi2.net/anki/wiki/Leeches">leech</a>.""")
% stripHTML(stripSounds(self.currentCard.question)).\
replace("\n", " ")[0:30])
if isLeech and self.deck.db.scalar(
"select 1 from cards where id = :id and type < 0", id=cardId):
txt += _(" It has been suspended.")
self.setNotice(txt)

89
aqt/share.py Normal file
View file

@ -0,0 +1,89 @@
# to be moved into libanki
def _copyToTmpDeck(self, name="cram.anki", tags="", ids=[]):
ndir = tempfile.mkdtemp(prefix="anki")
path = os.path.join(ndir, name)
from anki.exporting import AnkiExporter
e = AnkiExporter(self.deck)
e.includeMedia = False
if tags:
e.limitTags = parseTags(tags)
if ids:
e.limitCardIds = ids
path = unicode(path, sys.getfilesystemencoding())
e.exportInto(path)
return (e, path)
def onShare(self, tags):
pwd = os.getcwd()
# open tmp deck
(e, path) = self._copyToTmpDeck(name="shared.anki", tags=tags)
if not e.exportedCards:
showInfo(_("No cards matched the provided tags."))
return
self.deck.startProgress()
self.deck.updateProgress()
d = DeckStorage.Deck(path, backup=False)
# reset scheduling to defaults
d.newCardsPerDay = 20
d.delay0 = 600
d.delay1 = 0
d.delay2 = 0
d.hardIntervalMin = 1.0
d.hardIntervalMax = 1.1
d.midIntervalMin = 3.0
d.midIntervalMax = 5.0
d.easyIntervalMin = 7.0
d.easyIntervalMax = 9.0
d.syncName = None
d.setVar("newActive", u"")
d.setVar("newInactive", u"")
d.setVar("revActive", u"")
d.setVar("revInactive", u"")
self.deck.updateProgress()
# unsuspend cards
d.unsuspendCards(d.s.column0("select id from cards where type < 0"))
self.deck.updateProgress()
d.utcOffset = -2
d.flushMod()
d.save()
self.deck.updateProgress()
# media
d.s.statement("update deckVars set value = '' where key = 'mediaURL'")
self.deck.updateProgress()
d.s.statement("vacuum")
self.deck.updateProgress()
nfacts = d.factCount
mdir = self.deck.mediaDir()
d.close()
dir = os.path.dirname(path)
zippath = os.path.join(dir, "shared-%d.zip" % time.time())
# zip it up
zip = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
zip.writestr("facts", str(nfacts))
zip.writestr("version", str(2))
readmep = os.path.join(dir, "README.html")
readme = open(readmep, "w")
readme.write('''\
<html><body>
This is an exported packaged deck created by Anki.<p>
To share this deck with other people, upload it to
<a href="http://anki.ichi2.net/file/upload">
http://anki.ichi2.net/file/upload</a>, or email
it to your friends.
</body></html>''')
readme.close()
zip.write(readmep, "README.html")
zip.write(path, "shared.anki")
if mdir:
for f in os.listdir(mdir):
zip.write(os.path.join(mdir, f),
os.path.join("shared.media/", f))
os.chdir(pwd)
os.chdir(pwd)
self.deck.updateProgress()
zip.close()
os.unlink(path)
self.deck.finishProgress()
self.onOpenPluginFolder(dir)

284
aqt/studyopts.py Normal file
View file

@ -0,0 +1,284 @@
def _studyScreenState(self, oldState):
self.currentCard = None
# if self.deck.finishScheduler:
# self.deck.finishScheduler()
self.disableCardMenuItems()
self.showStudyScreen()
# Study screen
##########################################################################
def setupStudyScreen(self):
return
self.form.buttonStack.hide()
self.form.newCardOrder.insertItems(
0, QStringList(anki.consts.newCardOrderLabels().values()))
self.form.newCardScheduling.insertItems(
0, QStringList(anki.consts.newCardSchedulingLabels().values()))
self.form.revCardOrder.insertItems(
0, QStringList(anki.consts.revCardOrderLabels().values()))
self.connect(self.form.optionsHelpButton,
SIGNAL("clicked()"),
lambda: QDesktopServices.openUrl(QUrl(
aqt.appWiki + "StudyOptions")))
self.connect(self.form.minuteLimit,
SIGNAL("textChanged(QString)"), self.onMinuteLimitChanged)
self.connect(self.form.questionLimit,
SIGNAL("textChanged(QString)"), self.onQuestionLimitChanged)
self.connect(self.form.newPerDay,
SIGNAL("textChanged(QString)"), self.onNewLimitChanged)
self.connect(self.form.startReviewingButton,
SIGNAL("clicked()"),
self.onStartReview)
self.connect(self.form.newCardOrder,
SIGNAL("activated(int)"), self.onNewCardOrderChanged)
self.connect(self.form.failedCardMax,
SIGNAL("editingFinished()"),
self.onFailedMaxChanged)
self.connect(self.form.newCategories,
SIGNAL("clicked()"), self.onNewCategoriesClicked)
self.connect(self.form.revCategories,
SIGNAL("clicked()"), self.onRevCategoriesClicked)
self.form.tabWidget.setCurrentIndex(self.config['studyOptionsTab'])
def onNewCategoriesClicked(self):
aqt.activetags.show(self, "new")
def onRevCategoriesClicked(self):
aqt.activetags.show(self, "rev")
def onFailedMaxChanged(self):
try:
v = int(self.form.failedCardMax.text())
if v == 1 or v < 0:
v = 2
self.deck.failedCardMax = v
except ValueError:
pass
self.form.failedCardMax.setText(str(self.deck.failedCardMax))
self.deck.flushMod()
def onMinuteLimitChanged(self, qstr):
try:
val = float(self.form.minuteLimit.text()) * 60
if self.deck.sessionTimeLimit == val:
return
self.deck.sessionTimeLimit = val
except ValueError:
pass
self.deck.flushMod()
self.updateStudyStats()
def onQuestionLimitChanged(self, qstr):
try:
val = int(self.form.questionLimit.text())
if self.deck.sessionRepLimit == val:
return
self.deck.sessionRepLimit = val
except ValueError:
pass
self.deck.flushMod()
self.updateStudyStats()
def onNewLimitChanged(self, qstr):
try:
val = int(self.form.newPerDay.text())
if self.deck.newCardsPerDay == val:
return
self.deck.newCardsPerDay = val
except ValueError:
pass
self.deck.flushMod()
self.deck.reset()
self.statusView.redraw()
self.updateStudyStats()
def onNewCardOrderChanged(self, ncOrd):
def uf(obj, field, value):
if getattr(obj, field) != value:
setattr(obj, field, value)
self.deck.flushMod()
if ncOrd != 0:
if self.deck.newCardOrder == 0:
# need to put back in order
self.mw.startProgress()
self.mw.updateProgress(_("Ordering..."))
self.deck.orderNewCards()
self.deck.finishProgress()
uf(self.deck, 'newCardOrder', ncOrd)
elif ncOrd == 0:
# (re-)randomize
self.deck.startProgress()
self.deck.updateProgress(_("Randomizing..."))
self.deck.randomizeNewCards()
self.deck.finishProgress()
uf(self.deck, 'newCardOrder', ncOrd)
def updateActives(self):
labels = [
_("Show All Due Cards"),
_("Show Chosen Categories")
]
if self.deck.getVar("newActive") or self.deck.getVar("newInactive"):
new = labels[1]
else:
new = labels[0]
self.form.newCategoryLabel.setText(new)
if self.deck.getVar("revActive") or self.deck.getVar("revInactive"):
rev = labels[1]
else:
rev = labels[0]
self.form.revCategoryLabel.setText(rev)
def updateStudyStats(self):
self.form.buttonStack.hide()
self.deck.reset()
self.updateActives()
wasReached = self.deck.timeboxReached()
sessionColour = '<font color=#0000ff>%s</font>'
cardColour = '<font color=#0000ff>%s</font>'
# top label
h = {}
h['ret'] = cardColour % (self.deck.revCount+self.deck.failedSoonCount)
h['new'] = cardColour % self.deck.newCount
h['newof'] = str(self.deck.newCountAll())
# counts & time for today
todayStart = self.deck.failedCutoff - 86400
sql = "select count(), sum(userTime) from revlog"
(reps, time_) = self.deck.db.first(
sql + " where time > :start", start=todayStart)
h['timeToday'] = sessionColour % (
anki.utils.fmtTimeSpan(time_ or 0, short=True, point=1))
h['repsToday'] = sessionColour % reps
# and yesterday
yestStart = todayStart - 86400
(reps, time_) = self.deck.db.first(
sql + " where time > :start and time <= :end",
start=yestStart, end=todayStart)
h['timeTodayChg'] = str(
anki.utils.fmtTimeSpan(time_ or 0, short=True, point=1))
h['repsTodayChg'] = str(reps)
# session counts
limit = self.deck.sessionTimeLimit
start = self.deck.sessionStartTime or time.time() - limit
start2 = self.deck.lastSessionStart or start - limit
last10 = self.deck.db.scalar(
"select count(*) from revlog where time >= :t",
t=start)
last20 = self.deck.db.scalar(
"select count(*) from revlog where "
"time >= :t and time < :t2",
t=start2, t2=start)
h['repsInSes'] = sessionColour % last10
h['repsInSesChg'] = str(last20)
h['cs_header'] = "<b>" + _("Cards/session:") + "</b>"
h['cd_header'] = "<b>" + _("Cards/day:") + "</b>"
h['td_header'] = "<b>" + _("Time/day:") + "</b>"
h['rd_header'] = "<b>" + _("Reviews due:") + "</b>"
h['ntod_header'] = "<b>" + _("New today:") + "</b>"
h['ntot_header'] = "<b>" + _("New total:") + "</b>"
stats1 = ("""\
<table>
<tr><td width=150>%(cs_header)s</td><td width=50><b>%(repsInSesChg)s</b></td>
<td><b>%(repsInSes)s</b></td></tr></table>
<hr>
<table>
<tr><td width=150>
%(cd_header)s</td><td width=50><b>%(repsTodayChg)s</b></td>
<td><b>%(repsToday)s</b></td></tr>
<tr><td>%(td_header)s</td><td><b>%(timeTodayChg)s</b></td>
<td><b>%(timeToday)s</b></td></tr>
</table>""") % h
stats2 = ("""\
<table>
<tr><td width=180>%(rd_header)s</td><td align=right><b>%(ret)s</b></td></tr>
<tr><td>%(ntod_header)s</td><td align=right><b>%(new)s</b></td></tr>
<tr><td>%(ntot_header)s</td><td align=right>%(newof)s</td></tr>
</table>""") % h
self.form.optionsLabel.setText("""\
<p><table><tr>
%s
</tr><tr>
<td><hr>%s<hr></td></tr></table>""" % (stats1, stats2))
h['tt_header'] = _("Session Statistics")
h['cs_tip'] = _("The number of cards you studied in the current \
session (blue) and previous session (black)")
h['cd_tip'] = _("The number of cards you studied today (blue) and \
yesterday (black)")
h['td_tip'] = _("The number of minutes you studied today (blue) and \
yesterday (black)")
h['rd_tip'] = _("The number of cards that are waiting to be reviewed \
today")
h['ntod_tip'] = _("The number of new cards that are waiting to be \
learnt today")
h['ntot_tip'] = _("The total number of new cards in the deck")
statToolTip = ("""<h1>%(tt_header)s</h1>
<dl><dt><b>%(cs_header)s</b></dt><dd>%(cs_tip)s</dd></dl>
<dl><dt><b>%(cd_header)s</b></dt><dd>%(cd_tip)s</dd></dl>
<dl><dt><b>%(td_header)s</b></dt><dd>%(td_tip)s</dd></dl>
<dl><dt><b>%(rd_header)s</b></dt><dd>%(rd_tip)s</dd></dl>
<dl><dt><b>%(ntod_header)s</b></dt><dd>%(ntod_tip)s</dd></dl>
<dl><dt><b>%(ntot_header)s</b></dt><dd>%(ntot_tip)s<</dd></dl>""") % h
self.form.optionsLabel.setToolTip(statToolTip)
def showStudyScreen(self):
# forget last card
self.lastCard = None
self.switchToStudyScreen()
self.updateStudyStats()
self.form.startReviewingButton.setFocus()
self.setupStudyOptions()
self.form.studyOptionsFrame.setMaximumWidth(500)
self.form.studyOptionsFrame.show()
def setupStudyOptions(self):
self.form.newPerDay.setText(str(self.deck.newCardsPerDay))
lim = self.deck.sessionTimeLimit/60
if int(lim) == lim:
lim = int(lim)
self.form.minuteLimit.setText(str(lim))
self.form.questionLimit.setText(str(self.deck.sessionRepLimit))
self.form.newCardOrder.setCurrentIndex(self.deck.newCardOrder)
self.form.newCardScheduling.setCurrentIndex(self.deck.newCardSpacing)
self.form.revCardOrder.setCurrentIndex(self.deck.revCardOrder)
self.form.failedCardsOption.clear()
if self.deck.getFailedCardPolicy() == 5:
labels = failedCardOptionLabels().values()
else:
labels = failedCardOptionLabels().values()[0:-1]
self.form.failedCardsOption.insertItems(0, labels)
self.form.failedCardsOption.setCurrentIndex(self.deck.getFailedCardPolicy())
self.form.failedCardMax.setText(unicode(self.deck.failedCardMax))
def onStartReview(self):
def uf(obj, field, value):
if getattr(obj, field) != value:
setattr(obj, field, value)
self.deck.flushMod()
self.form.studyOptionsFrame.hide()
# make sure the size is updated before button stack shown
self.app.processEvents()
uf(self.deck, 'newCardSpacing',
self.form.newCardScheduling.currentIndex())
uf(self.deck, 'revCardOrder',
self.form.revCardOrder.currentIndex())
pol = self.deck.getFailedCardPolicy()
if (pol != 5 and pol !=
self.form.failedCardsOption.currentIndex()):
self.deck.setFailedCardPolicy(
self.form.failedCardsOption.currentIndex())
self.deck.flushMod()
self.deck.reset()
if not self.deck.finishScheduler:
self.deck.startTimebox()
self.config['studyOptionsTab'] = self.form.tabWidget.currentIndex()
self.moveToState("getQuestion")
def onStudyOptions(self):
if self.state == "studyScreen":
pass
else:
self.moveToState("studyScreen")

View file

@ -14,6 +14,307 @@ from anki.db import sqlite
import aqt.forms
from anki.hooks import addHook, removeHook
class SyncManager(object):
# Syncing
##########################################################################
def syncDeck(self, interactive=True, onlyMerge=False, reload=True):
"Synchronise a deck with the server."
self.raiseMain()
#self.setNotice()
# vet input
if interactive:
self.ensureSyncParams()
u=self.config['syncUsername']
p=self.config['syncPassword']
if not u or not p:
return
if self.deck:
if not self.deck.path:
if not self.save(required=True):
return
if self.deck and not self.deck.syncName:
if interactive:
if (not self.config['mediaLocation']
and self.deck.db.scalar("select 1 from media limit 1")):
showInfo(_("""\
Syncing sounds and images requires a free file synchronization service like \
DropBox. Click help to learn more, and OK to continue syncing."""),
help="SyncingMedia")
# enable syncing
self.deck.enableSyncing()
else:
return
if self.deck is None and getattr(self, 'deckPath', None) is None:
# sync all decks
self.loadAfterSync = -1
self.syncName = None
self.syncDecks = self.decksToSync()
if not self.syncDecks:
if interactive:
showInfo(_("""\
Please open a deck and run File>Sync. After you do this once, the deck \
will sync automatically from then on."""))
return
else:
# sync one deck
# hide all deck-associated dialogs
self.closeAllDeckWindows()
if self.deck:
# save first, so we can rollback on failure
self.deck.save()
# store data we need before closing the deck
self.deckPath = self.deck.path
self.syncName = self.deck.name()
self.lastSync = self.deck.lastSync
self.deck.close()
self.deck = None
self.loadAfterSync = reload
# bug triggered by preferences dialog - underlying c++ widgets are not
# garbage collected until the middle of the child thread
self.state = "nostate"
import gc; gc.collect()
self.form.welcomeText.setText(u"")
self.syncThread = aqt.sync.Sync(self, u, p, interactive, onlyMerge)
self.connect(self.syncThread, SIGNAL("setStatus"), self.setSyncStatus)
self.connect(self.syncThread, SIGNAL("showWarning"), self.showSyncWarning)
self.connect(self.syncThread, SIGNAL("moveToState"), self.moveToState)
self.connect(self.syncThread, SIGNAL("noMatchingDeck"), self.selectSyncDeck)
self.connect(self.syncThread, SIGNAL("syncClockOff"), self.syncClockOff)
self.connect(self.syncThread, SIGNAL("cleanNewDeck"), self.cleanNewDeck)
self.connect(self.syncThread, SIGNAL("syncFinished"), self.onSyncFinished)
self.connect(self.syncThread, SIGNAL("openSyncProgress"), self.openSyncProgress)
self.connect(self.syncThread, SIGNAL("closeSyncProgress"), self.closeSyncProgress)
self.connect(self.syncThread, SIGNAL("updateSyncProgress"), self.updateSyncProgress)
self.connect(self.syncThread, SIGNAL("bulkSyncFailed"), self.bulkSyncFailed)
self.connect(self.syncThread, SIGNAL("fullSyncStarted"), self.fullSyncStarted)
self.connect(self.syncThread, SIGNAL("fullSyncFinished"), self.fullSyncFinished)
self.connect(self.syncThread, SIGNAL("fullSyncProgress"), self.fullSyncProgress)
self.connect(self.syncThread, SIGNAL("badUserPass"), self.badUserPass)
self.connect(self.syncThread, SIGNAL("syncConflicts"), self.onConflict)
self.connect(self.syncThread, SIGNAL("syncClobber"), self.onClobber)
self.syncThread.start()
self.switchToWelcomeScreen()
self.setEnabled(False)
self.syncFinished = False
while not self.syncFinished:
self.app.processEvents()
self.syncThread.wait(100)
self.setEnabled(True)
return True
def decksToSync(self):
ok = []
for d in self.config['recentDeckPaths']:
if os.path.exists(d):
ok.append(d)
return ok
def onConflict(self, deckName):
diag = askUserDialog(_("""\
<b>%s</b> has been changed on both
the local and remote side. What do
you want to do?""" % deckName),
[_("Keep Local"),
_("Keep Remote"),
_("Cancel")])
diag.setDefault(2)
ret = diag.run()
if ret == _("Keep Local"):
self.syncThread.conflictResolution = "keepLocal"
elif ret == _("Keep Remote"):
self.syncThread.conflictResolution = "keepRemote"
else:
self.syncThread.conflictResolution = "cancel"
def onClobber(self, deckName):
diag = askUserDialog(_("""\
You are about to upload <b>%s</b>
to AnkiOnline. This will overwrite
the online copy of this deck.
Are you sure?""" % deckName),
[_("Upload"),
_("Cancel")])
diag.setDefault(1)
ret = diag.run()
if ret == _("Upload"):
self.syncThread.clobberChoice = "overwrite"
else:
self.syncThread.clobberChoice = "cancel"
def onSyncFinished(self):
"Reopen after sync finished."
self.form.buttonStack.show()
try:
try:
if not self.showBrowser:
# no deck load & no deck browser, as we're about to quit or do
# something manually
pass
else:
if self.loadAfterSync == -1:
# after sync all, so refresh browser list
self.browserLastRefreshed = 0
self.moveToState("deckBrowser")
elif self.loadAfterSync and self.deckPath:
if self.loadAfterSync == 2:
name = re.sub("[<>]", "", self.syncName)
p = os.path.join(self.documentDir, name + ".anki")
shutil.copy2(self.deckPath, p)
self.deckPath = p
# since we've moved the deck, we have to set sync path
# ourselves
c = sqlite.connect(p)
v = c.execute(
"select version from decks").fetchone()[0]
if v >= 52:
# deck has bene upgraded already, so we can
# use a checksum
name = checksum(p.encode("utf-8"))
else:
# FIXME: compat code because deck hasn't been
# upgraded yet. can be deleted in the future.
# strip off .anki part
name = os.path.splitext(
os.path.basename(p))[0]
c.execute("update decks set syncName = ?", (name,))
c.commit()
c.close()
self.loadDeck(self.deckPath)
else:
self.moveToState("deckBrowser")
except:
self.moveToState("deckBrowser")
raise
finally:
self.deckPath = None
self.syncFinished = True
def selectSyncDeck(self, decks):
name = aqt.sync.DeckChooser(self, decks).getName()
self.syncName = name
if name:
# name chosen
p = os.path.join(self.documentDir, name + ".anki")
if os.path.exists(p):
d = askUserDialog(_("""\
This deck already exists on your computer. Overwrite the local copy?"""),
["Overwrite", "Cancel"])
d.setDefault(1)
if d.run() == "Overwrite":
self.syncDeck(interactive=False, onlyMerge=True)
else:
self.syncFinished = True
self.cleanNewDeck()
else:
self.syncDeck(interactive=False, onlyMerge=True)
return
self.syncFinished = True
self.cleanNewDeck()
def cleanNewDeck(self):
"Unload a new deck if an initial sync failed."
self.deck = None
self.deckPath = None
self.moveToState("deckBrowser")
self.syncFinished = True
def setSyncStatus(self, text, *args):
self.form.welcomeText.append("<font size=+2>" + text + "</font>")
def syncClockOff(self, diff):
showWarning(
_("The time or date on your computer is not correct.\n") +
ngettext("It is off by %d second.\n\n",
"It is off by %d seconds.\n\n", diff) % diff +
_("Since this can cause many problems with syncing,\n"
"syncing is disabled until you fix the problem.")
)
self.onSyncFinished()
def showSyncWarning(self, text):
showWarning(text, self)
self.setStatus("")
def badUserPass(self):
aqt.preferences.Preferences(self, self.config).dialog.tabWidget.\
setCurrentIndex(1)
def openSyncProgress(self):
self.syncProgressDialog = QProgressDialog(_("Syncing Media..."),
"", 0, 0, self)
self.syncProgressDialog.setWindowTitle(_("Syncing Media..."))
self.syncProgressDialog.setCancelButton(None)
self.syncProgressDialog.setAutoClose(False)
self.syncProgressDialog.setAutoReset(False)
def closeSyncProgress(self):
self.syncProgressDialog.cancel()
def updateSyncProgress(self, args):
(type, x, y, fname) = args
self.syncProgressDialog.setMaximum(y)
self.syncProgressDialog.setValue(x)
self.syncProgressDialog.setMinimumDuration(0)
if type == "up":
self.syncProgressDialog.setLabelText("Uploading %s..." % fname)
else:
self.syncProgressDialog.setLabelText("Downloading %s..." % fname)
def bulkSyncFailed(self):
showWarning(_(
"Failed to upload media. Please run 'check media db'."), self)
def fullSyncStarted(self, max):
self.startProgress(max=max)
def fullSyncFinished(self):
self.finishProgress()
# need to deactivate interface again
self.setEnabled(False)
def fullSyncProgress(self, type, val):
if type == "fromLocal":
s = _("Uploaded %dKB to server...")
self.updateProgress(label=s % (val / 1024), value=val)
else:
s = _("Downloaded %dKB from server...")
self.updateProgress(label=s % (val / 1024))
def ensureSyncParams(self):
if not self.config['syncUsername'] or not self.config['syncPassword']:
d = QDialog(self)
vbox = QVBoxLayout()
l = QLabel(_(
'<h1>Online Account</h1>'
'To use your free <a href="http://anki.ichi2.net/">online account</a>,<br>'
"please enter your details below.<br><br>"
"You can change your details later with<br>"
"Settings->Preferences->Sync<br>"))
l.setOpenExternalLinks(True)
vbox.addWidget(l)
g = QGridLayout()
l1 = QLabel(_("Username:"))
g.addWidget(l1, 0, 0)
user = QLineEdit()
g.addWidget(user, 0, 1)
l2 = QLabel(_("Password:"))
g.addWidget(l2, 1, 0)
passwd = QLineEdit()
passwd.setEchoMode(QLineEdit.Password)
g.addWidget(passwd, 1, 1)
vbox.addLayout(g)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
self.connect(bb, SIGNAL("accepted()"), d.accept)
self.connect(bb, SIGNAL("rejected()"), d.reject)
vbox.addWidget(bb)
d.setLayout(vbox)
d.exec_()
self.config['syncUsername'] = unicode(user.text())
self.config['syncPassword'] = unicode(passwd.text())
# Synchronising a deck with a public server
##########################################################################