Anki/ankiqt/ui/modelproperties.py
2008-09-28 00:00:49 +09:00

474 lines
18 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
import ankiqt.forms
import anki
from anki.models import FieldModel, CardModel
from ankiqt import ui
class ModelProperties(QDialog):
def __init__(self, parent, model, main=None, onFinish=None):
QDialog.__init__(self, parent, Qt.Window)
if not main:
main = parent
self.parent = main
self.deck = main.deck
self.origModTime = self.deck.modified
self.m = model
self.onFinish = onFinish
self.dialog = ankiqt.forms.modelproperties.Ui_ModelProperties()
self.dialog.setupUi(self)
self.setupFields()
self.setupCards()
self.readData()
self.show()
def readData(self):
# properties section
self.dialog.name.setText(self.m.name)
self.dialog.description.setText(self.m.description)
self.dialog.tags.setText(self.m.tags)
self.dialog.decorators.setText(self.m.features)
self.dialog.spacing.setText(str(self.m.spacing))
self.dialog.initialSpacing.setText(str(self.m.initialSpacing/60))
# Fields
##########################################################################
def setupFields(self):
self.fieldOrdinalUpdatedIds = []
self.ignoreFieldUpdate = False
self.currentField = None
self.updateFields()
self.readCurrentField()
self.connect(self.dialog.fieldList, SIGNAL("currentRowChanged(int)"),
self.fieldRowChanged)
self.connect(self.dialog.tabWidget, SIGNAL("currentChanged(int)"),
self.fieldRowChanged)
self.connect(self.dialog.fieldAdd, SIGNAL("clicked()"),
self.addField)
self.connect(self.dialog.fieldDelete, SIGNAL("clicked()"),
self.deleteField)
self.connect(self.dialog.fieldUp, SIGNAL("clicked()"),
self.moveFieldUp)
self.connect(self.dialog.fieldDown, SIGNAL("clicked()"),
self.moveFieldDown)
def updateFields(self, row = None):
oldRow = self.dialog.fieldList.currentRow()
if oldRow == -1:
oldRow = 0
self.dialog.fieldList.clear()
n = 1
for field in self.m.fieldModels:
label = _("Field %(num)d: %(name)s [%(cards)s non-empty]") % {
'num': n,
'name': field.name,
'cards': self.deck.fieldModelUseCount(field)
}
item = QListWidgetItem(label)
self.dialog.fieldList.addItem(item)
n += 1
count = self.dialog.fieldList.count()
if row != None:
self.dialog.fieldList.setCurrentRow(row)
else:
while (count > 0 and oldRow > (count - 1)):
oldRow -= 1
self.dialog.fieldList.setCurrentRow(oldRow)
self.enableFieldMoveButtons()
def fieldRowChanged(self):
if self.ignoreFieldUpdate:
return
self.saveCurrentField()
self.readCurrentField()
def readCurrentField(self):
if not len(self.m.fieldModels):
self.dialog.fieldEditBox.hide()
self.dialog.fieldUp.setEnabled(False)
self.dialog.fieldDown.setEnabled(False)
return
else:
self.dialog.fieldEditBox.show()
self.currentField = self.m.fieldModels[self.dialog.fieldList.currentRow()]
field = self.currentField
self.dialog.fieldName.setText(field.name)
self.dialog.fieldDescription.setText(field.description)
self.dialog.fieldUnique.setChecked(field.unique)
self.dialog.fieldRequired.setChecked(field.required)
self.dialog.fieldFeatures.setText(field.features)
self.dialog.numeric.setChecked(field.numeric)
def enableFieldMoveButtons(self):
row = self.dialog.fieldList.currentRow()
if row < 1:
self.dialog.fieldUp.setEnabled(False)
else:
self.dialog.fieldUp.setEnabled(True)
if row == -1 or row >= (self.dialog.fieldList.count() - 1):
self.dialog.fieldDown.setEnabled(False)
else:
self.dialog.fieldDown.setEnabled(True)
def saveCurrentField(self):
if not self.currentField:
return
field = self.currentField
name = unicode(self.dialog.fieldName.text()).strip()
# renames
if not name:
name = _("Field %d") % (self.m.fieldModels.index(field) + 1)
if name != field.name:
self.deck.renameFieldModel(self.m, field, name)
# the card models will have been updated
self.readCurrentCard()
self.updateField(field, 'description',
unicode(self.dialog.fieldDescription.toPlainText()))
self.updateField(field, 'features',
unicode(self.dialog.fieldFeatures.text()))
# unique, required, numeric
self.updateField(field, 'unique',
self.dialog.fieldUnique.checkState() == Qt.Checked)
self.updateField(field, 'required',
self.dialog.fieldRequired.checkState() == Qt.Checked)
self.updateField(field, 'numeric',
self.dialog.numeric.checkState() == Qt.Checked)
self.ignoreFieldUpdate = True
self.updateFields()
self.ignoreFieldUpdate = False
def addField(self):
f = FieldModel()
f.name = _("Field %d") % (len(self.m.fieldModels) + 1)
self.deck.addFieldModel(self.m, f)
self.updateFields()
self.dialog.fieldList.setCurrentRow(len(self.m.fieldModels)-1)
self.dialog.fieldName.setFocus()
self.dialog.fieldName.selectAll()
def deleteField(self):
row = self.dialog.fieldList.currentRow()
if row == -1:
return
if len(self.m.fieldModels) < 2:
ui.utils.showInfo(
_("Please add a new field first."))
return
field = self.m.fieldModels[row]
count = self.deck.fieldModelUseCount(field)
if count:
if not ui.utils.askUser(
_("This field is used by %d cards. If you delete it,\n"
"all information in this field will be lost.\n"
"\nReally delete this field?") % count,
parent=self):
return
self.deck.deleteFieldModel(self.m, field)
self.currentField = None
self.updateFields()
# need to update q/a format
self.readCurrentCard()
def moveFieldUp(self):
row = self.dialog.fieldList.currentRow()
if row == -1:
return
if row == 0:
return
field = self.m.fieldModels[row]
tField = self.m.fieldModels[row - 1]
self.m.fieldModels.remove(field)
self.m.fieldModels.insert(row - 1, field)
if field.id not in self.fieldOrdinalUpdatedIds:
self.fieldOrdinalUpdatedIds.append(field.id)
if tField.id not in self.fieldOrdinalUpdatedIds:
self.fieldOrdinalUpdatedIds.append(tField.id)
self.ignoreFieldUpdate = True
self.updateFields(row - 1)
self.ignoreFieldUpdate = False
def moveFieldDown(self):
row = self.dialog.fieldList.currentRow()
if row == -1:
return
if row == len(self.m.fieldModels) - 1:
return
field = self.m.fieldModels[row]
tField = self.m.fieldModels[row + 1]
self.m.fieldModels.remove(field)
self.m.fieldModels.insert(row + 1, field)
if field.id not in self.fieldOrdinalUpdatedIds:
self.fieldOrdinalUpdatedIds.append(field.id)
if tField.id not in self.fieldOrdinalUpdatedIds:
self.fieldOrdinalUpdatedIds.append(tField.id)
self.ignoreFieldUpdate = True
self.updateFields(row + 1)
self.ignoreFieldUpdate = False
# Cards
##########################################################################
def setupCards(self):
self.cardOrdinalUpdatedIds = []
self.ignoreCardUpdate = False
self.currentCard = None
self.updateCards()
self.readCurrentCard()
self.connect(self.dialog.cardList, SIGNAL("currentRowChanged(int)"),
self.cardRowChanged)
self.connect(self.dialog.cardAdd, SIGNAL("clicked()"),
self.addCard)
self.connect(self.dialog.cardDelete, SIGNAL("clicked()"),
self.deleteCard)
self.connect(self.dialog.cardToggle, SIGNAL("clicked()"),
self.toggleCard)
self.connect(self.dialog.cardUp, SIGNAL("clicked()"),
self.moveCardUp)
self.connect(self.dialog.cardDown, SIGNAL("clicked()"),
self.moveCardDown)
def updateCards(self, row = None):
oldRow = self.dialog.cardList.currentRow()
if oldRow == -1:
oldRow = 0
self.dialog.cardList.clear()
n = 1
for card in self.m.cardModels:
if card.active:
status=""
else:
status=_("; disabled")
label = _("Card %(num)d (%(name)s): used %(cards)d times%(status)s") % {
'num': n,
'name': card.name,
'status': status,
'cards': self.deck.cardModelUseCount(card),
}
item = QListWidgetItem(label)
self.dialog.cardList.addItem(item)
n += 1
count = self.dialog.cardList.count()
if row != None:
self.dialog.cardList.setCurrentRow(row)
else:
while (count > 0 and oldRow > (count - 1)):
oldRow -= 1
self.dialog.cardList.setCurrentRow(oldRow)
self.enableCardMoveButtons()
def cardRowChanged(self):
if self.ignoreCardUpdate:
return
self.saveCurrentCard()
self.readCurrentCard()
def readCurrentCard(self):
if not len(self.m.cardModels):
self.dialog.cardEditBox.hide()
self.dialog.cardToggle.setEnabled(False)
self.dialog.cardDelete.setEnabled(False)
self.dialog.cardUp.setEnabled(False)
self.dialog.cardDown.setEnabled(False)
return
else:
self.dialog.cardEditBox.show()
self.dialog.cardToggle.setEnabled(True)
self.dialog.cardDelete.setEnabled(True)
self.currentCard = self.m.cardModels[self.dialog.cardList.currentRow()]
card = self.currentCard
self.dialog.cardName.setText(card.name)
self.dialog.cardDescription.setText(card.description)
self.dialog.cardQuestion.setPlainText(card.qformat.replace("<br>", "\n"))
self.dialog.cardAnswer.setPlainText(card.aformat.replace("<br>", "\n"))
if card.questionInAnswer:
self.dialog.questionInAnswer.setCheckState(Qt.Checked)
else:
self.dialog.questionInAnswer.setCheckState(Qt.Unchecked)
self.updateToggleButtonText(card)
def enableCardMoveButtons(self):
row = self.dialog.cardList.currentRow()
if row < 1:
self.dialog.cardUp.setEnabled(False)
else:
self.dialog.cardUp.setEnabled(True)
if row == -1 or row >= (self.dialog.cardList.count() - 1):
self.dialog.cardDown.setEnabled(False)
else:
self.dialog.cardDown.setEnabled(True)
def updateToggleButtonText(self, card):
if card.active:
self.dialog.cardToggle.setText(_("Disa&ble"))
else:
self.dialog.cardToggle.setText(_("Ena&ble"))
def saveCurrentCard(self):
if not self.currentCard:
return
card = self.currentCard
newname = unicode(self.dialog.cardName.text())
if not newname:
newname = _("Card %d") % (self.m.cardModels.index(card) + 1)
self.updateField(card, 'name', newname)
self.updateField(card, 'description', unicode(
self.dialog.cardDescription.toPlainText()))
s = unicode(self.dialog.cardQuestion.toPlainText()).strip()
s = s.replace("\n", "<br>")
changed = self.updateField(card, 'qformat', s)
s = unicode(self.dialog.cardAnswer.toPlainText()).strip()
s = s.replace("\n", "<br>")
changed2 = self.updateField(card, 'aformat', s)
changed = changed or changed2
self.updateField(card, 'questionInAnswer', self.dialog.questionInAnswer.isChecked())
if changed:
# need to generate all question/answers for this card
self.deck.updateCardsFromModel(self.currentCard)
self.ignoreCardUpdate = True
self.updateCards()
self.ignoreCardUpdate = False
def updateField(self, obj, field, value):
if getattr(obj, field) != value:
setattr(obj, field, value)
self.m.setModified()
self.deck.setModified()
return True
return False
def addCard(self):
cards = len(self.m.cardModels)
name = _("Card %d") % (cards+1)
cm = CardModel(name=name)
self.m.addCardModel(cm)
self.updateCards()
self.dialog.cardList.setCurrentRow(len(self.m.cardModels)-1)
self.dialog.cardName.setFocus()
self.dialog.cardName.selectAll()
def deleteCard(self):
row = self.dialog.cardList.currentRow()
if row == -1:
return
if len (self.m.cardModels) < 2:
ui.utils.showWarning(
_("Please add a new card first."),
parent=self)
return
card = self.m.cardModels[row]
count = self.deck.cardModelUseCount(card)
if count:
if not ui.utils.askUser(
_("This model is used by %d cards. If you delete it,\n"
"all the cards will be deleted too. If you just\n"
"want to prevent the creation of future cards with\n"
"this model, please use the 'disable' button\n"
"instead.\n\nReally delete these cards?") % count,
parent=self):
return
self.deck.deleteCardModel(self.m, card)
self.updateCards()
def toggleCard(self):
row = self.dialog.cardList.currentRow()
if row == -1:
return
card = self.m.cardModels[row]
active = 0
for c in self.m.cardModels:
if c.active:
active += 1
if active < 2 and card.active:
ui.utils.showWarning(
_("Please enable a different model first."),
parent=self)
return
card.active = not card.active
self.updateToggleButtonText(card)
self.updateCards()
self.m.setModified()
self.deck.setModified()
def moveCardUp(self):
row = self.dialog.cardList.currentRow()
if row == -1:
return
if row == 0:
return
card = self.m.cardModels[row]
tCard = self.m.cardModels[row - 1]
self.m.cardModels.remove(card)
self.m.cardModels.insert(row - 1, card)
if card.id not in self.cardOrdinalUpdatedIds:
self.cardOrdinalUpdatedIds.append(card.id)
if tCard.id not in self.cardOrdinalUpdatedIds:
self.cardOrdinalUpdatedIds.append(tCard.id)
self.ignoreCardUpdate = True
self.updateCards(row - 1)
self.ignoreCardUpdate = False
def moveCardDown(self):
row = self.dialog.cardList.currentRow()
if row == -1:
return
if row == len(self.m.cardModels) - 1:
return
card = self.m.cardModels[row]
tCard = self.m.cardModels[row + 1]
self.m.cardModels.remove(card)
self.m.cardModels.insert(row + 1, card)
if card.id not in self.cardOrdinalUpdatedIds:
self.cardOrdinalUpdatedIds.append(card.id)
if tCard.id not in self.cardOrdinalUpdatedIds:
self.cardOrdinalUpdatedIds.append(tCard.id)
self.ignoreCardUpdate = True
self.updateCards(row + 1)
self.ignoreCardUpdate = False
# Cleanup
##########################################################################
def reject(self):
"Save user settings on close."
# update properties
mname = unicode(self.dialog.name.text())
if not mname:
mname = _("Model")
self.updateField(self.m, 'name', mname)
self.updateField(self.m, 'description',
unicode(self.dialog.description.toPlainText()))
self.updateField(self.m, 'tags',
unicode(self.dialog.tags.text()))
self.updateField(self.m, 'features',
unicode(self.dialog.decorators.text()))
try:
self.updateField(self.m, 'spacing',
float(self.dialog.spacing.text()))
self.updateField(self.m, 'initialSpacing',
float(self.dialog.initialSpacing.text())*60)
except ValueError:
pass
# before field, or it's overwritten
self.saveCurrentCard()
self.saveCurrentField()
# rebuild ordinals if changed
if len(self.fieldOrdinalUpdatedIds) > 0:
self.deck.rebuildFieldOrdinals(self.m.id, self.fieldOrdinalUpdatedIds)
self.m.setModified()
self.deck.setModified()
if len(self.cardOrdinalUpdatedIds) > 0:
self.deck.rebuildCardOrdinals(self.cardOrdinalUpdatedIds)
self.m.setModified()
self.deck.setModified()
# if changed, reset deck
if self.origModTime != self.deck.modified:
self.parent.reset()
if self.onFinish:
self.onFinish()
QDialog.reject(self)