diff --git a/anki.bat b/anki.bat old mode 100644 new mode 100755 diff --git a/aqt/about.py b/aqt/about.py index 3755aea85..5a8f9b2cf 100644 --- a/aqt/about.py +++ b/aqt/about.py @@ -29,13 +29,13 @@ Duggan, Matthew Holtz, Meelis Vasser, Michael Penkov, Michael Keppler, Michal Petr Michalec, Piotr Kubowicz, Richard Colley, Samson Melamed, Stefaan De Pooter, Susanna Björverud, Tacutu, Timm Preetz, Timo Paulssen, Ursus, Victor Suba, and Xtru. - -Anki icon by Alex Fraser (CC GNU GPL) -Deck icon by Laurent Baumann (CC BY-NC-SA 3.0) -Deck browser icons from: -http://led24.de/iconset -http://p.yusukekamiyamane.com/ -Other icons under LGPL or public domain. +

+Anki icon by Alex Fraser (CC GNU GPL)
+Deck icon by Laurent Baumann (CC BY-NC-SA 3.0)
+Deck browser icons from:
+http://led24.de/iconset
+http://p.yusukekamiyamane.com/
+Other icons under LGPL or public domain.
""" } diff --git a/aqt/addcards.py b/aqt/addcards.py index 01ca8ac90..27b5a9c91 100644 --- a/aqt/addcards.py +++ b/aqt/addcards.py @@ -9,7 +9,7 @@ import anki from anki.errors import * from anki.utils import stripHTML from aqt.utils import saveGeom, restoreGeom, showWarning, askUser, shortcut, \ - tooltip + tooltip, openHelp from anki.sound import clearAudioQueue from anki.hooks import addHook, removeHook from anki.utils import stripHTMLMedia, isMac @@ -46,7 +46,7 @@ class AddCards(QDialog): self.mw, self.form.modelArea) def helpRequested(self): - aqt.openHelp("AddItems") + openHelp("AddItems") def setupButtons(self): bb = self.form.buttonBox diff --git a/aqt/browser.py b/aqt/browser.py index 2c36b3de7..0bd8de803 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -10,7 +10,7 @@ import anki, anki.utils, aqt.forms from anki.utils import fmtTimeSpan, ids2str, stripHTMLMedia, isWin, intTime from aqt.utils import saveGeom, restoreGeom, saveSplitter, restoreSplitter, \ saveHeader, restoreHeader, saveState, restoreState, applyStyles, getTag, \ - showInfo, askUser, tooltip + showInfo, askUser, tooltip, openHelp from anki.errors import * from anki.db import * from anki.hooks import runHook, addHook, removeHook @@ -69,7 +69,7 @@ class DataModel(QAbstractTableModel): return if role == Qt.FontRole: f = QFont() - f.setPixelSize(self.browser.mw.config['editFontSize']) + f.setPixelSize(self.browser.mw.pm.profile['editFontSize']) return f if role == Qt.TextAlignmentRole: align = Qt.AlignVCenter @@ -112,7 +112,7 @@ class DataModel(QAbstractTableModel): # the db progress handler may cause a refresh, so we need to zero out # old data first self.cards = [] - self.cards = self.col.findCards(txt, self.browser.mw.config['fullSearch']) + self.cards = self.col.findCards(txt, self.browser.mw.pm.profile['fullSearch']) print "fetch cards in %dms" % ((time.time() - t)*1000) if reset: self.endReset() @@ -332,8 +332,8 @@ class Browser(QMainWindow): self.onSearch() def setupToolbar(self): - self.form.toolBar.setIconSize(QSize(self.mw.config['iconSize'], - self.mw.config['iconSize'])) + self.form.toolBar.setIconSize(QSize(self.mw.pm.profile['iconSize'], + self.mw.pm.profile['iconSize'])) self.form.toolBar.toggleViewAction().setText(_("Toggle Toolbar")) def setupMenus(self): @@ -372,10 +372,10 @@ class Browser(QMainWindow): def updateFont(self): self.form.tableView.setFont(QFont( - self.mw.config['editFontFamily'], - self.mw.config['editFontSize'])) + self.mw.pm.profile['editFontFamily'], + self.mw.pm.profile['editFontSize'])) self.form.tableView.verticalHeader().setDefaultSectionSize( - self.mw.config['editLineSize']) + self.mw.pm.profile['editLineSize']) def closeEvent(self, evt): saveSplitter(self.form.splitter_2, "editor2") @@ -432,7 +432,7 @@ class Browser(QMainWindow): self.onSearch) self.setTabOrder(self.form.searchEdit, self.form.tableView) self.compModel = QStringListModel() - self.compModel.setStringList(self.mw.config['searchHistory']) + self.compModel.setStringList(self.mw.pm.profile['searchHistory']) self.searchComp = QCompleter(self.compModel, self.form.searchEdit) self.searchComp.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.searchComp.setCaseSensitivity(Qt.CaseInsensitive) @@ -441,18 +441,18 @@ class Browser(QMainWindow): def onSearch(self, reset=True): "Careful: if reset is true, the current note is saved." txt = unicode(self.form.searchEdit.text()).strip() - sh = self.mw.config['searchHistory'] + sh = self.mw.pm.profile['searchHistory'] if txt not in sh: sh.insert(0, txt) sh = sh[:30] self.compModel.setStringList(sh) - self.mw.config['searchHistory'] = sh + self.mw.pm.profile['searchHistory'] = sh self.model.search(txt, reset) if not self.model.cards: # no row change will fire self.onRowChanged(None, None) txt = _("No matches found.") - if not self.mw.config['fullSearch']: + if not self.mw.pm.profile['fullSearch']: txt += "

" + _( _("If your cards have formatting, you may want
" "to enable 'search within formatting' in the
" @@ -834,7 +834,7 @@ where id in %s""" % ids2str(sf)) return sf def onHelp(self): - aqt.openHelp("Browser") + openHelp("Browser") # Misc menu options ###################################################################### @@ -1066,18 +1066,18 @@ where id in %s""" % ids2str(self.selectedCards()), mod) frm = aqt.forms.browseropts.Ui_Dialog() frm.setupUi(d) frm.fontCombo.setCurrentFont(QFont( - self.mw.config['editFontFamily'])) - frm.fontSize.setValue(self.mw.config['editFontSize']) - frm.lineSize.setValue(self.mw.config['editLineSize']) - frm.fullSearch.setChecked(self.mw.config['fullSearch']) + self.mw.pm.profile['editFontFamily'])) + frm.fontSize.setValue(self.mw.pm.profile['editFontSize']) + frm.lineSize.setValue(self.mw.pm.profile['editLineSize']) + frm.fullSearch.setChecked(self.mw.pm.profile['fullSearch']) if d.exec_(): - self.mw.config['editFontFamily'] = ( + self.mw.pm.profile['editFontFamily'] = ( unicode(frm.fontCombo.currentFont().family())) - self.mw.config['editFontSize'] = ( + self.mw.pm.profile['editFontSize'] = ( int(frm.fontSize.value())) - self.mw.config['editLineSize'] = ( + self.mw.pm.profile['editLineSize'] = ( int(frm.lineSize.value())) - self.mw.config['fullSearch'] = frm.fullSearch.isChecked() + self.mw.pm.profile['fullSearch'] = frm.fullSearch.isChecked() self.updateFont() # Edit: replacing @@ -1130,7 +1130,7 @@ where id in %s""" % ids2str(self.selectedCards()), mod) }) def onFindReplaceHelp(self): - aqt.openHelp("Browser#FindReplace") + openHelp("Browser#FindReplace") # Edit: finding dupes ###################################################################### @@ -1311,7 +1311,7 @@ select id from cards where nid in %s and ord in %s""" % ( self.browser.onSearch() def onHelp(self): - aqt.openHelp("Browser#GenerateCards") + openHelp("Browser#GenerateCards") # Change model dialog ###################################################################### @@ -1477,4 +1477,4 @@ Are you sure you want to continue?""")): return QDialog.accept(self) def onHelp(self): - aqt.openHelp("Browser#ChangeModel") + openHelp("Browser#ChangeModel") diff --git a/aqt/clayout.py b/aqt/clayout.py index 2298fde8d..b6fa7e4d7 100644 --- a/aqt/clayout.py +++ b/aqt/clayout.py @@ -7,439 +7,217 @@ from anki.consts import * import aqt from anki.sound import playFromText, clearAudioQueue from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA, \ - saveSplitter, restoreSplitter, showInfo, askUser, getText + saveSplitter, restoreSplitter, showInfo, askUser, getOnlyText, \ + showWarning, openHelp from anki.utils import isMac, isWin import aqt.templates -# fixme: replace font substitutions with native comma list - -class ResizingTextEdit(QTextEdit): - def sizeHint(self): - return QSize(200, 800) +# raise Exception("Remember to disallow media&latex refs in edit.") class CardLayout(QDialog): - # type is previewCards() type - def __init__(self, mw, note, type=0, ord=0, parent=None): + def __init__(self, mw, note, ord=0, parent=None): QDialog.__init__(self, parent or mw, Qt.Window) - raise Exception("Remember to disallow media&latex refs in edit.") self.mw = aqt.mw self.parent = parent or mw self.note = note - self.type = type self.ord = ord self.col = self.mw.col self.mm = self.mw.col.models self.model = note.model() - self.form = aqt.forms.clayout.Ui_Dialog() - self.form.setupUi(self) + self.setupTabs() 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()"), + v1 = QVBoxLayout() + v1.addWidget(self.tabs) + self.bbox = QDialogButtonBox( + QDialogButtonBox.Close|QDialogButtonBox.Help) + v1.addWidget(self.bbox) + self.setLayout(v1) + + self.connect(self.bbox, 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") + self.bbox.button(QDialogButtonBox.Help).setAutoDefault(False) + self.bbox.button(QDialogButtonBox.Close).setAutoDefault(False) + self.mw.checkpoint(_("Card Layout")) + self.redraw() restoreGeom(self, "CardLayout") - if not self.reload(first=True): - return self.exec_() - def reload(self, first=False): - self.cards = self.col.previewCards(self.note, self.type) - if not self.cards: - self.accept() - if first: - showInfo(_("Please enter some text first.")) - else: - showInfo(_("The current note was deleted.")) - return - self.fillCardList() - self.fillFieldList() - self.fieldChanged() - self.readField() - return True + def redraw(self): + self.cards = self.col.previewCards(self.note, 2) + self.redrawing = True + self.updateTabs() + self.redrawing = False + self.selectCard(self.ord) + + def setupTabs(self): + c = self.connect + self.tabs = QTabWidget() + self.tabs.setTabsClosable(True) + self.tabs.setUsesScrollButtons(True) + add = QPushButton("+") + add.setFixedWidth(30) + c(add, SIGNAL("clicked()"), self.onAddCard) + self.tabs.setCornerWidget(add) + c(self.tabs, SIGNAL("currentChanged(int)"), self.selectCard) + c(self.tabs, SIGNAL("tabCloseRequested(int)"), self.onRemoveTab) + + def updateTabs(self): + self.forms = [] + self.tabs.clear() + for t in self.model['tmpls']: + self.addTab(t) + + def addTab(self, t): + c = self.connect + w = QWidget() + h = QHBoxLayout() + h.addStretch() + rename = QPushButton("Rename") + c(rename, SIGNAL("clicked()"), self.onRename) + h.addWidget(rename) + order = QPushButton(_("Reposition")) + h.addWidget(order) + c(order, SIGNAL("clicked()"), self.onReorder) + h.addStretch() + v = QVBoxLayout() + v.setMargin(3) + v.setSpacing(3) + v.addLayout(h) + l = QHBoxLayout() + l.setMargin(0) + l.setSpacing(3) + left = QWidget() + # template area + tform = aqt.forms.template.Ui_Form() + tform.setupUi(left) + c(tform.front, SIGNAL("textChanged()"), self.onTemplateEdit) + c(tform.back, SIGNAL("textChanged()"), self.onTemplateEdit) + l.addWidget(left, 5) + # preview area + right = QWidget() + pform = aqt.forms.preview.Ui_Form() + pform.setupUi(right) + def linkClicked(url): + QDesktopServices.openUrl(QUrl(url)) + for wig in pform.front, pform.back: + wig.page().setLinkDelegationPolicy( + QWebPage.DelegateExternalLinks) + c(wig, SIGNAL("linkClicked(QUrl)"), linkClicked) + l.addWidget(right, 5) + v.addLayout(l) + w.setLayout(v) + self.forms.append({'tform': tform, 'pform': pform}) + self.tabs.addTab(w, t['name']) + + def onRemoveTab(self, idx): + if not self.mm.remTemplate(self.model, self.cards[idx].template()): + return showWarning(_("""\ +Removing this card would cause one or more notes to be deleted. \ +Please create a new card first.""")) + self.redraw() # 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 note:")) - 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) - c(f.clozectx, SIGNAL("clicked()"), self.saveCard) - 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(alignmentLabels().values()) - self.typeFieldNames = self.mm.fieldMap(self.model) - 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, 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: + def selectCard(self, idx): + if self.redrawing: 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) - # model-level, but there's nowhere else to put this - f.clozectx.setChecked(self.model['clozectx']) - 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(cards) - self.form.cardList.setCurrentIndex(idx) - self.cardChanged(idx) - self.form.cardList.setFocus() - - def cardChanged(self, idx): + self.ord = idx self.card = self.cards[idx] + self.tab = self.forms[idx] + self.tabs.setCurrentIndex(idx) self.readCard() self.renderPreview() - def saveCard(self): - if self.updatingCards: - return + def readCard(self): 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.model['clozectx'] = self.form.clozectx.isChecked() + self.redrawing = True + self.tab['tform'].front.setPlainText(t['qfmt']) + self.tab['tform'].back.setPlainText(t['afmt']) + self.redrawing = False + + def onTemplateEdit(self): + if self.redrawing: + return + text = self.tab['tform'].front.toPlainText() + self.card.template()['qfmt'] = text + text = self.tab['tform'].back.toPlainText() + self.card.template()['afmt'] = text 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 saveCard(self): + t = self.card.template() + self.renderPreview() def renderPreview(self): + print "preview" c = self.card - styles = self.model['css'] - styles += "\n.cloze { font-weight: bold; color: blue; }" - self.form.preview.setHtml( - ('%s' % - (getBase(self.col), 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 + styles = "\n.cloze { font-weight: bold; color: blue; }" + html = '%s' + self.tab['pform'].front.setHtml( + html % (styles, mungeQA(c.q(reload=True)))) + self.tab['pform'].back.setHtml( + html % (styles, mungeQA(c.a()))) def maybeTextInput(self): + return "text input" if self.card.template()['typeAns'] is not None: return "
" return "" + def onRename(self): + name = getOnlyText(_("New name:")) + if not name: + return + if name in [c.template()['name'] for c in self.cards + if c.template()['ord'] != self.ord]: + return showWarning(_("That name is already used.")) + self.card.template()['name'] = name + self.tabs.setTabText(self.tabs.currentIndex(), name) + + def onReorder(self): + n = len(self.cards) + cur = self.card.template()['ord']+1 + pos = getOnlyText( + _("Enter new card position (1..%s):") % n, + default=str(cur)) + if not pos: + return + try: + pos = int(pos) + except ValueError: + return + if pos < 1 or pos > n: + return + if pos == cur: + return + pos -= 1 + self.mm.moveTemplate(self.model, self.card.template(), pos) + self.ord = pos + self.redraw() + + def onAddCard(self): + name = getOnlyText(_("Name:")) + if not name: + return + if name in [c.template()['name'] for c in self.cards]: + return showWarning(_("That name is already used.")) + t = self.mm.newTemplate(name) + self.mm.addTemplate(self.model, t) + self.redraw() + + # Closing & Help + ###################################################################### + def accept(self): self.reject() def reject(self): self.mm.save(self.model) - saveGeom(self, "CardLayout") - saveSplitter(self.form.splitter, "clayout") self.mw.reset() + saveGeom(self, "CardLayout") return QDialog.reject(self) - - modified = False - self.mw.startProgress() - self.col.updateProgress(_("Applying changes...")) - reset=True - if len(self.fieldOrdinalUpdatedIds) > 0: - self.col.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds) - modified = True - if self.needFieldRebuild: - modified = True - if modified: - self.note.model.setModified() - self.col.flushMod() - if self.noteedit and self.noteedit.onChange: - self.noteedit.onChange("all") - reset=False - if reset: - self.mw.reset() - self.col.finishProgress() - QDialog.reject(self) - def onHelp(self): - aqt.openHelp("CardLayout") - - # Fields - ########################################################################## - - def setupFields(self): - self.fieldOrdinalUpdatedIds = [] - self.updatingFields = False - self.needFieldRebuild = False - c = self.connect; f = self.form - sc = SIGNAL("stateChanged(int)") - cl = SIGNAL("clicked()") - c(f.fieldAdd, cl, self.addField) - c(f.fieldDelete, cl, self.deleteField) - c(f.fieldUp, cl, self.moveFieldUp) - c(f.fieldDown, cl, self.moveFieldDown) - c(f.preserveWhitespace, sc, self.saveField) - c(f.fieldUnique, sc, self.saveField) - c(f.fieldRequired, sc, self.saveField) - c(f.sticky, sc, self.saveField) - c(f.fieldList, SIGNAL("currentRowChanged(int)"), - self.fieldChanged) - c(f.fieldName, SIGNAL("lostFocus()"), - self.saveField) - c(f.fontFamily, SIGNAL("currentFontChanged(QFont)"), - self.saveField) - c(f.fontSize, SIGNAL("valueChanged(int)"), - self.saveField) - c(f.fontSizeEdit, SIGNAL("valueChanged(int)"), - self.saveField) - w = self.form.fontColour - if self.plastiqueStyle: - w.setStyle(self.plastiqueStyle) - c(w, SIGNAL("clicked()"), - lambda w=w: self.chooseColour(w)) - c(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['flds'][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']) - f.sticky.setChecked(fld['sticky']) - 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() - fld['sticky'] = self.form.sticky.isChecked() - self.updatingFields = False - if fld['name'] != name: - self.mm.renameField(self.model, fld, name) - # as the field name has changed, we have to regenerate cards - self.cards = self.col.previewCards(self.note, 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['flds']: - 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.mm.newField(self.model) - l = len(self.model['flds']) - f['name'] = _("Field %d") % l - self.mw.progress.start() - self.mm.addField(self.model, 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 notes?")): - 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() + openHelp("CardLayout") diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 0fb566ecd..0fa206db1 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -3,7 +3,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from aqt.qt import * -from aqt.utils import askUser +from aqt.utils import askUser, getOnlyText class DeckBrowser(object): @@ -27,7 +27,7 @@ class DeckBrowser(object): if cmd == "open": self._selDeck(arg) elif cmd == "opts": - self._optsForRow(int(arg)) + self._showOptions(arg) elif cmd == "download": self.mw.onGetSharedDeck() elif cmd == "new": @@ -43,7 +43,7 @@ class DeckBrowser(object): def _selDeck(self, did): self.mw.col.decks.select(did) - self.mw.moveToState("overview") + self.mw.onOverview() # HTML generation ########################################################################## @@ -85,13 +85,15 @@ body { margin: 1em; } def _deckRow(self, node, depth): name, did, due, new, children = node + def indent(): + return " "*3*depth # due image - buf = "" + self._dueImg(due, new) + buf = "" + indent() + self._dueImg(due, new) # deck link buf += " %s"% (did, name) # options buf += "%s" % self.mw.button( - link="opts:%d"%did, name=_("Options")+'▾') + link="opts:%d"%did, name="▾") # children buf += self._renderDeckTree(children, depth+1) return buf @@ -108,25 +110,29 @@ body { margin: 1em; } # Options ########################################################################## - def _optsForRow(self, n): + def _showOptions(self, did): m = QMenu(self.mw) - # delete - a = m.addAction(QIcon(":/icons/editdelete.png"), _("Delete")) - a.connect(a, SIGNAL("triggered()"), lambda n=n: self._deleteRow(n)) + a = m.addAction(_("Rename")) + a.connect(a, SIGNAL("triggered()"), lambda did=did: self._rename(did)) + a = m.addAction(_("Delete")) + a.connect(a, SIGNAL("triggered()"), lambda did=did: self._delete(did)) m.exec_(QCursor.pos()) - def _deleteRow(self, c): - d = self._decks[c] - if d['state'] == 'missing': - return self._hideRow(c) + def _rename(self, did): + deck = self.mw.col.decks.get(did) + newName = getOnlyText(_("New deck name:")) + if not newName: + return + if deck in self.mw.col.decks.allNames(): + return showWarning(_("That deck already exists.")) + self.mw.col.decks.rename(deck, newName) + self.show() + + def _delete(self, did): + if did == 1: + return showWarning(_("The default deck can't be deleted.")) + deck = self.mw.col.decks.get(did) if askUser(_("""\ -Delete %s? If this deck is synchronized the online version will \ -not be touched.""") % d['name']): - deck = d['path'] - os.unlink(deck) - try: - shutil.rmtree(re.sub(".anki$", ".media", deck)) - except OSError: - pass - self.mw.config.delRecentDeck(deck) - self.refresh() +Are you sure you wish to delete all of the cards in %s?""")%deck['name']): + self.mw.col.decks.rem(did, True) + self.show() diff --git a/aqt/deckopts.py b/aqt/deckopts.py index 5c12573b8..b4b3f411f 100644 --- a/aqt/deckopts.py +++ b/aqt/deckopts.py @@ -4,7 +4,7 @@ from aqt.qt import * import sys, re import aqt -from aqt.utils import maybeHideClose +from aqt.utils import maybeHideClose, openHelp class ColOptions(QDialog): @@ -28,7 +28,7 @@ class ColOptions(QDialog): self.form.mediaURL.setText(self.d.conf['mediaURL']) def helpRequested(self): - aqt.openHelp("ColOptions") + openHelp("ColOptions") def reject(self): needSync = False diff --git a/aqt/editor.py b/aqt/editor.py index c21058b43..9acc8a20a 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -9,7 +9,8 @@ from anki.sound import play from anki.hooks import runHook from aqt.sound import getAudio from aqt.webview import AnkiWebView -from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile +from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \ + openHelp import aqt import anki.js @@ -449,7 +450,7 @@ class Editor(object): form = aqt.forms.edithtml.Ui_Dialog() form.setupUi(d) d.connect(form.buttonBox, SIGNAL("helpRequested()"), - lambda: aqt.openHelp("HtmlEditor")) + lambda: openHelp("HtmlEditor")) form.textEdit.setPlainText(self.note.fields[self.currentField]) form.textEdit.moveCursor(QTextCursor.End) d.exec_() @@ -582,7 +583,7 @@ class Editor(object): txtcol) def colourChanged(self): - recent = self.mw.config['recentColours'] + recent = self.mw.pm.profile['recentColours'] self._updateForegroundButton(recent[-1]) def onForeground(self): @@ -606,7 +607,7 @@ class Editor(object): self.colourChoose = QShortcut(QKeySequence("F6"), p) p.connect(self.colourChoose, SIGNAL("activated()"), self.onChooseColourKey) - for n, c in enumerate(reversed(self.mw.config['recentColours'])): + for n, c in enumerate(reversed(self.mw.pm.profile['recentColours'])): col = QToolButton() col.setAutoRaise(True) col.setFixedWidth(64) @@ -640,7 +641,7 @@ class Editor(object): p.show() def onRemoveColour(self, colour): - recent = self.mw.config['recentColours'] + recent = self.mw.pm.profile['recentColours'] recent.remove(colour) if not recent: recent.append("#000000") @@ -659,7 +660,7 @@ class Editor(object): pass def onChooseColour(self, colour): - recent = self.mw.config['recentColours'] + recent = self.mw.pm.profile['recentColours'] recent.remove(colour) recent.append(colour) self.web.eval("setFormat('forecolor', '%s')" % colour) @@ -669,7 +670,7 @@ class Editor(object): def onNewColour(self): new = QColorDialog.getColor(Qt.white, self.widget) self.widget.raise_() - recent = self.mw.config['recentColours'] + recent = self.mw.pm.profile['recentColours'] if new.isValid(): txtcol = unicode(new.name()) if txtcol not in recent: @@ -698,7 +699,7 @@ class Editor(object): # copy to media folder name = self.mw.col.media.addFile(path) # remove original? - if canDelete and self.mw.config['deleteMedia']: + if canDelete and self.mw.pm.profile['deleteMedia']: if os.path.abspath(name) != os.path.abspath(path): try: os.unlink(old) @@ -738,7 +739,7 @@ class Editor(object): ###################################################################### def setupKeyboard(self): - if isWin and self.mw.config['preserveKeyboard']: + if isWin and self.mw.pm.profile['preserveKeyboard']: a = ctypes.windll.user32.ActivateKeyboardLayout a.restype = ctypes.c_void_p a.argtypes = [ctypes.c_void_p, ctypes.c_uint] @@ -773,7 +774,7 @@ class EditorWebView(AnkiWebView): AnkiWebView.__init__(self, parent) self.editor = editor self.errtxt = _("An error occured while opening %s") - self.strip = self.editor.mw.config['stripHTML'] + self.strip = self.editor.mw.pm.profile['stripHTML'] def keyPressEvent(self, evt): self._curKey = True diff --git a/aqt/fields.py b/aqt/fields.py new file mode 100644 index 000000000..1d52d33a5 --- /dev/null +++ b/aqt/fields.py @@ -0,0 +1,495 @@ +# Copyright: Damien Elmes +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +from aqt.qt import * +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, askUser, getText, \ + openHelp +from anki.utils import isMac, isWin +import aqt.templates + +# raise Exception("Remember to disallow media&latex refs in edit.") + +# need to strip the field management code out of this + +class CardLayout(QDialog): + + def __init__(self, mw, note, ord=0, parent=None): + QDialog.__init__(self, parent or mw, Qt.Window) + self.mw = aqt.mw + self.parent = parent or mw + self.note = note + self.ord = ord + self.col = self.mw.col + self.mm = self.mw.col.models + self.model = note.model() + self.setupTabs() + v1 = QVBoxLayout() + v1.addWidget(self.tabs) + self.bbox = QDialogButtonBox(QDialogButtonBox.Close) + v1.addWidget(self.bbox) + self.setLayout(v1) + self.updateTabs() + self.exec_() + return + + def setupTabs(self): + self.tabs = QTabWidget() + self.tabs.setTabsClosable(True) + self.tabs.setUsesScrollButtons(True) + self.tabs.setMovable(True) + add = QPushButton("+") + add.setFixedWidth(30) + self.tabs.setCornerWidget(add) + + def updateTabs(self): + self.forms = [] + self.tabs.clear() + for t in self.model['tmpls']: + self.addTab(t) + + def addTab(self, t): + w = QWidget() + h = QHBoxLayout() + h.addStretch() + label = QLabel(_("Name:")) + h.addWidget(label) + edit = QLineEdit() + edit.setFixedWidth(200) + h.addWidget(edit) + h.addStretch() + v = QVBoxLayout() + v.addLayout(h) + l = QHBoxLayout() + l.setMargin(0) + l.setSpacing(3) + left = QWidget() + # template area + tform = aqt.forms.template.Ui_Form() + tform.setupUi(left) + l.addWidget(left, 5) + # preview area + right = QWidget() + pform = aqt.forms.preview.Ui_Form() + pform.setupUi(right) + l.addWidget(right, 5) + v.addLayout(l) + w.setLayout(v) + self.tabs.addTab(w, t['name']) + self.forms.append([tform, pform, edit]) + + def old(): + 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") + if not self.reload(first=True): + return + self.exec_() + + + def reload(self, first=False): + self.cards = self.col.previewCards(self.note, self.type) + if not self.cards: + self.accept() + if first: + showInfo(_("Please enter some text first.")) + else: + showInfo(_("The current note was deleted.")) + return + self.fillCardList() + self.fillFieldList() + self.fieldChanged() + self.readField() + return True + + # 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 note:")) + 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) + c(f.clozectx, SIGNAL("clicked()"), self.saveCard) + def linkClicked(url): + QDesktopServices.openUrl(QUrl(url)) + f.preview.page().setLinkDelegationPolicy( + QWebPage.DelegateExternalLinks) + self.connect(f.preview, + SIGNAL("linkClicked(QUrl)"), + linkClicked) + f.alignment.addItems(alignmentLabels().values()) + self.typeFieldNames = self.mm.fieldMap(self.model) + 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, 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) + # model-level, but there's nowhere else to put this + f.clozectx.setChecked(self.model['clozectx']) + 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(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.model['clozectx'] = self.form.clozectx.isChecked() + 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['css'] + styles += "\n.cloze { font-weight: bold; color: blue; }" + self.form.preview.setHtml( + ('%s' % + (getBase(self.col), 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): + return QDialog.reject(self) + self.mm.save(self.model) + saveGeom(self, "CardLayout") + saveSplitter(self.form.splitter, "clayout") + self.mw.reset() + return QDialog.reject(self) + + + modified = False + self.mw.startProgress() + self.col.updateProgress(_("Applying changes...")) + reset=True + if len(self.fieldOrdinalUpdatedIds) > 0: + self.col.rebuildFieldOrdinals(self.model.id, self.fieldOrdinalUpdatedIds) + modified = True + if self.needFieldRebuild: + modified = True + if modified: + self.note.model.setModified() + self.col.flushMod() + if self.noteedit and self.noteedit.onChange: + self.noteedit.onChange("all") + reset=False + if reset: + self.mw.reset() + self.col.finishProgress() + QDialog.reject(self) + + def onHelp(self): + openHelp("CardLayout") + + # Fields + ########################################################################## + + def setupFields(self): + self.fieldOrdinalUpdatedIds = [] + self.updatingFields = False + self.needFieldRebuild = False + c = self.connect; f = self.form + sc = SIGNAL("stateChanged(int)") + cl = SIGNAL("clicked()") + c(f.fieldAdd, cl, self.addField) + c(f.fieldDelete, cl, self.deleteField) + c(f.fieldUp, cl, self.moveFieldUp) + c(f.fieldDown, cl, self.moveFieldDown) + c(f.preserveWhitespace, sc, self.saveField) + c(f.fieldUnique, sc, self.saveField) + c(f.fieldRequired, sc, self.saveField) + c(f.sticky, sc, self.saveField) + c(f.fieldList, SIGNAL("currentRowChanged(int)"), + self.fieldChanged) + c(f.fieldName, SIGNAL("lostFocus()"), + self.saveField) + c(f.fontFamily, SIGNAL("currentFontChanged(QFont)"), + self.saveField) + c(f.fontSize, SIGNAL("valueChanged(int)"), + self.saveField) + c(f.fontSizeEdit, SIGNAL("valueChanged(int)"), + self.saveField) + w = self.form.fontColour + c(w, SIGNAL("clicked()"), + lambda w=w: self.chooseColour(w)) + c(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['flds'][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']) + f.sticky.setChecked(fld['sticky']) + 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() + fld['sticky'] = self.form.sticky.isChecked() + self.updatingFields = False + if fld['name'] != name: + self.mm.renameField(self.model, fld, name) + # as the field name has changed, we have to regenerate cards + self.cards = self.col.previewCards(self.note, 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['flds']: + 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.mm.newField(self.model) + l = len(self.model['flds']) + f['name'] = _("Field %d") % l + self.mw.progress.start() + self.mm.addField(self.model, 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 notes?")): + 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() diff --git a/aqt/getshared.py b/aqt/getshared.py index 083130cc1..eae2e1ff0 100644 --- a/aqt/getshared.py +++ b/aqt/getshared.py @@ -51,7 +51,7 @@ Error was:
%s
""") self.form.table, SIGNAL("currentCellChanged(int,int,int,int)"), self.onCellChanged) self.form.table.verticalHeader().setDefaultSectionSize( - self.parent.config['editLineSize']) + self.parent.pm.profile['editLineSize']) self.connect(self.form.search, SIGNAL("textChanged(QString)"), self.limit) @@ -222,7 +222,7 @@ Error was:
%s
""") tit = tit[0:40] if self.type == 0: # col - dd = self.parent.config['documentDir'] + dd = self.parent.pm.profile['documentDir'] p = os.path.join(dd, tit + ".anki") if os.path.exists(p): tit += "%d" % time.time() diff --git a/aqt/groupconf.py b/aqt/groupconf.py index 304e8d09a..c4946cbdb 100644 --- a/aqt/groupconf.py +++ b/aqt/groupconf.py @@ -5,7 +5,7 @@ from aqt.qt import * import aqt, simplejson from anki.utils import ids2str -from aqt.utils import showInfo, showWarning +from aqt.utils import showInfo, showWarning, openHelp # Configuration editing ########################################################################## @@ -24,7 +24,7 @@ class GroupConf(QDialog): self.setup() self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: aqt.openHelp("GroupOptions")) + lambda: openHelp("GroupOptions")) self.connect(self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults), SIGNAL("clicked()"), self.onRestore) diff --git a/aqt/groups.py b/aqt/groups.py index d34dd75de..12789827f 100644 --- a/aqt/groups.py +++ b/aqt/groups.py @@ -4,7 +4,7 @@ from aqt.qt import * import aqt -from aqt.utils import showInfo, getOnlyText +from aqt.utils import showInfo, getOnlyText, openHelp COLNAME = 0 COLOPTS = 1 @@ -79,7 +79,7 @@ class Groups(QDialog): button(f.delete_2, self.onDelete) self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: aqt.openHelp("Groups")) + lambda: openHelp("Groups")) def onSelectAll(self): for i in self.items: diff --git a/aqt/importing.py b/aqt/importing.py index 99d530420..2100b9528 100644 --- a/aqt/importing.py +++ b/aqt/importing.py @@ -65,7 +65,7 @@ class UpdateMap(QDialog): self.exec_() def helpRequested(self): - aqt.openHelp("FileImport") + openHelp("FileImport") def accept(self): self.updateKey = ( @@ -311,4 +311,4 @@ you can enter it here. Use \\t to represent tab."""), QDialog.reject(self) def helpRequested(self): - aqt.openHelp("FileImport") + openHelp("FileImport") diff --git a/aqt/main.py b/aqt/main.py index 6c8263b94..dddf41178 100755 --- a/aqt/main.py +++ b/aqt/main.py @@ -191,8 +191,10 @@ Are you sure?"""): # maybe sync self.onSync() # then load collection and launch into the deck browser + print "fixme: safeguard against multiple instances" self.col = Collection(self.pm.collectionPath()) - self.moveToState("overview") + # skip the reset step; open overview directly + self.moveToState("review") def unloadProfile(self): self.col = None @@ -633,8 +635,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") def onCardLayout(self): from aqt.clayout import CardLayout - CardLayout(self, self.reviewer.card.note(), type=1, - ord=self.reviewer.card.ord) + CardLayout(self, self.reviewer.card.note(), ord=self.reviewer.card.ord) def onDeckOpts(self): import aqt.deckopts @@ -797,7 +798,7 @@ Please choose a new deck name:""")) self.form.actionDelete.setEnabled(True) self.form.actionBuryNote.setEnabled(True) self.form.actionEditCurrent.setEnabled(True) - self.form.actionEditdeck.setEnabled(True) + self.form.actionBrowse.setEnabled(True) self.updateMarkAction() runHook("enableCardMenuItems") diff --git a/aqt/modelchooser.py b/aqt/modelchooser.py index 9ca540231..75705f8c7 100644 --- a/aqt/modelchooser.py +++ b/aqt/modelchooser.py @@ -196,4 +196,4 @@ class AddModel(QDialog): QDialog.accept(self) def onHelp(self): - aqt.openHelp("AddModel") + openHelp("AddModel") diff --git a/aqt/models.py b/aqt/models.py index 42ceec5ea..6e4870559 100644 --- a/aqt/models.py +++ b/aqt/models.py @@ -15,7 +15,7 @@ class Models(QDialog): self.form = aqt.forms.models.Ui_Dialog() self.form.setupUi(self) self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: aqt.openHelp("Models")) + lambda: openHelp("Models")) self.setupModels() self.exec_() diff --git a/aqt/overview.py b/aqt/overview.py index 91746c3bf..753470bc0 100644 --- a/aqt/overview.py +++ b/aqt/overview.py @@ -6,7 +6,7 @@ import simplejson from aqt.qt import * from anki.consts import NEW_CARDS_RANDOM from anki.hooks import addHook -from aqt.utils import limitedCount, showInfo +from aqt.utils import showInfo class Overview(object): "Deck overview." @@ -57,9 +57,6 @@ class Overview(object): ############################################################ def _renderPage(self): - css = self.mw.sharedCSS + self._overviewCSS - fc = self._ovForecast() - tbl = self._overviewTable() but = self.mw.button deck = self.mw.col.decks.current() sid = deck.get("sharedFrom") @@ -67,85 +64,67 @@ class Overview(object): shareLink = 'Reviews and Updates' else: shareLink = "" - header = "" - self.web.stdHtml(self._overviewBody % dict( - title=_("Overview"), - table=tbl, - fcsub=_("Reviews over next two weeks"), + print self._body % dict( deck=deck['name'], shareLink=shareLink, - desc="", - header=header, - fcdata=fc, - ), css) + desc=self._desc(deck), + table=self._table()) + self.web.stdHtml(self._body % dict( + deck=deck['name'], + shareLink=shareLink, + desc=self._desc(deck), + table=self._table() + ), self.mw.sharedCSS + self._css) - _overviewBody = """ -%(header)s + def _desc(self, deck): + desc = deck.get("desc", "") + if not desc: + return "" + if len(desc) < 160: + return '
%s
' % desc + else: + return ''' +
%s\ +...More
+
%s
''' % ( + desc[:160], desc) + + def _table(self): + counts = self.mw.col.sched.repCounts() + finished = not sum(counts) + but = self.mw.button + if finished: + return '
%s
' % ( + self.mw.col.sched.finishedMsg()) + else: + return ''' + +
+ + + + +
%s:%s
%s:%s
%s:%s
+
%s
''' % (_("New"), counts[0], + _("In Learning"), counts[1], + _("To Review"), counts[2], + but("study", _("Study"))) + + _body = """

%(deck)s

%(shareLink)s %(desc)s

-

-%(fcsub)s -

+%(table)s

- - """ - _overviewCSS = """ -.due { text-align: right; } -.new { text-align: right; } -.sub { font-size: 80%; color: #555; } -.smallLink { font-size: 12px; } + _css = """ +.smallLink { font-size: 10px; } h3 { margin-bottom: 0; } +.fin { font-size: 12px; font-weight: normal; } +td { font-size: 14px; } """ - def _overviewTable(self): - return "" - but = self.mw.button - buf = "" - buf += "" % _("Due") - buf += "" % _("New") - line = "" - line += "" - buf += line % ( - "%s" % _("Selected Groups"), - counts[0], counts[1], - but("study", _("Study"), _("s"), "gbut", id="study") + - but("cram", _("Cram"), "c")) - buf += line % ( - _("Whole Deck"), - counts[2], counts[3], - but("opts", _("Study Options"))) - buf += "
%s%s
%s%s%s%s
" - return buf - def _ovOpts(self): - return "" - - # Data - ########################################################################## - - def _ovForecast(self): - fc = self.mw.col.sched.dueForecast(14) - if not sum(fc): - return "'%s'" % _('No cards due in next two weeks') - return simplejson.dumps(tuple(enumerate(fc))) diff --git a/aqt/preferences.py b/aqt/preferences.py index 10a7b71d1..51844050c 100644 --- a/aqt/preferences.py +++ b/aqt/preferences.py @@ -13,12 +13,12 @@ class Preferences(QDialog): def __init__(self, mw): QDialog.__init__(self, mw, Qt.Window) self.mw = mw - self.config = mw.config + self.config = mw.pm.config self.form = aqt.forms.preferences.Ui_Preferences() self.form.setupUi(self) self.needDeckClose = False self.connect(self.form.buttonBox, SIGNAL("helpRequested()"), - lambda: aqt.openHelp("Preferences")) + lambda: openHelp("Preferences")) self.setupLang() self.setupNetwork() self.setupBackup() diff --git a/aqt/reviewer.py b/aqt/reviewer.py index f6cd5e54f..00ab263e8 100644 --- a/aqt/reviewer.py +++ b/aqt/reviewer.py @@ -89,36 +89,18 @@ class Reviewer(object): ########################################################################## _revHtml = """ -
-
-
-
-
-
- -
%(showans)s
-
-
-
+
-%s""" % ( - css, anki.js.all, bodyClass, body), loadCB) +%s""" % ( + css, anki.js.all, bodyID, body), loadCB) # ensure we're focused self.setFocus() def setBridge(self, bridge): diff --git a/designer/clayout.ui b/designer/clayout.ui index ed134990b..2969298ca 100644 --- a/designer/clayout.ui +++ b/designer/clayout.ui @@ -6,546 +6,152 @@ 0 0 - 759 - 560 + 1311 + 658 - - - - - Qt::Horizontal - - - false - - - - - - - - 2 - 0 - - - - 0 - - - - Appearance - - - - - - 6 - - - - - - 2 - 0 - - - - - 50 - 0 - - - - Qt::ScrollBarAlwaysOff - - - true - - - - - - - - 0 - 0 - - - - - 50 - 0 - - - - Qt::ScrollBarAlwaysOff - - - true - - - false - - - - - - - - - Question - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - Flip - - - - :/icons/multisynk.png:/icons/multisynk.png - - - - - - - Answer - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - - - Alignment - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - Background - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - false - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - &Manage... - - - Ctrl+M - - - - - - - - - - - - - - Hide the question when showing answer - - - - - - - Add cards even if answer is blank - - - - - - - Include context in cloze answers - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Fields - - - - - - - - - 0 - 0 - - - - - 50 - 60 - - - - - - - - - - &Add - - - false - - - - - - - Move selected field up - - - &Up - - - false - - - - - - - Move selected field down - - - Dow&n - - - false - - - - - - - &Delete - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - Name - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Font - - - - - - - Size - - - - - - - Color - - - - - - - - 0 - 25 - - - - - - - - true - - - - - - false - - - - - - - - - Reviewing - - - - - - - 5 - - - 300 - - - - - - - Editing - - - - - - - 5 - - - 300 - - - - - - - Qt::Horizontal - - - - 1 - 20 - - - - - - - - - - Options - - - - - - - Reverse text direction (RTL) - - - - - - - Require text in field - - - - - - - Prevent duplicates - - - - - - - Preserve whitespace - - - - - - - Keep previous input when adding cards - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Close|QDialogButtonBox::Help - - - - - - + + + + 20 + 120 + 360 + 366 + + + + + 6 + + + - - 1 + + 2 0 - - Preview + + + 50 + 0 + + + + Qt::ScrollBarAlwaysOff + + + true - - - 6 - - - - - - 1 - 0 - - - - Qt::NoFocus - - - - about:blank - - - - - - - - + + + + + + 0 + 0 + + + + + 50 + 0 + + + + Qt::ScrollBarAlwaysOff + + + true + + + false + + + + + + + + + Question + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + Answer + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + + + + + + + + + 20 + 500 + 366 + 26 + + + + + + + 20 + 540 + 371 + 20 + + + + Include context in cloze answers + + + + + + 450 + 90 + 308 + 499 + + + + + 1 + 0 + + + + Qt::NoFocus + + + + about:blank + + + @@ -555,70 +161,11 @@ - tabWidget - cardList - editTemplates cardQuestion - flipButton cardAnswer - alignment - background - typeAnswer - questionInAnswer - allowEmptyAnswer - clozectx - buttonBox - fieldList - fieldAdd - fieldUp - fieldDown - fieldDelete - fieldName - fontFamily - fontSize - fontSizeEdit - fontColour - fieldUnique - fieldRequired - sticky - preserveWhitespace - rtl - - - buttonBox - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + diff --git a/designer/fields.ui b/designer/fields.ui new file mode 100644 index 000000000..25dcb1064 --- /dev/null +++ b/designer/fields.ui @@ -0,0 +1,243 @@ + + + Dialog + + + + 0 + 0 + 372 + 340 + + + + Fields + + + + + + + + + 0 + 0 + + + + + 50 + 60 + + + + + + + + + + + + + + :/icons/list-add.png:/icons/list-add.png + + + false + + + + + + + + + + + :/icons/editdelete.png:/icons/editdelete.png + + + false + + + + + + + Move selected field up + + + + + + + :/icons/arrow-up.png:/icons/arrow-up.png + + + false + + + + + + + Move selected field down + + + + + + + :/icons/arrow-down.png:/icons/arrow-down.png + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Font + + + + + + + + 0 + 25 + + + + + + + + Options + + + + + + + Reverse text direction (RTL) + + + + + + + 5 + + + 300 + + + + + + + Remember last input + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Help + + + + + + + fieldList + fieldAdd + fieldDelete + fieldUp + fieldDown + fieldName + fontFamily + fontSize + sticky + rtl + buttonBox + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/icons.qrc b/designer/icons.qrc index a6a9f8855..cfde582db 100644 --- a/designer/icons.qrc +++ b/designer/icons.qrc @@ -1,6 +1,9 @@ + icons/arrow-up.png + icons/arrow-down.png icons/blue.png + icons/gears.png icons/both.png icons/green.png icons/clock-icon.png diff --git a/designer/icons/gears.png b/designer/icons/gears.png new file mode 100644 index 000000000..3c2bc3336 Binary files /dev/null and b/designer/icons/gears.png differ diff --git a/designer/preview.ui b/designer/preview.ui new file mode 100644 index 000000000..ba66fc64d --- /dev/null +++ b/designer/preview.ui @@ -0,0 +1,82 @@ + + + Form + + + + 0 + 0 + 335 + 282 + + + + Form + + + + 0 + + + + + Front Preview + + + + 0 + + + + + + 1 + 0 + + + + Qt::NoFocus + + + + about:blank + + + + + + + + + + + Back Preview + + + + 0 + + + + + + about:blank + + + + + + + + + + + + QWebView + QWidget +
QtWebKit/QWebView
+
+
+ + +
diff --git a/designer/template.ui b/designer/template.ui new file mode 100644 index 000000000..c46ef20b0 --- /dev/null +++ b/designer/template.ui @@ -0,0 +1,68 @@ + + + Form + + + + 0 + 0 + 279 + 251 + + + + + 0 + 0 + + + + Form + + + + 0 + + + + + Front Template + + + + 0 + + + + + + + + + + + + 10 + 0 + + + + Back Template + + + + 0 + + + + + + + + + + + + + +