Anki/aqt/clayout.py

442 lines
16 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 *
from PyQt4.QtWebKit import QWebPage, QWebView
import re
from anki.consts import *
import aqt
from anki.sound import playFromText, clearAudioQueue
from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA, \
saveSplitter, restoreSplitter, showInfo, isMac, isWin, askUser
from anki.hooks import runFilter
class ResizingTextEdit(QTextEdit):
def sizeHint(self):
return QSize(200, 800)
class CardLayout(QDialog):
# type is previewCards() type
def __init__(self, mw, fact, type=0, ord=0, parent=None):
QDialog.__init__(self, parent or mw, Qt.Window)
self.mw = aqt.mw
self.parent = parent or mw
self.fact = fact
self.type = type
self.ord = ord
self.deck = self.mw.deck
self.model = fact.model()
self.form = aqt.forms.clayout.Ui_Dialog()
self.form.setupUi(self)
self.plastiqueStyle = None
if isMac or isWin:
self.plastiqueStyle = QStyleFactory.create("plastique")
# FIXME: add editing
self.form.editTemplates.hide()
self.connect(self.form.buttonBox, SIGNAL("helpRequested()"),
self.onHelp)
self.setupCards()
self.setupFields()
self.form.buttonBox.button(QDialogButtonBox.Help).setAutoDefault(False)
self.form.buttonBox.button(QDialogButtonBox.Close).setAutoDefault(False)
restoreSplitter(self.form.splitter, "clayout")
restoreGeom(self, "CardLayout")
self.reload()
if not self.cards:
showInfo(_("Please enter some text first."),
parent=self.parent)
return
self.exec_()
def reload(self):
self.cards = self.deck.previewCards(self.fact, self.type)
self.fillCardList()
self.fillFieldList()
self.fieldChanged()
self.readField()
# Cards & Preview
##########################################################################
def setupCards(self):
self.updatingCards = False
self.playedAudio = {}
f = self.form
if self.type == 0:
f.templateType.setText(
_("Templates that will be created:"))
elif self.type == 1:
f.templateType.setText(
_("Templates used by fact:"))
else:
f.templateType.setText(
_("All templates:"))
# replace with more appropriate size hints
for e in ("cardQuestion", "cardAnswer"):
w = getattr(f, e)
idx = f.templateLayout.indexOf(w)
r = f.templateLayout.getItemPosition(idx)
f.templateLayout.removeWidget(w)
w.hide()
w.deleteLater()
w = ResizingTextEdit(self)
setattr(f, e, w)
f.templateLayout.addWidget(w, r[0], r[1])
self.connect(f.cardList, SIGNAL("activated(int)"),
self.cardChanged)
# self.connect(f.editTemplates, SIGNAL("clicked())"),
# self.onEdit)
c = self.connect
c(f.cardQuestion, SIGNAL("textChanged()"), self.formatChanged)
c(f.cardAnswer, SIGNAL("textChanged()"), self.formatChanged)
c(f.alignment, SIGNAL("activated(int)"), self.saveCard)
c(f.background, SIGNAL("clicked()"),
lambda w=f.background:\
self.chooseColour(w, "card"))
c(f.questionInAnswer, SIGNAL("clicked()"), self.saveCard)
c(f.allowEmptyAnswer, SIGNAL("clicked()"), self.saveCard)
c(f.typeAnswer, SIGNAL("activated(int)"), self.saveCard)
c(f.flipButton, SIGNAL("clicked()"), self.onFlip)
def linkClicked(url):
QDesktopServices.openUrl(QUrl(url))
f.preview.page().setLinkDelegationPolicy(
QWebPage.DelegateExternalLinks)
self.connect(f.preview,
SIGNAL("linkClicked(QUrl)"),
linkClicked)
if self.plastiqueStyle:
f.background.setStyle(self.plastiqueStyle)
f.alignment.addItems(
QStringList(alignmentLabels().values()))
self.typeFieldNames = self.model.fieldMap()
s = [_("Don't ask me to type in the answer")]
s += [_("Compare with field '%s'") % fi
for fi in self.typeFieldNames.keys()]
f.typeAnswer.insertItems(0, QStringList(s))
def formatToScreen(self, fmt):
fmt = fmt.replace("}}<br>", "}}\n")
return fmt
def screenToFormat(self, fmt):
fmt = fmt.replace("}}\n", "}}<br>")
return fmt
# def onEdit(self):
# ui.modelproperties.ModelProperties(
# self, self.deck, self.model, self.mw,
# onFinish=self.updateModelsList)
def formatChanged(self):
if self.updatingCards:
return
text = unicode(self.form.cardQuestion.toPlainText())
self.card.template()['qfmt'] = self.screenToFormat(text)
text = unicode(self.form.cardAnswer.toPlainText())
self.card.template()['afmt'] = self.screenToFormat(text)
self.renderPreview()
def onFlip(self):
q = unicode(self.form.cardQuestion.toPlainText())
a = unicode(self.form.cardAnswer.toPlainText())
self.form.cardAnswer.setPlainText(q)
self.form.cardQuestion.setPlainText(a)
def readCard(self):
self.updatingCards = True
t = self.card.template()
f = self.form
f.background.setPalette(QPalette(QColor(t['bg'])))
f.cardQuestion.setPlainText(self.formatToScreen(t['qfmt']))
f.cardAnswer.setPlainText(self.formatToScreen(t['afmt']))
f.questionInAnswer.setChecked(t['hideQ'])
f.allowEmptyAnswer.setChecked(t['emptyAns'])
f.alignment.setCurrentIndex(t['align'])
if t['typeAns'] is None:
f.typeAnswer.setCurrentIndex(0)
else:
f.typeAnswer.setCurrentIndex(t['typeAns'] + 1)
self.updatingCards = False
def fillCardList(self):
self.form.cardList.clear()
cards = []
idx = 0
for n, c in enumerate(self.cards):
if c.ord == self.ord:
cards.append(_("%s (current)") % c.template()['name'])
idx = n
else:
cards.append(c.template()['name'])
self.form.cardList.addItems(
QStringList(cards))
self.form.editTemplates.setEnabled(False)
self.form.cardList.setCurrentIndex(idx)
self.cardChanged(idx)
self.form.cardList.setFocus()
def cardChanged(self, idx):
self.card = self.cards[idx]
self.readCard()
self.renderPreview()
def saveCard(self):
if self.updatingCards:
return
t = self.card.template()
t['align'] = self.form.alignment.currentIndex()
t['bg'] = unicode(
self.form.background.palette().window().color().name())
t['hideQ'] = self.form.questionInAnswer.isChecked()
t['emptyAns'] = self.form.allowEmptyAnswer.isChecked()
idx = self.form.typeAnswer.currentIndex()
if not idx:
t['typeAns'] = None
else:
t['typeAns'] = idx-1
self.renderPreview()
def chooseColour(self, button, type="field"):
new = QColorDialog.getColor(button.palette().window().color(), self,
_("Choose Color"),
QColorDialog.DontUseNativeDialog)
if new.isValid():
button.setPalette(QPalette(new))
if type == "field":
self.saveField()
else:
self.saveCard()
def renderPreview(self):
c = self.card
styles = self.model.genCSS()
self.form.preview.setHtml(
('<html><head>%s</head><body class="%s">' %
(getBase(self.deck, c), c.cssClass())) +
"<style>" + styles + "</style>" +
mungeQA(c.q(reload=True)) +
self.maybeTextInput() +
"<hr>" +
mungeQA(c.a())
+ "</body></html>")
clearAudioQueue()
if c.id not in self.playedAudio:
playFromText(c.q())
playFromText(c.a())
self.playedAudio[c.id] = True
def maybeTextInput(self):
if self.card.template()['typeAns'] is not None:
return "<center><input type=text></center>"
return ""
def reject(self):
self.model.flush()
saveGeom(self, "CardLayout")
saveSplitter(self.form.splitter, "clayout")
return QDialog.reject(self)
self.fact.model.setModified()
modified = False
self.mw.startProgress()
self.deck.updateProgress(_("Applying changes..."))
reset=True
if len(self.fieldOrdinalUpdatedIds) > 0:
self.deck.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds)
modified = True
if self.needFieldRebuild:
modified = True
if modified:
self.fact.model.setModified()
self.deck.flushMod()
if self.factedit and self.factedit.onChange:
self.factedit.onChange("all")
reset=False
if reset:
self.mw.reset()
self.deck.finishProgress()
QDialog.reject(self)
def onHelp(self):
aqt.openHelp("CardLayout")
# Fields
##########################################################################
def setupFields(self):
self.fieldOrdinalUpdatedIds = []
self.updatingFields = False
self.needFieldRebuild = False
self.connect(self.form.fieldList, SIGNAL("currentRowChanged(int)"),
self.fieldChanged)
self.connect(self.form.fieldAdd, SIGNAL("clicked()"),
self.addField)
self.connect(self.form.fieldDelete, SIGNAL("clicked()"),
self.deleteField)
self.connect(self.form.fieldUp, SIGNAL("clicked()"),
self.moveFieldUp)
self.connect(self.form.fieldDown, SIGNAL("clicked()"),
self.moveFieldDown)
self.connect(self.form.fieldName, SIGNAL("lostFocus()"),
self.fillFieldList)
self.connect(self.form.fontFamily, SIGNAL("currentFontChanged(QFont)"),
self.saveField)
self.connect(self.form.fontSize, SIGNAL("valueChanged(int)"),
self.saveField)
self.connect(self.form.fontSizeEdit, SIGNAL("valueChanged(int)"),
self.saveField)
self.connect(self.form.fieldName, SIGNAL("textEdited(QString)"),
self.saveField)
self.connect(self.form.preserveWhitespace, SIGNAL("stateChanged(int)"),
self.saveField)
self.connect(self.form.fieldUnique, SIGNAL("stateChanged(int)"),
self.saveField)
self.connect(self.form.fieldRequired, SIGNAL("stateChanged(int)"),
self.saveField)
w = self.form.fontColour
if self.plastiqueStyle:
w.setStyle(self.plastiqueStyle)
self.connect(w, SIGNAL("clicked()"),
lambda w=w: self.chooseColour(w))
self.connect(self.form.rtl,
SIGNAL("stateChanged(int)"),
self.saveField)
def fieldChanged(self):
row = self.form.fieldList.currentRow()
if row == -1:
row = 0
self.field = self.model.fields[row]
self.readField()
self.enableFieldMoveButtons()
def readField(self):
fld = self.field
f = self.form
self.updatingFields = True
f.fieldName.setText(fld['name'])
f.fieldUnique.setChecked(fld['uniq'])
f.fieldRequired.setChecked(fld['req'])
f.fontFamily.setCurrentFont(QFont(fld['font']))
f.fontSize.setValue(fld['qsize'])
f.fontSizeEdit.setValue(fld['esize'])
f.fontColour.setPalette(QPalette(QColor(fld['qcol'])))
f.rtl.setChecked(fld['rtl'])
f.preserveWhitespace.setChecked(fld['pre'])
self.updatingFields = False
def saveField(self, *args):
self.needFieldRebuild = True
if self.updatingFields:
return
self.updatingFields = True
fld = self.field
# get name; we'll handle it last
name = unicode(self.form.fieldName.text())
if not name:
return
fld['uniq'] = self.form.fieldUnique.isChecked()
fld['req'] = self.form.fieldRequired.isChecked()
fld['font'] = unicode(
self.form.fontFamily.currentFont().family())
fld['qsize'] = self.form.fontSize.value()
fld['esize'] = self.form.fontSizeEdit.value()
fld['qcol'] = str(
self.form.fontColour.palette().window().color().name())
fld['rtl'] = self.form.rtl.isChecked()
fld['pre'] = self.form.preserveWhitespace.isChecked()
self.updatingFields = False
if fld['name'] != name:
self.model.renameField(fld, name)
# as the field name has changed, we have to regenerate cards
self.cards = self.deck.previewCards(self.fact, self.type)
self.cardChanged(0)
self.renderPreview()
self.fillFieldList()
def fillFieldList(self, row = None):
oldRow = self.form.fieldList.currentRow()
if oldRow == -1:
oldRow = 0
self.form.fieldList.clear()
n = 1
for field in self.model.fields:
label = field['name']
item = QListWidgetItem(label)
self.form.fieldList.addItem(item)
n += 1
count = self.form.fieldList.count()
if row != None:
self.form.fieldList.setCurrentRow(row)
else:
while (count > 0 and oldRow > (count - 1)):
oldRow -= 1
self.form.fieldList.setCurrentRow(oldRow)
self.enableFieldMoveButtons()
def enableFieldMoveButtons(self):
row = self.form.fieldList.currentRow()
if row < 1:
self.form.fieldUp.setEnabled(False)
else:
self.form.fieldUp.setEnabled(True)
if row == -1 or row >= (self.form.fieldList.count() - 1):
self.form.fieldDown.setEnabled(False)
else:
self.form.fieldDown.setEnabled(True)
def addField(self):
f = self.model.newField()
l = len(self.model.fields)
f['name'] = _("Field %d") % l
self.mw.progress.start()
self.model.addField(f)
self.mw.progress.finish()
self.reload()
self.form.fieldList.setCurrentRow(l)
self.form.fieldName.setFocus()
self.form.fieldName.selectAll()
def deleteField(self):
row = self.form.fieldList.currentRow()
if row == -1:
return
if len(self.model.fields) < 2:
showInfo(
_("Please add a new field first."),
parent=self)
return
if askUser(_("Delete this field and its data from all facts?"),
self.parent):
self.mw.progress.start()
self.model.delField(self.field)
self.mw.progress.finish()
# need to update q/a format
self.reload()
def moveFieldUp(self):
row = self.form.fieldList.currentRow()
if row == -1:
return
if row == 0:
return
self.mw.progress.start()
self.model.moveField(self.field, row-1)
self.mw.progress.finish()
self.form.fieldList.setCurrentRow(row-1)
self.reload()
def moveFieldDown(self):
row = self.form.fieldList.currentRow()
if row == -1:
return
if row == len(self.model.fields) - 1:
return
self.mw.progress.start()
self.model.moveField(self.field, row+1)
self.mw.progress.finish()
self.form.fieldList.setCurrentRow(row+1)
self.reload()