anki/qt/aqt/models.py
Damien Elmes 0a0d17ff98 Update Python deps
- Black's formatting has changed
- Pylint has introduced a new lint
2023-03-31 14:04:05 +10:00

289 lines
9.4 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 concurrent.futures import Future
from operator import itemgetter
from typing import Any, Optional, Sequence
import aqt.clayout
from anki import stdmodels
from anki.collection import Collection
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: Optional[QWidget] = None,
fromMain: bool = False,
selected_notetype_id: Optional[NotetypeId] = 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()
restoreGeom(self, "models")
self.show()
# Models
##########################################################################
def maybe_select_provided_notetype(self) -> None:
if not self.selected_notetype_id:
self.form.modelsList.setCurrentRow(0)
return
for i, m in enumerate(self.models):
if m.id == self.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, *ignored_args: Any) -> None:
QueryOp(
parent=self,
op=lambda col: col.models.all_use_counts(),
success=self.updateModelsList,
).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():
nt["name"] = text
update_notetype_legacy(parent=self, notetype=nt).success(
self.refresh_list
).run_in_background()
def updateModelsList(self, notetypes: Sequence[NotetypeNameIdUseCount]) -> 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.form.modelsList.setCurrentRow(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
if notetype["id"]:
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
add_notetype_legacy(parent=self, notetype=notetype).success(
self.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()
).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(
self.refresh_list
).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: Optional[NotetypeDict]
def __init__(
self,
mw: AnkiQt,
on_success: Callable[[NotetypeDict], None],
parent: Optional[QWidget] = 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[
Union[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)