# Copyright: Damien Elmes # 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, \ getText import aqt.templates 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.setWindowTitle(_("%s Layout") % self.model.name) self.plastiqueStyle = None if isMac or isWin: self.plastiqueStyle = QStyleFactory.create("plastique") 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.")) return self.exec_() def reload(self): self.cards = self.deck.previewCards(self.fact, self.type) if not self.cards: self.accept() showInfo(_( "The current fact was deleted.")) self return 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]) c = self.connect c(f.cardList, SIGNAL("activated(int)"), self.cardChanged) c(f.editTemplates, SIGNAL("clicked()"), self.onEdit) 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("}}
", "}}\n") return fmt def screenToFormat(self, fmt): fmt = fmt.replace("}}\n", "}}
") return fmt def onEdit(self): aqt.templates.Templates(self.mw, self.model, self) self.reload() 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.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( ('%s' % (getBase(self.deck), c.cssClass())) + "" + mungeQA(c.q(reload=True)) + self.maybeTextInput() + "
" + mungeQA(c.a()) + "") 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 "
" return "" def accept(self): self.reject() def reject(self): self.model.flush() saveGeom(self, "CardLayout") saveSplitter(self.form.splitter, "clayout") self.mw.reset() 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.")) return if askUser(_("Delete this field and its data from all facts?")): 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()