modelchooser and card adding

This commit is contained in:
Damien Elmes 2011-04-06 23:14:34 +09:00
parent 5b7daf5060
commit 4d694f0064
7 changed files with 188 additions and 236 deletions

View file

@ -9,16 +9,16 @@ import anki
from anki.facts import Fact from anki.facts import Fact
from anki.errors import * from anki.errors import *
from anki.utils import stripHTML, parseTags from anki.utils import stripHTML, parseTags
from aqt.utils import saveGeom, restoreGeom from aqt.utils import saveGeom, restoreGeom, showWarning, askUser
from anki.sound import clearAudioQueue from anki.sound import clearAudioQueue
from anki.hooks import addHook, removeHook from anki.hooks import addHook, removeHook
import aqt.editor, aqt.modelchooser import aqt.editor, aqt.modelchooser
class FocusButton(QPushButton): # todo:
def focusInEvent(self, evt): # if field.fieldModel.features:
if evt.reason() == Qt.TabFocusReason: # w.setLayoutDirection(Qt.RightToLeft)
self.emit(SIGNAL("tabIn")) # else:
QPushButton.focusInEvent(self, evt) # w.setLayoutDirection(Qt.LeftToRight)
class AddCards(QDialog): class AddCards(QDialog):
@ -26,39 +26,37 @@ class AddCards(QDialog):
windParent = None windParent = None
QDialog.__init__(self, windParent, Qt.Window) QDialog.__init__(self, windParent, Qt.Window)
self.mw = mw self.mw = mw
self.dialog = aqt.forms.addcards.Ui_Dialog() self.form = aqt.forms.addcards.Ui_Dialog()
self.dialog.setupUi(self) self.form.setupUi(self)
self.setWindowTitle(_("Add")) self.setWindowTitle(_("Add"))
self.setupEditor() self.setupEditor()
self.addChooser() self.setupChooser()
self.addButtons() self.setupButtons()
self.modelChanged() self.onReset()
self.addedItems = 0 self.addedItems = 0
self.forceClose = False self.forceClose = False
restoreGeom(self, "add") restoreGeom(self, "add")
addHook('reset', self.onReset)
self.setupNewFact()
self.show() self.show()
addHook('guiReset', self.modelChanged)
def setupEditor(self): def setupEditor(self):
self.editor = aqt.editor.Editor(self.mw, self.dialog.fieldsArea) self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea)
# get a fact for testing # get a fact for testing
fact = self.mw.deck.getFact(3951) #fact = self.mw.deck.getFact(3951)
self.editor.setFact(fact) #self.editor.setFact(fact)
def addChooser(self): def setupChooser(self):
return self.modelChooser = aqt.modelchooser.ModelChooser(
self.modelChooser = aqt.modelchooser.ModelChooser(self, self.mw, self.form.modelArea)
self.mw, # modelChanged func
self.mw.deck,
self.modelChanged)
self.dialog.modelArea.setLayout(self.modelChooser)
def helpRequested(self): def helpRequested(self):
aqt.openHelp("AddItems") aqt.openHelp("AddItems")
def addButtons(self): def setupButtons(self):
self.addButton = QPushButton(_("Add")) self.addButton = QPushButton(_("Add"))
self.dialog.buttonBox.addButton(self.addButton, self.form.buttonBox.addButton(self.addButton,
QDialogButtonBox.ActionRole) QDialogButtonBox.ActionRole)
self.addButton.setShortcut(_("Ctrl+Return")) self.addButton.setShortcut(_("Ctrl+Return"))
if sys.platform.startswith("darwin"): if sys.platform.startswith("darwin"):
@ -70,11 +68,11 @@ class AddCards(QDialog):
self.connect(self.addButton, SIGNAL("clicked()"), self.addCards) self.connect(self.addButton, SIGNAL("clicked()"), self.addCards)
self.closeButton = QPushButton(_("Close")) self.closeButton = QPushButton(_("Close"))
self.closeButton.setAutoDefault(False) self.closeButton.setAutoDefault(False)
self.dialog.buttonBox.addButton(self.closeButton, self.form.buttonBox.addButton(self.closeButton,
QDialogButtonBox.RejectRole) QDialogButtonBox.RejectRole)
self.helpButton = QPushButton(_("Help")) self.helpButton = QPushButton(_("Help"))
self.helpButton.setAutoDefault(False) self.helpButton.setAutoDefault(False)
self.dialog.buttonBox.addButton(self.helpButton, self.form.buttonBox.addButton(self.helpButton,
QDialogButtonBox.HelpRole) QDialogButtonBox.HelpRole)
self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested) self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested)
@ -84,31 +82,41 @@ class AddCards(QDialog):
browser.updateSearch() browser.updateSearch()
browser.onFact() browser.onFact()
def modelChanged(self, model=None): # FIXME: need to make sure to clean up fact on exit
return def setupNewFact(self, set=True):
f = self.mw.deck.newFact()
if self.editor.fact:
# keep tags?
f.tags = self.editor.fact.tags
if set:
self.editor.setFact(f)
return f
def onReset(self, model=None):
oldFact = self.editor.fact oldFact = self.editor.fact
# create a new fact fact = self.setupNewFact(set=False)
fact = self.mw.deck.newFact()
# copy fields from old fact # copy fields from old fact
if oldFact: if oldFact:
n = 0 self.removeTempFact(oldFact)
for field in fact.fields: for n in range(len(fact._fields)):
try: try:
field.value = oldFact.fields[n].value fact._fields[n] = oldFact._fields[n]
except IndexError: except IndexError:
break break
n += 1 self.editor.setFact(fact)
fact.tags = oldFact.tags #self.setTabOrder(self.editor.tags, self.addButton)
else: #self.setTabOrder(self.addButton, self.closeButton)
fact.tags = "last" #self.mw.deck.lastTags #self.setTabOrder(self.closeButton, self.helpButton)
# set the new fact
self.editor.setFact(fact, check=True, forceRedraw=True) def removeTempFact(self, fact):
self.setTabOrder(self.editor.tags, self.addButton) if not fact:
self.setTabOrder(self.addButton, self.closeButton) return
self.setTabOrder(self.closeButton, self.helpButton) # we don't have to worry about cards; just the fact
self.mw.deck._delFacts([fact.id])
def reportAddedFact(self, fact): def reportAddedFact(self, fact):
self.dialog.status.append( return
self.form.status.append(
_("Added %(num)d card(s) for <a href=\"%(id)d\">" _("Added %(num)d card(s) for <a href=\"%(id)d\">"
"%(str)s</a>.") % { "%(str)s</a>.") % {
"num": len(fact.cards), "num": len(fact.cards),
@ -118,57 +126,31 @@ class AddCards(QDialog):
}) })
def addFact(self, fact): def addFact(self, fact):
try: if any(fact.problems()):
fact = self.mw.deck.addFact(fact, False) showWarning(_(
except FactInvalidError:
ui.utils.showInfo(_(
"Some fields are missing or not unique."), "Some fields are missing or not unique."),
parent=self, help="AddItems#AddError") help="AddItems#AddError")
return return
if not fact: cards = self.mw.deck.addFact(fact)
ui.utils.showWarning(_("""\ if not cards:
showWarning(_("""\
The input you have provided would make an empty The input you have provided would make an empty
question or answer on all cards."""), parent=self) question or answer on all cards."""), help="AddItems")
return return
self.reportAddedFact(fact) self.reportAddedFact(fact)
# we don't reset() until the add cards dialog is closed # FIXME: return to overview on add?
return fact return fact
def initializeNewFact(self, old_fact):
f = self.mw.deck.newFact()
f.tags = self.mw.deck.lastTags
return f
def clearOldFact(self, old_fact):
f = self.initializeNewFact(old_fact)
self.editor.setFact(f, check=True, scroll=True)
# let completer know our extra tags
self.editor.tags.addTags(parseTags(self.mw.deck.lastTags))
return f
def addCards(self): def addCards(self):
# make sure updated self.editor.saveNow()
self.editor.saveFieldsNow()
fact = self.editor.fact fact = self.editor.fact
n = _("Add")
self.mw.deck.setUndoStart(n)
fact = self.addFact(fact) fact = self.addFact(fact)
if not fact: if not fact:
return return
# stop anything playing # stop anything playing
clearAudioQueue() clearAudioQueue()
self.setupNewFact()
self.mw.deck.setUndoEnd(n) self.mw.deck.autosave()
self.mw.deck.rebuildCounts()
self.mw.updateTitleBar()
# start a new fact
self.clearOldFact(fact)
self.mw.autosave()
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
"Show answer on RET or register answer." "Show answer on RET or register answer."
@ -178,37 +160,20 @@ question or answer on all cards."""), parent=self)
return return
return QDialog.keyPressEvent(self, evt) return QDialog.keyPressEvent(self, evt)
def closeEvent(self, evt):
if self.onClose():
evt.accept()
else:
evt.ignore()
def reject(self): def reject(self):
# remove dupe if not self.canClose():
return
clearAudioQueue()
self.removeTempFact(self.editor.fact)
self.editor.setFact(None)
self.modelChooser.cleanup()
removeHook('reset', self.onReset)
saveGeom(self, "add")
aqt.dialogs.close("AddCards") aqt.dialogs.close("AddCards")
QDialog.reject(self) QDialog.reject(self)
def canClose(self):
if self.onClose():
self.modelChooser.deinit()
QDialog.reject(self)
def onClose(self):
return
removeHook('guiReset', self.modelChanged)
# stop anything playing
clearAudioQueue()
if (self.forceClose or self.editor.fieldsAreBlank() or if (self.forceClose or self.editor.fieldsAreBlank() or
ui.utils.askUser(_("Close and lose current input?"), askUser(_("Close and lose current input?"))):
self)):
self.modelChooser.deinit()
self.editor.close()
ui.dialogs.close("AddCards")
self.mw.deck.db.flush()
self.mw.deck.rebuildCSS()
self.mw.reset()
saveGeom(self, "add")
return True return True
else: return False
return False

View file

@ -13,6 +13,8 @@ from aqt.utils import saveGeom, restoreGeom, getBase, mungeQA, \
getText getText
import aqt.templates import aqt.templates
# fixme: replace font substitutions with native comma list
class ResizingTextEdit(QTextEdit): class ResizingTextEdit(QTextEdit):
def sizeHint(self): def sizeHint(self):
return QSize(200, 800) return QSize(200, 800)

View file

@ -15,13 +15,6 @@ from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile
import aqt import aqt
import anki.js import anki.js
# todo:
# if field.fieldModel.features:
# w.setLayoutDirection(Qt.RightToLeft)
# else:
# w.setLayoutDirection(Qt.LeftToRight)
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif") pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif")
audio = ("wav", "mp3", "ogg", "flac") audio = ("wav", "mp3", "ogg", "flac")
@ -175,6 +168,7 @@ $(function () {
</body></html> </body></html>
""" """
# caller is responsible for resetting fact on reset
class Editor(object): class Editor(object):
def __init__(self, mw, widget): def __init__(self, mw, widget):
self.widget = widget self.widget = widget
@ -184,20 +178,12 @@ class Editor(object):
self._keepButtons = False self._keepButtons = False
# current card, for card layout # current card, for card layout
self.card = None self.card = None
addHook("deckClosed", self.deckClosedHook)
addHook("guiReset", self.refresh)
addHook("colourChanged", self.colourChanged)
self.setupOuter() self.setupOuter()
self.setupButtons() self.setupButtons()
self.setupWeb() self.setupWeb()
self.setupTagsAndGroup() self.setupTagsAndGroup()
self.setupKeyboard() self.setupKeyboard()
def close(self):
removeHook("deckClosed", self.deckClosedHook)
removeHook("guiReset", self.refresh)
removeHook("colourChanged", self.colourChanged)
# Initial setup # Initial setup
############################################################ ############################################################
@ -380,15 +366,6 @@ class Editor(object):
return [(f['font'], f['esize']) return [(f['font'], f['esize'])
for f in self.fact.model().fields] for f in self.fact.model().fields]
def refresh(self):
if self.fact:
self.fact.load()
# fixme: what if fact is deleted?
self.setFact(self.fact)
def deckClosedHook(self):
self.setFact(None)
def saveNow(self): def saveNow(self):
"Must call this before adding cards, closing dialog, etc." "Must call this before adding cards, closing dialog, etc."
if not self.fact: if not self.fact:
@ -409,6 +386,14 @@ class Editor(object):
cols.append("#ffc") cols.append("#ffc")
self.web.eval("setBackgrounds(%s);" % simplejson.dumps(cols)) self.web.eval("setBackgrounds(%s);" % simplejson.dumps(cols))
def fieldsAreBlank(self):
if not self.fact:
return True
for f in self.fact._fields:
if f:
return False
return True
# HTML editing # HTML editing
###################################################################### ######################################################################
@ -592,7 +577,7 @@ class Editor(object):
recent.append("#000000") recent.append("#000000")
self.colourDiag.close() self.colourDiag.close()
self.onForeground() self.onForeground()
runHook("colourChanged") self.colourChanged()
def onNextColour(self): def onNextColour(self):
self.colourDiag.focusWidget().nextInFocusChain().setFocus() self.colourDiag.focusWidget().nextInFocusChain().setFocus()
@ -610,7 +595,7 @@ class Editor(object):
recent.append(colour) recent.append(colour)
self.web.eval("setFormat('forecolor', '%s')" % colour) self.web.eval("setFormat('forecolor', '%s')" % colour)
self.colourDiag.close() self.colourDiag.close()
runHook("colourChanged") self.colourChanged()
def onNewColour(self): def onNewColour(self):
new = QColorDialog.getColor(Qt.white, self.widget) new = QColorDialog.getColor(Qt.white, self.widget)
@ -620,7 +605,7 @@ class Editor(object):
txtcol = unicode(new.name()) txtcol = unicode(new.name())
if txtcol not in recent: if txtcol not in recent:
recent.append(txtcol) recent.append(txtcol)
runHook("colourChanged") self.colourChanged()
self.onChooseColour(txtcol) self.onChooseColour(txtcol)
# Audio/video/images # Audio/video/images

View file

@ -113,7 +113,12 @@ class AnkiQt(QMainWindow):
def _editCurrentState(self, oldState): def _editCurrentState(self, oldState):
pass pass
def reset(self): def factChanged(self, fid):
"Called when a card or fact is edited (but not deleted)."
runHook("factChanged", fid)
def reset(self, type="all", *args):
"Called for non-trivial edits. Rebuilds queue."
self.deck.reset() self.deck.reset()
runHook("reset") runHook("reset")

View file

@ -4,38 +4,42 @@
from PyQt4.QtGui import * from PyQt4.QtGui import *
from PyQt4.QtCore import * from PyQt4.QtCore import *
from operator import attrgetter from operator import attrgetter
import anki, sys
from anki import stdmodels from anki import stdmodels
from anki.models import * from anki.lang import ngettext
import aqt.forms
from anki.hooks import addHook, removeHook from anki.hooks import addHook, removeHook
from aqt.utils import isMac
class ModelChooser(QHBoxLayout): class ModelChooser(QHBoxLayout):
def __init__(self, parent, main, deck, onChangeFunc=None, cards=True, label=True): def __init__(self, mw, widget, cards=True, label=True):
QHBoxLayout.__init__(self) QHBoxLayout.__init__(self)
self.parent = parent self.widget = widget
self.main = main self.mw = mw
self.deck = deck self.deck = mw.deck
self.onChangeFunc = onChangeFunc self.handleCards = cards
self.label = label
self._ignoreReset = False
self.setMargin(0) self.setMargin(0)
self.setSpacing(4) self.setSpacing(4)
self.shortcuts = [] self.setupModels()
if label: self.setupTemplates()
addHook('reset', self.onReset)
self.widget.setLayout(self)
def setupModels(self):
if self.label:
self.modelLabel = QLabel(_("<b>Model</b>:")) self.modelLabel = QLabel(_("<b>Model</b>:"))
self.addWidget(self.modelLabel) self.addWidget(self.modelLabel)
# models dropdown
self.models = QComboBox() self.models = QComboBox()
s = QShortcut(QKeySequence(_("Shift+Alt+m")), self.parent) s = QShortcut(QKeySequence(_("Shift+Alt+m")), self.widget)
s.connect(s, SIGNAL("activated()"), s.connect(s, SIGNAL("activated()"),
lambda: self.models.showPopup()) lambda: self.models.showPopup())
self.drawModels()
sizePolicy = QSizePolicy(
QSizePolicy.Policy(7),
QSizePolicy.Policy(0))
self.models.setSizePolicy(sizePolicy)
self.addWidget(self.models) self.addWidget(self.models)
self.connect(self.models, SIGNAL("activated(int)"), self.onModelChange)
# edit button
self.edit = QPushButton() self.edit = QPushButton()
if not sys.platform.startswith("darwin"): if not isMac:
self.edit.setFixedWidth(32) self.edit.setFixedWidth(32)
self.edit.setIcon(QIcon(":/icons/configure.png")) self.edit.setIcon(QIcon(":/icons/configure.png"))
self.edit.setShortcut(_("Shift+Alt+e")) self.edit.setShortcut(_("Shift+Alt+e"))
@ -43,120 +47,105 @@ class ModelChooser(QHBoxLayout):
self.edit.setAutoDefault(False) self.edit.setAutoDefault(False)
self.addWidget(self.edit) self.addWidget(self.edit)
self.connect(self.edit, SIGNAL("clicked()"), self.onEdit) self.connect(self.edit, SIGNAL("clicked()"), self.onEdit)
self.connect(self.models, SIGNAL("activated(int)"), self.onChange) # layout
self.handleCards = False sizePolicy = QSizePolicy(
if cards: QSizePolicy.Policy(7),
self.handleCards = True QSizePolicy.Policy(0))
label = QLabel(_("<b>Cards</b>:")) self.models.setSizePolicy(sizePolicy)
self.addWidget(label) self.updateModels()
def setupTemplates(self):
self.cardShortcuts = []
if self.handleCards:
self.cards = QPushButton() self.cards = QPushButton()
self.cards.setAutoDefault(False) self.cards.setAutoDefault(False)
self.connect(self.cards, SIGNAL("clicked()"), self.onCardChange) self.connect(self.cards, SIGNAL("clicked()"), self.onCardChange)
self.addWidget(self.cards) self.addWidget(self.cards)
self.drawCardModels() self.updateTemplates()
addHook('guiReset', self.onModelEdited)
def deinit(self): def cleanup(self):
removeHook('guiReset', self.onModelEdited) removeHook('reset', self.onReset)
def onReset(self):
if not self._ignoreReset:
self.updateModels()
self.updateTemplates()
def show(self): def show(self):
for i in range(self.count()): self.widget.show()
self.itemAt(i).widget().show()
def hide(self): def hide(self):
for i in range(self.count()): self.widget.hide()
self.itemAt(i).widget().hide()
def onEdit(self): def onEdit(self):
ui.deckproperties.DeckProperties(self.parent, self.deck, import aqt.models
onFinish=self.onModelEdited) aqt.models.Models(self.mw, self.widget)
def onModelEdited(self): def onModelChange(self, idx):
# hack
from aqt import mw
self.deck = mw.deck
self.drawModels()
self.changed(self.deck.currentModel)
def onChange(self, idx):
model = self._models[idx] model = self._models[idx]
self.deck.currentModel = model self.deck.conf['currentModelId'] = model.id
self.changed(self.deck.currentModel) self.updateTemplates()
self.deck.setModified() self._ignoreReset = True
self.mw.reset()
self._ignoreReset = False
def changed(self, model): def updateModels(self):
self.deck.addModel(model)
if self.onChangeFunc:
self.onChangeFunc(model)
self.drawCardModels()
def drawModels(self):
self.models.clear() self.models.clear()
self._models = sorted(self.deck.models().values(), key=attrgetter("name")) self._models = sorted(self.deck.models().values(),
key=attrgetter("name"))
self.models.addItems(QStringList( self.models.addItems(QStringList(
[m.name for m in self._models])) [m.name for m in self._models]))
idx = self._models.index(self.deck.currentModel()) for c, m in enumerate(self._models):
self.models.setCurrentIndex(idx) if m.id == self.deck.conf['currentModelId']:
self.models.setCurrentIndex(c)
break
def drawCardModels(self): def updateTemplates(self):
if not self.handleCards: if not self.handleCards:
return return
# remove any shortcuts # remove any shortcuts
for s in self.shortcuts: for s in self.cardShortcuts:
s.setEnabled(False) s.setEnabled(False)
self.shortcuts = [] self.cardShortcuts = []
m = self.deck.currentModel m = self.deck.currentModel()
txt = ", ".join([c.name for c in m.cardModels if c.active]) ts = m.templates
if len(txt) > 30: active = [t['name'] for t in ts if t['actv']]
txt = txt[0:30] + "..." txt = ngettext("%d card", "%d cards", len(active)) % len(active)
self.cards.setText(txt) self.cards.setText(txt)
n = 1 n = 1
for c in m.cardModels: for t in ts:
s = QShortcut(QKeySequence("Ctrl+%d" % n), self.parent) s = QShortcut(QKeySequence("Ctrl+%d" % n), self.widget)
self.parent.connect(s, SIGNAL("activated()"), self.widget.connect(s, SIGNAL("activated()"),
lambda c=c: self.toggleCard(c)) lambda t=t: self.toggleTemplate(t))
self.shortcuts.append(s) self.cardShortcuts.append(s)
n += 1 n += 1
def onCardChange(self): def onCardChange(self):
m = QMenu(self.parent) m = QMenu(self.widget)
m.setTitle("menu") model = self.deck.currentModel()
model = self.deck.currentModel for template in model.templates:
for card in model.cardModels: action = QAction(self.widget)
action = QAction(self.parent)
action.setCheckable(True) action.setCheckable(True)
if card.active: if template['actv']:
action.setChecked(True) action.setChecked(True)
action.setText(card.name) action.setText(template['name'])
self.connect(action, SIGNAL("toggled(bool)"), self.connect(action, SIGNAL("activated()"),
lambda b, a=action, c=card: \ lambda t=template: \
self.cardChangeTriggered(b,a,c)) self.toggleTemplate(t))
m.addAction(action) m.addAction(action)
m.exec_(self.cards.mapToGlobal(QPoint(0,0))) m.exec_(self.cards.mapToGlobal(QPoint(0,0)))
def cardChangeTriggered(self, bool, action, card): def canDisableTemplate(self):
if bool: return len([t for t in self.deck.currentModel().templates
card.active = True if t['actv']]) > 1
elif self.canDisableModel():
card.active = False
self.drawCardModels()
def canDisableModel(self): def toggleTemplate(self, card):
active = 0 if not card['actv']:
model = self.deck.currentModel card['actv'] = True
for c in model.cardModels: elif self.canDisableTemplate():
if c.active: card['actv'] = False
active += 1 self.deck.currentModel().flush()
if active > 1: self.updateTemplates()
return True
return False
def toggleCard(self, card):
if not card.active:
card.active = True
elif self.canDisableModel():
card.active = False
self.drawCardModels()
class AddModel(QDialog): class AddModel(QDialog):

View file

@ -23,7 +23,6 @@ class Overview(object):
self.refresh() self.refresh()
def refresh(self): def refresh(self):
print "refreshing"
self._renderPage() self._renderPage()
# Handlers # Handlers

View file

@ -45,7 +45,14 @@
<number>4</number> <number>4</number>
</property> </property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QWidget" name="modelArea" native="true"/> <widget class="QWidget" name="modelArea" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>10</height>
</size>
</property>
</widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="Line" name="line"> <widget class="Line" name="line">