mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00

- moved progress handling into separate progress.py - moved deck browser code into separate deckbrowser.py - started reworking the state code; views will be rolled into this in the future - the main window has been stripped of the study options, inline editor, congrats screen and so on, and now consists of a single main widget which has a webview placed inside it. The stripped features will be implemented either in separate windows, or as part of the web view
241 lines
9.4 KiB
Python
241 lines
9.4 KiB
Python
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
|
|
|
from PyQt4.QtGui import *
|
|
from PyQt4.QtCore import *
|
|
import sys, re, time
|
|
import aqt.forms
|
|
import anki
|
|
from aqt import ui
|
|
from anki.utils import parseTags
|
|
from anki.deck import newCardOrderLabels, newCardSchedulingLabels
|
|
from anki.deck import revCardOrderLabels
|
|
from anki.utils import hexifyID, dehexifyID
|
|
import aqt
|
|
|
|
class DeckProperties(QDialog):
|
|
|
|
def __init__(self, parent, deck, onFinish=None):
|
|
QDialog.__init__(self, parent, Qt.Window)
|
|
self.parent = parent
|
|
self.d = deck
|
|
self.onFinish = onFinish
|
|
self.origMod = self.d.modified
|
|
self.dialog = aqt.forms.deckproperties.Ui_DeckProperties()
|
|
self.dialog.setupUi(self)
|
|
self.dialog.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False)
|
|
self.dialog.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
|
|
self.readData()
|
|
self.connect(self.dialog.modelsAdd, SIGNAL("clicked()"), self.onAdd)
|
|
self.connect(self.dialog.modelsEdit, SIGNAL("clicked()"), self.onEdit)
|
|
self.connect(self.dialog.modelsDelete, SIGNAL("clicked()"), self.onDelete)
|
|
self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"), self.helpRequested)
|
|
self.show()
|
|
|
|
def readData(self):
|
|
# syncing
|
|
if self.d.syncName:
|
|
self.dialog.doSync.setCheckState(Qt.Checked)
|
|
else:
|
|
self.dialog.doSync.setCheckState(Qt.Unchecked)
|
|
self.dialog.mediaURL.setText(self.d.getVar("mediaURL") or "")
|
|
# latex
|
|
self.dialog.latexHeader.setText(self.d.getVar("latexPre"))
|
|
self.dialog.latexFooter.setText(self.d.getVar("latexPost"))
|
|
# scheduling
|
|
for type in ("hard", "mid", "easy"):
|
|
v = getattr(self.d, type + "IntervalMin")
|
|
getattr(self.dialog, type + "Min").setText(str(v))
|
|
v = getattr(self.d, type + "IntervalMax")
|
|
getattr(self.dialog, type + "Max").setText(str(v))
|
|
self.dialog.delay0.setText(unicode(self.d.delay0/60.0))
|
|
self.dialog.delay1.setText(unicode(self.d.delay1))
|
|
self.dialog.delay2.setText(unicode(int(self.d.delay2*100)))
|
|
self.dialog.collapse.setCheckState(self.d.collapseTime
|
|
and Qt.Checked or Qt.Unchecked)
|
|
self.dialog.perDay.setCheckState(self.d.getBool("perDay")
|
|
and Qt.Checked or Qt.Unchecked)
|
|
# models
|
|
self.updateModelsList()
|
|
# hour shift
|
|
self.dialog.timeOffset.setText(str(
|
|
(self.d.utcOffset - time.timezone) / 60.0 / 60.0))
|
|
# leeches
|
|
self.dialog.suspendLeeches.setChecked(self.d.getBool("suspendLeeches"))
|
|
self.dialog.leechFails.setValue(self.d.getInt("leechFails"))
|
|
# spacing
|
|
self.dialog.newSpacing.setText(unicode(self.d.getFloat("newSpacing")/60.0))
|
|
self.dialog.revSpacing.setText(unicode(self.d.getFloat("revSpacing")*100))
|
|
|
|
def updateModelsList(self):
|
|
idx = self.dialog.modelsList.currentRow()
|
|
self.dialog.modelsList.clear()
|
|
self.models = []
|
|
for model in self.d.models:
|
|
name = ngettext("%(name)s [%(facts)d fact]",
|
|
"%(name)s [%(facts)d facts]", self.d.modelUseCount(model)) % {
|
|
'name': model.name,
|
|
'facts': self.d.modelUseCount(model),
|
|
}
|
|
self.models.append((name, model))
|
|
self.models.sort()
|
|
for (name, model) in self.models:
|
|
item = QListWidgetItem(name)
|
|
self.dialog.modelsList.addItem(item)
|
|
cm = self.d.currentModel
|
|
try:
|
|
if aqt.mw.currentCard:
|
|
cm = aqt.mw.currentCard.fact.model
|
|
except:
|
|
# model has been deleted
|
|
pass
|
|
if model == cm:
|
|
self.dialog.modelsList.setCurrentItem(item)
|
|
|
|
def onAdd(self):
|
|
m = ui.modelchooser.AddModel(self, self.parent, self.d).getModel()
|
|
if m:
|
|
self.d.addModel(m)
|
|
self.updateModelsList()
|
|
|
|
def onEdit(self):
|
|
model = self.selectedModel()
|
|
if not model:
|
|
return
|
|
# set to current
|
|
self.d.currentModel = model
|
|
ui.modelproperties.ModelProperties(self, self.d, model, self.parent,
|
|
onFinish=self.updateModelsList)
|
|
|
|
def onDelete(self):
|
|
model = self.selectedModel()
|
|
row = self.dialog.modelsList.currentRow()
|
|
if not model:
|
|
return
|
|
if len(self.d.models) < 2:
|
|
ui.utils.showWarning(_("Please add another model first."),
|
|
parent=self)
|
|
return
|
|
if self.d.s.scalar("select 1 from sources where id=:id",
|
|
id=model.source):
|
|
ui.utils.showWarning(_("This model is used by deck source:\n"
|
|
"%s\nYou will need to remove the source "
|
|
"first.") % hexifyID(model.source))
|
|
return
|
|
count = self.d.modelUseCount(model)
|
|
if count:
|
|
if not ui.utils.askUser(
|
|
_("This model is used by %d facts.\n"
|
|
"Are you sure you want to delete it?\n"
|
|
"If you delete it, these cards will be lost.")
|
|
% count, parent=self):
|
|
return
|
|
self.d.deleteModel(model)
|
|
self.updateModelsList()
|
|
self.dialog.modelsList.setCurrentRow(row)
|
|
aqt.mw.reset()
|
|
|
|
def selectedModel(self):
|
|
row = self.dialog.modelsList.currentRow()
|
|
if row == -1:
|
|
return None
|
|
return self.models[self.dialog.modelsList.currentRow()][1]
|
|
|
|
def updateField(self, obj, field, value):
|
|
if getattr(obj, field) != value:
|
|
setattr(obj, field, value)
|
|
self.d.setModified()
|
|
|
|
def helpRequested(self):
|
|
QDesktopServices.openUrl(QUrl(aqt.appWiki +
|
|
"DeckProperties"))
|
|
|
|
def reject(self):
|
|
n = _("Deck Properties")
|
|
self.mw.startProgress()
|
|
self.d.setUndoStart(n)
|
|
needSync = False
|
|
# syncing
|
|
if self.dialog.doSync.checkState() == Qt.Checked:
|
|
old = self.d.syncName
|
|
oldSync = self.d.lastSync
|
|
self.d.enableSyncing()
|
|
if self.d.syncName != old:
|
|
needSync = True
|
|
else:
|
|
# put it back
|
|
self.d.lastSync = oldSync
|
|
else:
|
|
self.d.disableSyncing()
|
|
url = unicode(self.dialog.mediaURL.text())
|
|
if url:
|
|
if not re.match("^(http|https|ftp)://", url, re.I):
|
|
url = "http://" + url
|
|
if not url.endswith("/"):
|
|
url += "/"
|
|
old = self.d.getVar("mediaURL") or ""
|
|
if old != url:
|
|
self.d.setVar("mediaURL", url)
|
|
# latex
|
|
self.d.setVar('latexPre', unicode(self.dialog.latexHeader.toPlainText()))
|
|
self.d.setVar('latexPost', unicode(self.dialog.latexFooter.toPlainText()))
|
|
# scheduling
|
|
minmax = ("Min", "Max")
|
|
for type in ("hard", "mid", "easy"):
|
|
v = getattr(self.dialog, type + "Min").text()
|
|
try:
|
|
v = float(v)
|
|
except ValueError:
|
|
continue
|
|
self.updateField(self.d, type + "IntervalMin", v)
|
|
v = getattr(self.dialog, type + "Max").text()
|
|
try:
|
|
v = float(v)
|
|
except ValueError:
|
|
continue
|
|
self.updateField(self.d, type + "IntervalMax", v)
|
|
try:
|
|
v = float(self.dialog.delay0.text()) * 60.0
|
|
self.updateField(self.d, 'delay0', v)
|
|
v2 = int(self.dialog.delay1.text())
|
|
v2 = max(0, v2)
|
|
self.updateField(self.d, 'delay1', v2)
|
|
v = float(self.dialog.delay2.text()) / 100.0
|
|
self.updateField(self.d, 'delay2', max(0, min(100, v)))
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
self.d.setVar("suspendLeeches",
|
|
not not self.dialog.suspendLeeches.isChecked())
|
|
self.d.setVar("leechFails",
|
|
int(self.dialog.leechFails.value()))
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
self.d.setVar("newSpacing", float(self.dialog.newSpacing.text()) * 60)
|
|
self.d.setVar("revSpacing", float(self.dialog.revSpacing.text()) / 100.0)
|
|
except ValueError:
|
|
pass
|
|
# hour shift
|
|
try:
|
|
offset = float(str(self.dialog.timeOffset.text()))
|
|
offset = max(min(offset, 24), -24)
|
|
self.updateField(self.d, 'utcOffset', offset*60*60+time.timezone)
|
|
except:
|
|
pass
|
|
was = self.d.modified
|
|
self.updateField(self.d, 'collapseTime',
|
|
self.dialog.collapse.isChecked() and 1 or 0)
|
|
if self.dialog.perDay.isChecked() != self.d.getBool("perDay"):
|
|
self.d.setVar('perDay', self.dialog.perDay.isChecked())
|
|
# mark deck dirty and close
|
|
if self.origMod != self.d.modified:
|
|
aqt.mw.deck.updateCutoff()
|
|
aqt.mw.reset()
|
|
self.d.setUndoEnd(n)
|
|
self.d.finishProgress()
|
|
if self.onFinish:
|
|
self.onFinish()
|
|
QDialog.reject(self)
|
|
if needSync:
|
|
aqt.mw.syncDeck(interactive=-1)
|