2019-02-05 04:59:03 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
2012-12-21 08:51:59 +01:00
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
2019-12-20 10:19:03 +01:00
|
|
|
import collections
|
2020-04-06 07:33:46 +02:00
|
|
|
import re
|
2012-12-21 08:51:59 +01:00
|
|
|
from operator import itemgetter
|
2020-05-04 07:21:03 +02:00
|
|
|
from typing import List, Optional
|
2019-12-20 10:19:03 +01:00
|
|
|
|
2018-12-15 03:45:38 +01:00
|
|
|
import aqt.clayout
|
2012-12-21 08:51:59 +01:00
|
|
|
from anki import stdmodels
|
2019-03-04 02:58:34 +01:00
|
|
|
from anki.lang import _, ngettext
|
2020-05-04 13:52:48 +02:00
|
|
|
from anki.models import NoteType
|
2020-04-25 12:13:46 +02:00
|
|
|
from anki.rsbackend import pb
|
2020-03-19 12:03:09 +01:00
|
|
|
from aqt import AnkiQt, gui_hooks
|
2019-12-20 10:19:03 +01:00
|
|
|
from aqt.qt import *
|
2019-12-23 01:34:10 +01:00
|
|
|
from aqt.utils import (
|
|
|
|
askUser,
|
|
|
|
getText,
|
|
|
|
maybeHideClose,
|
|
|
|
openHelp,
|
|
|
|
restoreGeom,
|
|
|
|
saveGeom,
|
|
|
|
showInfo,
|
|
|
|
)
|
2019-12-20 10:19:03 +01:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
class Models(QDialog):
|
2019-12-16 11:27:58 +01:00
|
|
|
def __init__(self, mw: AnkiQt, parent=None, fromMain=False):
|
2020-05-04 13:52:48 +02:00
|
|
|
self.mw = mw.weakref()
|
2019-12-16 12:17:32 +01:00
|
|
|
parent = parent or mw
|
2012-12-22 00:21:24 +01:00
|
|
|
self.fromMain = fromMain
|
2019-12-16 12:17:32 +01:00
|
|
|
QDialog.__init__(self, parent, Qt.Window)
|
2020-05-04 13:52:48 +02:00
|
|
|
self.col = mw.col.weakref()
|
2019-12-23 01:34:10 +01:00
|
|
|
assert self.col
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mm = self.col.models
|
|
|
|
self.mw.checkpoint(_("Note Types"))
|
|
|
|
self.form = aqt.forms.models.Ui_Dialog()
|
|
|
|
self.form.setupUi(self)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("notetypes"))
|
2020-04-25 12:13:46 +02:00
|
|
|
self.models: List[pb.NoteTypeNameIDUseCount] = []
|
2012-12-21 08:51:59 +01:00
|
|
|
self.setupModels()
|
|
|
|
restoreGeom(self, "models")
|
|
|
|
self.exec_()
|
|
|
|
|
|
|
|
# Models
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
def setupModels(self):
|
|
|
|
self.model = None
|
2019-12-23 01:34:10 +01:00
|
|
|
f = self.form
|
|
|
|
box = f.buttonBox
|
2012-12-21 08:51:59 +01:00
|
|
|
t = QDialogButtonBox.ActionRole
|
|
|
|
b = box.addButton(_("Add"), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onAdd)
|
2012-12-21 08:51:59 +01:00
|
|
|
b = box.addButton(_("Rename"), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onRename)
|
2012-12-21 08:51:59 +01:00
|
|
|
b = box.addButton(_("Delete"), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onDelete)
|
2012-12-22 00:21:24 +01:00
|
|
|
if self.fromMain:
|
|
|
|
b = box.addButton(_("Fields..."), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onFields)
|
2012-12-22 00:21:24 +01:00
|
|
|
b = box.addButton(_("Cards..."), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onCards)
|
2012-12-21 08:51:59 +01:00
|
|
|
b = box.addButton(_("Options..."), t)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.onAdvanced)
|
|
|
|
qconnect(f.modelsList.itemDoubleClicked, self.onRename)
|
2020-05-04 13:52:48 +02:00
|
|
|
|
|
|
|
def on_done(fut):
|
|
|
|
self.updateModelsList(fut.result())
|
|
|
|
|
|
|
|
self.mw.taskman.with_progress(self.col.models.all_use_counts, on_done, self)
|
2012-12-21 08:51:59 +01:00
|
|
|
f.modelsList.setCurrentRow(0)
|
|
|
|
maybeHideClose(box)
|
|
|
|
|
|
|
|
def onRename(self):
|
2020-05-04 13:52:48 +02:00
|
|
|
nt = self.current_notetype()
|
|
|
|
txt = getText(_("New name:"), default=nt["name"])
|
2013-01-08 02:38:23 +01:00
|
|
|
if txt[1] and txt[0]:
|
2020-05-04 13:52:48 +02:00
|
|
|
nt["name"] = txt[0]
|
|
|
|
self.saveAndRefresh(nt)
|
|
|
|
|
|
|
|
def saveAndRefresh(self, nt: NoteType) -> None:
|
|
|
|
def save():
|
|
|
|
self.mm.save(nt)
|
|
|
|
return self.col.models.all_use_counts()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2020-05-04 13:52:48 +02:00
|
|
|
def on_done(fut):
|
|
|
|
self.updateModelsList(fut.result())
|
|
|
|
|
|
|
|
self.mw.taskman.with_progress(save, on_done, self)
|
|
|
|
|
|
|
|
def updateModelsList(self, notetypes):
|
2012-12-21 08:51:59 +01:00
|
|
|
row = self.form.modelsList.currentRow()
|
|
|
|
if row == -1:
|
|
|
|
row = 0
|
|
|
|
self.form.modelsList.clear()
|
2020-05-04 13:52:48 +02:00
|
|
|
|
|
|
|
self.models = notetypes
|
2012-12-21 08:51:59 +01:00
|
|
|
for m in self.models:
|
2020-04-25 12:13:46 +02:00
|
|
|
mUse = m.use_count
|
2012-12-21 08:51:59 +01:00
|
|
|
mUse = ngettext("%d note", "%d notes", mUse) % mUse
|
2020-04-25 12:13:46 +02:00
|
|
|
item = QListWidgetItem("%s [%s]" % (m.name, mUse))
|
2012-12-21 08:51:59 +01:00
|
|
|
self.form.modelsList.addItem(item)
|
|
|
|
self.form.modelsList.setCurrentRow(row)
|
|
|
|
|
2020-05-04 13:52:48 +02:00
|
|
|
def current_notetype(self) -> NoteType:
|
|
|
|
row = self.form.modelsList.currentRow()
|
|
|
|
return self.mm.get(self.models[row].id)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
def onAdd(self):
|
|
|
|
m = AddModel(self.mw, self).get()
|
|
|
|
if m:
|
2019-12-23 01:34:10 +01:00
|
|
|
txt = getText(_("Name:"), default=m["name"])[0]
|
2012-12-21 08:51:59 +01:00
|
|
|
if txt:
|
2019-12-23 01:34:10 +01:00
|
|
|
m["name"] = txt
|
2020-05-04 13:52:48 +02:00
|
|
|
self.saveAndRefresh(m)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
def onDelete(self):
|
|
|
|
if len(self.models) < 2:
|
2019-12-23 01:34:10 +01:00
|
|
|
showInfo(_("Please add another note type first."), parent=self)
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2020-05-04 07:46:10 +02:00
|
|
|
idx = self.form.modelsList.currentRow()
|
|
|
|
if self.models[idx].use_count:
|
2012-12-21 08:51:59 +01:00
|
|
|
msg = _("Delete this note type and all its cards?")
|
|
|
|
else:
|
|
|
|
msg = _("Delete this unused note type?")
|
|
|
|
if not askUser(msg, parent=self):
|
|
|
|
return
|
2020-05-04 13:52:48 +02:00
|
|
|
|
|
|
|
self.col.modSchema(check=True)
|
|
|
|
|
|
|
|
nt = self.current_notetype()
|
|
|
|
|
|
|
|
def save():
|
|
|
|
self.mm.rem(nt)
|
|
|
|
return self.col.models.all_use_counts()
|
|
|
|
|
|
|
|
def on_done(fut):
|
|
|
|
self.updateModelsList(fut.result())
|
|
|
|
|
|
|
|
self.mw.taskman.with_progress(save, on_done, self)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
def onAdvanced(self):
|
2020-05-04 13:52:48 +02:00
|
|
|
nt = self.current_notetype()
|
2012-12-21 08:51:59 +01:00
|
|
|
d = QDialog(self)
|
|
|
|
frm = aqt.forms.modelopts.Ui_Dialog()
|
|
|
|
frm.setupUi(d)
|
2020-05-04 13:52:48 +02:00
|
|
|
frm.latexsvg.setChecked(nt.get("latexsvg", False))
|
|
|
|
frm.latexHeader.setText(nt["latexPre"])
|
|
|
|
frm.latexFooter.setText(nt["latexPost"])
|
|
|
|
d.setWindowTitle(_("Options for %s") % nt["name"])
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(frm.buttonBox.helpRequested, lambda: openHelp("latex"))
|
2014-06-18 20:47:45 +02:00
|
|
|
restoreGeom(d, "modelopts")
|
2020-03-19 12:03:09 +01:00
|
|
|
gui_hooks.models_advanced_will_show(d)
|
2012-12-21 08:51:59 +01:00
|
|
|
d.exec_()
|
2014-06-18 20:47:45 +02:00
|
|
|
saveGeom(d, "modelopts")
|
2020-05-04 13:52:48 +02:00
|
|
|
nt["latexsvg"] = frm.latexsvg.isChecked()
|
|
|
|
nt["latexPre"] = str(frm.latexHeader.toPlainText())
|
|
|
|
nt["latexPost"] = str(frm.latexFooter.toPlainText())
|
|
|
|
self.saveAndRefresh(nt)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2012-12-22 00:21:24 +01:00
|
|
|
def _tmpNote(self):
|
2020-05-04 13:52:48 +02:00
|
|
|
nt = self.current_notetype()
|
|
|
|
self.mm.setCurrent(nt)
|
2013-01-08 13:39:37 +01:00
|
|
|
n = self.col.newNote(forDeck=False)
|
2020-04-06 07:33:46 +02:00
|
|
|
field_names = list(n.keys())
|
|
|
|
for name in field_names:
|
2019-12-23 01:34:10 +01:00
|
|
|
n[name] = "(" + name + ")"
|
2020-04-06 07:33:46 +02:00
|
|
|
|
|
|
|
cloze_re = re.compile(r"{{(?:[^}:]*:)*cloze:(?:[^}:]*:)*([^}]+)}}")
|
2020-05-04 13:52:48 +02:00
|
|
|
q_template = nt["tmpls"][0]["qfmt"]
|
|
|
|
a_template = nt["tmpls"][0]["afmt"]
|
2020-04-06 07:33:46 +02:00
|
|
|
|
|
|
|
used_cloze_fields = []
|
|
|
|
used_cloze_fields.extend(cloze_re.findall(q_template))
|
|
|
|
used_cloze_fields.extend(cloze_re.findall(a_template))
|
|
|
|
for field in used_cloze_fields:
|
|
|
|
if field in field_names:
|
|
|
|
n[field] = f"{field}: " + _("This is a {{c1::sample}} cloze deletion.")
|
|
|
|
|
2012-12-22 00:21:24 +01:00
|
|
|
return n
|
|
|
|
|
|
|
|
def onFields(self):
|
|
|
|
from aqt.fields import FieldDialog
|
2019-12-23 01:34:10 +01:00
|
|
|
|
2020-05-04 13:52:48 +02:00
|
|
|
FieldDialog(self.mw, self.current_notetype(), parent=self)
|
2012-12-22 00:21:24 +01:00
|
|
|
|
|
|
|
def onCards(self):
|
|
|
|
from aqt.clayout import CardLayout
|
2019-12-23 01:34:10 +01:00
|
|
|
|
2012-12-22 00:21:24 +01:00
|
|
|
n = self._tmpNote()
|
|
|
|
CardLayout(self.mw, n, ord=0, parent=self, addMode=True)
|
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
# Cleanup
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
# need to flush model on change or reject
|
|
|
|
|
|
|
|
def reject(self):
|
|
|
|
self.mw.reset()
|
|
|
|
saveGeom(self, "models")
|
|
|
|
QDialog.reject(self)
|
|
|
|
|
|
|
|
|
2019-12-23 01:34:10 +01:00
|
|
|
class AddModel(QDialog):
|
2020-05-04 05:23:08 +02:00
|
|
|
def __init__(self, mw: AnkiQt, parent: Optional[QWidget] = None):
|
|
|
|
self.parent_ = parent or mw
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mw = mw
|
|
|
|
self.col = mw.col
|
2020-05-04 05:23:08 +02:00
|
|
|
QDialog.__init__(self, self.parent_, Qt.Window)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.model = None
|
|
|
|
self.dialog = aqt.forms.addmodel.Ui_Dialog()
|
|
|
|
self.dialog.setupUi(self)
|
|
|
|
# standard models
|
|
|
|
self.models = []
|
|
|
|
for (name, func) in stdmodels.models:
|
2020-05-04 05:23:08 +02:00
|
|
|
if isinstance(name, collections.Callable): # type: ignore
|
|
|
|
name = name() # type: ignore
|
2012-12-21 08:51:59 +01:00
|
|
|
item = QListWidgetItem(_("Add: %s") % name)
|
|
|
|
self.dialog.models.addItem(item)
|
|
|
|
self.models.append((True, func))
|
|
|
|
# add copies
|
2013-01-08 02:39:51 +01:00
|
|
|
for m in sorted(self.col.models.all(), key=itemgetter("name")):
|
2019-12-23 01:34:10 +01:00
|
|
|
item = QListWidgetItem(_("Clone: %s") % m["name"])
|
2012-12-21 08:51:59 +01:00
|
|
|
self.dialog.models.addItem(item)
|
2020-05-04 07:21:03 +02:00
|
|
|
self.models.append((False, m)) # type: ignore
|
2012-12-21 08:51:59 +01:00
|
|
|
self.dialog.models.setCurrentRow(0)
|
|
|
|
# the list widget will swallow the enter key
|
|
|
|
s = QShortcut(QKeySequence("Return"), self)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(s.activated, self.accept)
|
2012-12-21 08:51:59 +01:00
|
|
|
# help
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(self.dialog.buttonBox.helpRequested, self.onHelp)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
def get(self):
|
|
|
|
self.exec_()
|
|
|
|
return self.model
|
|
|
|
|
|
|
|
def reject(self):
|
|
|
|
QDialog.reject(self)
|
|
|
|
|
|
|
|
def accept(self):
|
|
|
|
(isStd, model) = self.models[self.dialog.models.currentRow()]
|
|
|
|
if isStd:
|
|
|
|
# create
|
|
|
|
self.model = model(self.col)
|
|
|
|
else:
|
|
|
|
# add copy to deck
|
|
|
|
self.model = self.mw.col.models.copy(model)
|
|
|
|
self.mw.col.models.setCurrent(self.model)
|
|
|
|
QDialog.accept(self)
|
|
|
|
|
|
|
|
def onHelp(self):
|
|
|
|
openHelp("notetypes")
|