Anki/qt/aqt/models.py
Arthur Milchior fc56d9182b Note types manager select the relevant note type
If the note type was renamed, the same note type is still selected.
If a note type is added, it's selected.

This allows both to see what was done (e.g. proof read the new name)
and to follow adding the note type by editing its fields/card types.
2026-01-01 11:20:41 +01:00

311 lines
10 KiB
Python

# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from collections.abc import Callable, Sequence
from concurrent.futures import Future
from operator import itemgetter
import aqt.clayout
from anki import stdmodels
from anki.collection import Collection, OpChangesWithId
from anki.lang import without_unicode_isolation
from anki.models import NotetypeDict, NotetypeId, NotetypeNameIdUseCount
from anki.notes import Note
from aqt import AnkiQt, gui_hooks
from aqt.operations import QueryOp
from aqt.operations.notetype import (
add_notetype_legacy,
remove_notetype,
update_notetype_legacy,
)
from aqt.qt import *
from aqt.schema_change_tracker import ChangeTracker
from aqt.utils import (
HelpPage,
askUser,
disable_help_button,
getText,
maybeHideClose,
openHelp,
restoreGeom,
saveGeom,
showInfo,
tr,
)
class Models(QDialog):
def __init__(
self,
mw: AnkiQt,
parent: QWidget | None = None,
fromMain: bool = False,
selected_notetype_id: NotetypeId | None = None,
):
self.mw = mw
parent = parent or mw
self.fromMain = fromMain
self.selected_notetype_id = selected_notetype_id
QDialog.__init__(self, parent or mw)
self.col = mw.col.weakref()
assert self.col
self.mm = self.col.models
self.form = aqt.forms.models.Ui_Dialog()
self.form.setupUi(self)
qconnect(
self.form.buttonBox.helpRequested,
lambda: openHelp(HelpPage.ADDING_A_NOTE_TYPE),
)
self.models: Sequence[NotetypeNameIdUseCount] = []
self.setupModels()
self.setWindowFlags(
self.windowFlags()
| Qt.WindowType.WindowMaximizeButtonHint
| Qt.WindowType.WindowMinimizeButtonHint
)
restoreGeom(self, "models")
self.show()
# Models
##########################################################################
def maybe_select_provided_notetype(
self, selected_notetype_id: NotetypeId | None = None, row: int = 0
) -> None:
"""Select the provided notetype ID, if any.
Otherwise the one at `self.selected_notetype_id`,
otherwise the `row`-th element."""
selected_notetype_id = selected_notetype_id or self.selected_notetype_id
if not selected_notetype_id:
self.form.modelsList.setCurrentRow(row)
return
for i, m in enumerate(self.models):
if m.id == selected_notetype_id:
self.form.modelsList.setCurrentRow(i)
break
def setupModels(self) -> None:
self.model = None
f = self.form
box = f.buttonBox
default_buttons = [
(tr.actions_add(), self.onAdd),
(tr.actions_rename(), self.onRename),
(tr.actions_delete(), self.onDelete),
]
if self.fromMain:
default_buttons.extend(
[
(tr.notetypes_fields(), self.onFields),
(tr.notetypes_cards(), self.onCards),
]
)
default_buttons.append((tr.notetypes_options(), self.onAdvanced))
for label, func in gui_hooks.models_did_init_buttons(default_buttons, self):
button = box.addButton(label, QDialogButtonBox.ButtonRole.ActionRole)
qconnect(button.clicked, func)
qconnect(f.modelsList.itemDoubleClicked, self.onRename)
def on_done(fut: Future) -> None:
self.updateModelsList(fut.result())
self.maybe_select_provided_notetype()
self.mw.taskman.with_progress(self.col.models.all_use_counts, on_done, self)
maybeHideClose(box)
def refresh_list(self, selected_notetype_id: NotetypeId | None = None) -> None:
QueryOp(
parent=self,
op=lambda col: col.models.all_use_counts(),
success=lambda notetypes: self.updateModelsList(
notetypes, selected_notetype_id
),
).run_in_background()
def onRename(self) -> None:
nt = self.current_notetype()
text, ok = getText(tr.actions_new_name(), default=nt["name"])
if ok and text.strip():
selected_notetype_id = nt["id"]
nt["name"] = text
update_notetype_legacy(parent=self, notetype=nt).success(
lambda _: self.refresh_list(selected_notetype_id)
).run_in_background()
def updateModelsList(
self,
notetypes: Sequence[NotetypeNameIdUseCount],
selected_notetype_id: NotetypeId | None = None,
) -> None:
row = self.form.modelsList.currentRow()
if row == -1:
row = 0
self.form.modelsList.clear()
self.models = notetypes
for m in self.models:
mUse = tr.browsing_note_count(count=m.use_count)
item = QListWidgetItem(f"{m.name} [{mUse}]")
self.form.modelsList.addItem(item)
self.maybe_select_provided_notetype(selected_notetype_id, row)
def current_notetype(self) -> NotetypeDict:
row = self.form.modelsList.currentRow()
return self.mm.get(NotetypeId(self.models[row].id))
def onAdd(self) -> None:
def on_success(notetype: NotetypeDict) -> None:
# if legacy add-ons already added the notetype, skip adding
nid = notetype["id"]
if nid:
self.refresh_list(nid)
return
# prompt for name
text, ok = getText(tr.actions_name(), default=notetype["name"], parent=self)
if not ok or not text.strip():
return
notetype["name"] = text
def refresh_list(op: OpChangesWithId) -> None:
self.refresh_list(NotetypeId(op.id))
add_notetype_legacy(parent=self, notetype=notetype).success(
refresh_list
).run_in_background()
AddModel(self.mw, on_success, self)
def onDelete(self) -> None:
if len(self.models) < 2:
showInfo(tr.notetypes_please_add_another_note_type_first(), parent=self)
return
idx = self.form.modelsList.currentRow()
if self.models[idx].use_count:
msg = tr.notetypes_delete_this_note_type_and_all()
else:
msg = tr.notetypes_delete_this_unused_note_type()
if not askUser(msg, parent=self):
return
tracker = ChangeTracker(self.mw)
if not tracker.mark_schema():
return
nt = self.current_notetype()
remove_notetype(parent=self, notetype_id=nt["id"]).success(
lambda _: self.refresh_list(None)
).run_in_background()
def onAdvanced(self) -> None:
nt = self.current_notetype()
d = QDialog(self)
disable_help_button(d)
frm = aqt.forms.modelopts.Ui_Dialog()
frm.setupUi(d)
frm.latexsvg.setChecked(nt.get("latexsvg", False))
frm.latexHeader.setText(nt["latexPre"])
frm.latexFooter.setText(nt["latexPost"])
d.setWindowTitle(
without_unicode_isolation(tr.actions_options_for(val=nt["name"]))
)
qconnect(frm.buttonBox.helpRequested, lambda: openHelp(HelpPage.LATEX))
restoreGeom(d, "modelopts")
gui_hooks.models_advanced_will_show(d)
d.exec()
saveGeom(d, "modelopts")
nt["latexsvg"] = frm.latexsvg.isChecked()
nt["latexPre"] = str(frm.latexHeader.toPlainText())
nt["latexPost"] = str(frm.latexFooter.toPlainText())
update_notetype_legacy(parent=self, notetype=nt).success(
lambda _: self.refresh_list(nt["id"])
).run_in_background()
def _tmpNote(self) -> Note:
nt = self.current_notetype()
return Note(self.col, nt)
def onFields(self) -> None:
from aqt.fields import FieldDialog
FieldDialog(self.mw, self.current_notetype(), parent=self)
def onCards(self) -> None:
from aqt.clayout import CardLayout
n = self._tmpNote()
CardLayout(self.mw, n, ord=0, parent=self, fill_empty=True)
# Cleanup
##########################################################################
def reject(self) -> None:
saveGeom(self, "models")
QDialog.reject(self)
class AddModel(QDialog):
model: NotetypeDict | None
def __init__(
self,
mw: AnkiQt,
on_success: Callable[[NotetypeDict], None],
parent: QWidget | None = None,
) -> None:
self.parent_ = parent or mw
self.mw = mw
self.col = mw.col
QDialog.__init__(self, self.parent_, Qt.WindowType.Window)
self.model = None
self.dialog = aqt.forms.addmodel.Ui_Dialog()
self.dialog.setupUi(self)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
disable_help_button(self)
# standard models
self.notetypes: list[NotetypeDict | Callable[[Collection], NotetypeDict]] = []
for name, func in stdmodels.get_stock_notetypes(self.col):
item = QListWidgetItem(tr.notetypes_add(val=name))
self.dialog.models.addItem(item)
self.notetypes.append(func)
# add copies
for m in sorted(self.col.models.all(), key=itemgetter("name")):
item = QListWidgetItem(tr.notetypes_clone(val=m["name"]))
self.dialog.models.addItem(item)
self.notetypes.append(m)
self.dialog.models.setCurrentRow(0)
# the list widget will swallow the enter key
s = QShortcut(QKeySequence("Return"), self)
qconnect(s.activated, self.accept)
# help
qconnect(self.dialog.buttonBox.helpRequested, self.onHelp)
self.on_success = on_success
self.show()
def reject(self) -> None:
QDialog.reject(self)
def accept(self) -> None:
model = self.notetypes[self.dialog.models.currentRow()]
if isinstance(model, dict):
# clone existing
self.model = self.mw.col.models.copy(model, add=False)
else:
self.model = model(self.col)
QDialog.accept(self)
# On mac, we need to allow time for the existing modal to close or
# Qt gets confused.
self.mw.progress.single_shot(100, lambda: self.on_success(self.model), True)
def onHelp(self) -> None:
openHelp(HelpPage.ADDING_A_NOTE_TYPE)