mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
update the card layout screen
- changes are now committed in bulk when closing the dialog, and can be canceled - it's not necessary to save the note to the database to preview it - duplicate fields are now shown as duplicates in the top list - redraw preview more quickly - use + instead of _ when deduplicating names, as the latter is a glob character
This commit is contained in:
parent
12b8fe6147
commit
6680cdf1d3
11 changed files with 251 additions and 184 deletions
|
@ -33,7 +33,7 @@ message BackendInput {
|
||||||
DeckTreeIn deck_tree = 18;
|
DeckTreeIn deck_tree = 18;
|
||||||
SearchCardsIn search_cards = 19;
|
SearchCardsIn search_cards = 19;
|
||||||
SearchNotesIn search_notes = 20;
|
SearchNotesIn search_notes = 20;
|
||||||
// RenderCardIn render_card = 21;
|
RenderUncommittedCardIn render_uncommitted_card = 21;
|
||||||
int64 local_minutes_west = 22;
|
int64 local_minutes_west = 22;
|
||||||
string strip_av_tags = 23;
|
string strip_av_tags = 23;
|
||||||
ExtractAVTagsIn extract_av_tags = 24;
|
ExtractAVTagsIn extract_av_tags = 24;
|
||||||
|
@ -120,7 +120,7 @@ message BackendOutput {
|
||||||
DeckTreeNode deck_tree = 18;
|
DeckTreeNode deck_tree = 18;
|
||||||
SearchCardsOut search_cards = 19;
|
SearchCardsOut search_cards = 19;
|
||||||
SearchNotesOut search_notes = 20;
|
SearchNotesOut search_notes = 20;
|
||||||
// RenderCardOut render_card = 21;
|
RenderCardOut render_uncommitted_card = 21;
|
||||||
string add_media_file = 26;
|
string add_media_file = 26;
|
||||||
Empty sync_media = 27;
|
Empty sync_media = 27;
|
||||||
MediaCheckOut check_media = 28;
|
MediaCheckOut check_media = 28;
|
||||||
|
@ -269,6 +269,12 @@ message RenderExistingCardIn {
|
||||||
bool browser = 2;
|
bool browser = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RenderUncommittedCardIn {
|
||||||
|
Note note = 1;
|
||||||
|
uint32 card_ord = 2;
|
||||||
|
bytes template = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message RenderCardOut {
|
message RenderCardOut {
|
||||||
repeated RenderedTemplateNode question_nodes = 1;
|
repeated RenderedTemplateNode question_nodes = 1;
|
||||||
repeated RenderedTemplateNode answer_nodes = 2;
|
repeated RenderedTemplateNode answer_nodes = 2;
|
||||||
|
|
|
@ -14,7 +14,6 @@ from anki.models import NoteType, Template
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.rsbackend import BackendCard
|
from anki.rsbackend import BackendCard
|
||||||
from anki.sound import AVTag
|
from anki.sound import AVTag
|
||||||
from anki.utils import joinFields
|
|
||||||
|
|
||||||
# Cards
|
# Cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -133,6 +132,9 @@ class Card:
|
||||||
).render()
|
).render()
|
||||||
return self._render_output
|
return self._render_output
|
||||||
|
|
||||||
|
def set_render_output(self, output: anki.template.TemplateRenderOutput) -> None:
|
||||||
|
self._render_output = output
|
||||||
|
|
||||||
def note(self, reload: bool = False) -> Note:
|
def note(self, reload: bool = False) -> Note:
|
||||||
if not self._note or reload:
|
if not self._note or reload:
|
||||||
self._note = self.col.getNote(self.nid)
|
self._note = self.col.getNote(self.nid)
|
||||||
|
|
|
@ -408,24 +408,27 @@ class ModelManager:
|
||||||
t["name"] = name
|
t["name"] = name
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def addTemplate(self, m: NoteType, template: Template) -> None:
|
def addTemplate(self, m: NoteType, template: Template, save=True) -> None:
|
||||||
if m["id"]:
|
if m["id"]:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
|
|
||||||
m["tmpls"].append(template)
|
m["tmpls"].append(template)
|
||||||
|
|
||||||
if m["id"]:
|
if save and m["id"]:
|
||||||
self.save(m)
|
self.save(m)
|
||||||
|
|
||||||
def remTemplate(self, m: NoteType, template: Template) -> None:
|
def remTemplate(self, m: NoteType, template: Template, save=True) -> None:
|
||||||
assert len(m["tmpls"]) > 1
|
assert len(m["tmpls"]) > 1
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
|
|
||||||
m["tmpls"].remove(template)
|
m["tmpls"].remove(template)
|
||||||
|
|
||||||
self.save(m)
|
if save:
|
||||||
|
self.save(m)
|
||||||
|
|
||||||
def moveTemplate(self, m: NoteType, template: Template, idx: int) -> None:
|
def moveTemplate(
|
||||||
|
self, m: NoteType, template: Template, idx: int, save=True
|
||||||
|
) -> None:
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
|
|
||||||
oldidx = m["tmpls"].index(template)
|
oldidx = m["tmpls"].index(template)
|
||||||
|
@ -435,7 +438,8 @@ class ModelManager:
|
||||||
m["tmpls"].remove(template)
|
m["tmpls"].remove(template)
|
||||||
m["tmpls"].insert(idx, template)
|
m["tmpls"].insert(idx, template)
|
||||||
|
|
||||||
self.save(m)
|
if save:
|
||||||
|
self.save(m)
|
||||||
|
|
||||||
# Model changing
|
# Model changing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -312,6 +312,23 @@ class RustBackend:
|
||||||
|
|
||||||
return PartiallyRenderedCard(qnodes, anodes)
|
return PartiallyRenderedCard(qnodes, anodes)
|
||||||
|
|
||||||
|
def render_uncommitted_card(
|
||||||
|
self, note: BackendNote, card_ord: int, template: Dict
|
||||||
|
) -> PartiallyRenderedCard:
|
||||||
|
template_json = orjson.dumps(template)
|
||||||
|
out = self._run_command(
|
||||||
|
pb.BackendInput(
|
||||||
|
render_uncommitted_card=pb.RenderUncommittedCardIn(
|
||||||
|
note=note, template=template_json, card_ord=card_ord
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).render_uncommitted_card
|
||||||
|
|
||||||
|
qnodes = proto_replacement_list_to_native(out.question_nodes) # type: ignore
|
||||||
|
anodes = proto_replacement_list_to_native(out.answer_nodes) # type: ignore
|
||||||
|
|
||||||
|
return PartiallyRenderedCard(qnodes, anodes)
|
||||||
|
|
||||||
def local_minutes_west(self, stamp: int) -> int:
|
def local_minutes_west(self, stamp: int) -> int:
|
||||||
return self._run_command(
|
return self._run_command(
|
||||||
pb.BackendInput(local_minutes_west=stamp)
|
pb.BackendInput(local_minutes_west=stamp)
|
||||||
|
|
|
@ -56,15 +56,10 @@ class TemplateRenderContext:
|
||||||
return TemplateRenderContext(card.col, card, card.note(), browser)
|
return TemplateRenderContext(card.col, card, card.note(), browser)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_card_layout(cls, note: Note, card_ord: int) -> TemplateRenderContext:
|
def from_card_layout(
|
||||||
card = cls.synthesized_card(note.col, card_ord)
|
cls, note: Note, card: Card, template: Dict
|
||||||
return TemplateRenderContext(note.col, card, note)
|
) -> TemplateRenderContext:
|
||||||
|
return TemplateRenderContext(note.col, card, note, template=template)
|
||||||
@classmethod
|
|
||||||
def synthesized_card(cls, col: anki.storage._Collection, ord: int):
|
|
||||||
c = Card(col)
|
|
||||||
c.ord = ord
|
|
||||||
return c
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -72,7 +67,7 @@ class TemplateRenderContext:
|
||||||
card: Card,
|
card: Card,
|
||||||
note: Note,
|
note: Note,
|
||||||
browser: bool = False,
|
browser: bool = False,
|
||||||
template: Optional[Any] = None,
|
template: Optional[Dict] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._col = col.weakref()
|
self._col = col.weakref()
|
||||||
self._card = card
|
self._card = card
|
||||||
|
@ -146,7 +141,9 @@ class TemplateRenderContext:
|
||||||
def _partially_render(self) -> PartiallyRenderedCard:
|
def _partially_render(self) -> PartiallyRenderedCard:
|
||||||
if self._template:
|
if self._template:
|
||||||
# card layout screen
|
# card layout screen
|
||||||
raise Exception("nyi")
|
return self._col.backend.render_uncommitted_card(
|
||||||
|
self._note.to_backend_note(), self._card.ord, self._template
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# existing card (eg study mode)
|
# existing card (eg study mode)
|
||||||
return self._col.backend.render_existing_card(self._card.id, self._browser)
|
return self._col.backend.render_existing_card(self._card.id, self._browser)
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import collections
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
|
@ -13,6 +12,7 @@ from anki.consts import *
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.rsbackend import TemplateError
|
from anki.rsbackend import TemplateError
|
||||||
|
from anki.template import TemplateRenderContext
|
||||||
from anki.utils import isMac, isWin, joinFields
|
from anki.utils import isMac, isWin, joinFields
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -27,41 +27,34 @@ from aqt.utils import (
|
||||||
saveGeom,
|
saveGeom,
|
||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
|
tooltip,
|
||||||
)
|
)
|
||||||
from aqt.webview import AnkiWebView
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
# fixme: removeColons()
|
||||||
|
# fixme: previewing with empty fields
|
||||||
|
# fixme: deck name on new cards
|
||||||
|
# fixme: card count when removing
|
||||||
|
# fixme: i18n
|
||||||
|
# fixme: change tracking and tooltip in fields
|
||||||
|
|
||||||
|
|
||||||
class CardLayout(QDialog):
|
class CardLayout(QDialog):
|
||||||
card: Optional[Card]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self, mw: AnkiQt, note: Note, ord=0, parent: Optional[QWidget] = None,
|
||||||
mw: AnkiQt,
|
|
||||||
note: Note,
|
|
||||||
ord=0,
|
|
||||||
parent: Optional[QWidget] = None,
|
|
||||||
addMode=False,
|
|
||||||
):
|
):
|
||||||
QDialog.__init__(self, parent or mw, Qt.Window)
|
QDialog.__init__(self, parent or mw, Qt.Window)
|
||||||
mw.setupDialogGC(self)
|
mw.setupDialogGC(self)
|
||||||
self.mw = aqt.mw
|
self.mw = aqt.mw
|
||||||
self.note = note
|
self.note = note
|
||||||
self.ord = ord
|
self.ord = ord
|
||||||
self.col = self.mw.col
|
self.col = self.mw.col.weakref()
|
||||||
self.mm = self.mw.col.models
|
self.mm = self.mw.col.models
|
||||||
self.model = note.model()
|
self.model = note.model()
|
||||||
|
self.templates = self.model["tmpls"]
|
||||||
|
self.mm._remove_from_cache(self.model["id"])
|
||||||
self.mw.checkpoint(_("Card Types"))
|
self.mw.checkpoint(_("Card Types"))
|
||||||
self.addMode = addMode
|
self.changed = False
|
||||||
if addMode:
|
|
||||||
# save it to DB temporarily
|
|
||||||
self.emptyFields = []
|
|
||||||
for name, val in list(note.items()):
|
|
||||||
if val.strip():
|
|
||||||
continue
|
|
||||||
self.emptyFields.append(name)
|
|
||||||
note[name] = "(%s)" % name
|
|
||||||
note.flush()
|
|
||||||
self.removeColons()
|
|
||||||
self.setupTopArea()
|
self.setupTopArea()
|
||||||
self.setupMainArea()
|
self.setupMainArea()
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
|
@ -74,7 +67,7 @@ class CardLayout(QDialog):
|
||||||
v1.setContentsMargins(12, 12, 12, 12)
|
v1.setContentsMargins(12, 12, 12, 12)
|
||||||
self.setLayout(v1)
|
self.setLayout(v1)
|
||||||
gui_hooks.card_layout_will_show(self)
|
gui_hooks.card_layout_will_show(self)
|
||||||
self.redraw()
|
self.redraw_everything()
|
||||||
restoreGeom(self, "CardLayout")
|
restoreGeom(self, "CardLayout")
|
||||||
self.setWindowModality(Qt.ApplicationModal)
|
self.setWindowModality(Qt.ApplicationModal)
|
||||||
self.show()
|
self.show()
|
||||||
|
@ -82,32 +75,26 @@ class CardLayout(QDialog):
|
||||||
# as users tend to accidentally type into the template
|
# as users tend to accidentally type into the template
|
||||||
self.setFocus()
|
self.setFocus()
|
||||||
|
|
||||||
def redraw(self):
|
def redraw_everything(self):
|
||||||
did = None
|
self.ignore_change_signals = True
|
||||||
if hasattr(self.parent(), "deckChooser"):
|
|
||||||
did = self.parent().deckChooser.selectedId()
|
|
||||||
self.cards = self.col.previewCards(self.note, 2, did=did)
|
|
||||||
idx = self.ord
|
|
||||||
if idx >= len(self.cards):
|
|
||||||
self.ord = len(self.cards) - 1
|
|
||||||
|
|
||||||
self.redrawing = True
|
|
||||||
self.updateTopArea()
|
self.updateTopArea()
|
||||||
self.updateMainArea()
|
self.updateMainArea()
|
||||||
self.redrawing = False
|
self.ignore_change_signals = False
|
||||||
self.onCardSelected(self.ord)
|
self.update_current_ordinal_and_redraw(self.ord)
|
||||||
|
|
||||||
def setupShortcuts(self):
|
def update_current_ordinal_and_redraw(self, idx):
|
||||||
for i in range(1, 9):
|
if self.ignore_change_signals:
|
||||||
QShortcut(
|
return
|
||||||
QKeySequence("Ctrl+%d" % i),
|
self.ord = idx
|
||||||
self,
|
self.playedAudio = {}
|
||||||
activated=lambda i=i: self.selectCard(i),
|
self.fill_fields_from_template()
|
||||||
) # type: ignore
|
self.renderPreview()
|
||||||
|
|
||||||
def selectCard(self, n):
|
def _isCloze(self):
|
||||||
self.ord = n - 1
|
return self.model["type"] == MODEL_CLOZE
|
||||||
self.redraw()
|
|
||||||
|
# Top area
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
def setupTopArea(self):
|
def setupTopArea(self):
|
||||||
self.topArea = QWidget()
|
self.topArea = QWidget()
|
||||||
|
@ -115,7 +102,10 @@ class CardLayout(QDialog):
|
||||||
self.topAreaForm.setupUi(self.topArea)
|
self.topAreaForm.setupUi(self.topArea)
|
||||||
self.topAreaForm.templateOptions.setText(_("Options") + " " + downArrow())
|
self.topAreaForm.templateOptions.setText(_("Options") + " " + downArrow())
|
||||||
qconnect(self.topAreaForm.templateOptions.clicked, self.onMore)
|
qconnect(self.topAreaForm.templateOptions.clicked, self.onMore)
|
||||||
qconnect(self.topAreaForm.templatesBox.currentIndexChanged, self.onCardSelected)
|
qconnect(
|
||||||
|
self.topAreaForm.templatesBox.currentIndexChanged,
|
||||||
|
self.update_current_ordinal_and_redraw,
|
||||||
|
)
|
||||||
|
|
||||||
def updateTopArea(self):
|
def updateTopArea(self):
|
||||||
cnt = self.mw.col.models.useCount(self.model)
|
cnt = self.mw.col.models.useCount(self.model)
|
||||||
|
@ -130,17 +120,19 @@ class CardLayout(QDialog):
|
||||||
self.updateCardNames()
|
self.updateCardNames()
|
||||||
|
|
||||||
def updateCardNames(self):
|
def updateCardNames(self):
|
||||||
self.redrawing = True
|
self.ignore_change_signals = True
|
||||||
combo = self.topAreaForm.templatesBox
|
combo = self.topAreaForm.templatesBox
|
||||||
combo.clear()
|
combo.clear()
|
||||||
combo.addItems(self._summarizedName(t) for t in self.model["tmpls"])
|
combo.addItems(
|
||||||
|
self._summarizedName(idx, tmpl) for (idx, tmpl) in enumerate(self.templates)
|
||||||
|
)
|
||||||
combo.setCurrentIndex(self.ord)
|
combo.setCurrentIndex(self.ord)
|
||||||
combo.setEnabled(not self._isCloze())
|
combo.setEnabled(not self._isCloze())
|
||||||
self.redrawing = False
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def _summarizedName(self, tmpl):
|
def _summarizedName(self, idx: int, tmpl: Dict):
|
||||||
return "{}: {}: {} -> {}".format(
|
return "{}: {}: {} -> {}".format(
|
||||||
tmpl["ord"] + 1,
|
idx + 1,
|
||||||
tmpl["name"],
|
tmpl["name"],
|
||||||
self._fieldsOnTemplate(tmpl["qfmt"]),
|
self._fieldsOnTemplate(tmpl["qfmt"]),
|
||||||
self._fieldsOnTemplate(tmpl["afmt"]),
|
self._fieldsOnTemplate(tmpl["afmt"]),
|
||||||
|
@ -148,8 +140,8 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
def _fieldsOnTemplate(self, fmt):
|
def _fieldsOnTemplate(self, fmt):
|
||||||
matches = re.findall("{{[^#/}]+?}}", fmt)
|
matches = re.findall("{{[^#/}]+?}}", fmt)
|
||||||
charsAllowed = 30
|
chars_allowed = 30
|
||||||
result: Dict[str, bool] = collections.OrderedDict()
|
field_names: List[str] = []
|
||||||
for m in matches:
|
for m in matches:
|
||||||
# strip off mustache
|
# strip off mustache
|
||||||
m = re.sub(r"[{}]", "", m)
|
m = re.sub(r"[{}]", "", m)
|
||||||
|
@ -159,19 +151,30 @@ class CardLayout(QDialog):
|
||||||
if m == "FrontSide":
|
if m == "FrontSide":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if m not in result:
|
field_names.append(m)
|
||||||
result[m] = True
|
chars_allowed -= len(m)
|
||||||
charsAllowed -= len(m)
|
if chars_allowed <= 0:
|
||||||
if charsAllowed <= 0:
|
break
|
||||||
break
|
|
||||||
|
|
||||||
s = "+".join(result.keys())
|
s = "+".join(field_names)
|
||||||
if charsAllowed <= 0:
|
if chars_allowed <= 0:
|
||||||
s += "+..."
|
s += "+..."
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def _isCloze(self):
|
def setupShortcuts(self):
|
||||||
return self.model["type"] == MODEL_CLOZE
|
for i in range(1, 9):
|
||||||
|
QShortcut(
|
||||||
|
QKeySequence("Ctrl+%d" % i),
|
||||||
|
self,
|
||||||
|
activated=lambda i=i: self.selectCard(i),
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
def selectCard(self, n):
|
||||||
|
self.ord = n - 1
|
||||||
|
self.redraw_everything()
|
||||||
|
|
||||||
|
# Main area
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
def setupMainArea(self):
|
def setupMainArea(self):
|
||||||
w = self.mainArea = QWidget()
|
w = self.mainArea = QWidget()
|
||||||
|
@ -192,9 +195,9 @@ class CardLayout(QDialog):
|
||||||
tform.tlayout2.setContentsMargins(0, 11, 0, 0)
|
tform.tlayout2.setContentsMargins(0, 11, 0, 0)
|
||||||
tform.tlayout3.setContentsMargins(0, 11, 0, 0)
|
tform.tlayout3.setContentsMargins(0, 11, 0, 0)
|
||||||
tform.groupBox_3.setTitle(_("Styling (shared between cards)"))
|
tform.groupBox_3.setTitle(_("Styling (shared between cards)"))
|
||||||
qconnect(tform.front.textChanged, self.saveCard)
|
qconnect(tform.front.textChanged, self.write_edits_to_template_and_redraw)
|
||||||
qconnect(tform.css.textChanged, self.saveCard)
|
qconnect(tform.css.textChanged, self.write_edits_to_template_and_redraw)
|
||||||
qconnect(tform.back.textChanged, self.saveCard)
|
qconnect(tform.back.textChanged, self.write_edits_to_template_and_redraw)
|
||||||
l.addWidget(left, 5)
|
l.addWidget(left, 5)
|
||||||
# preview area
|
# preview area
|
||||||
right = QWidget()
|
right = QWidget()
|
||||||
|
@ -235,7 +238,7 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
def _on_bridge_cmd(self, cmd: str) -> Any:
|
def _on_bridge_cmd(self, cmd: str) -> Any:
|
||||||
if cmd.startswith("play:"):
|
if cmd.startswith("play:"):
|
||||||
play_clicked_audio(cmd, self.card)
|
play_clicked_audio(cmd, self.rendered_card)
|
||||||
|
|
||||||
def updateMainArea(self):
|
def updateMainArea(self):
|
||||||
if self._isCloze():
|
if self._isCloze():
|
||||||
|
@ -243,23 +246,14 @@ class CardLayout(QDialog):
|
||||||
for g in self.pform.groupBox, self.pform.groupBox_2:
|
for g in self.pform.groupBox, self.pform.groupBox_2:
|
||||||
g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1))
|
g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1))
|
||||||
|
|
||||||
def onRemove(self):
|
def ephemeral_card_for_rendering(self) -> Card:
|
||||||
if len(self.model["tmpls"]) < 2:
|
card = Card(self.col)
|
||||||
return showInfo(_("At least one card type is required."))
|
card.ord = self.ord
|
||||||
idx = self.ord
|
output = TemplateRenderContext.from_card_layout(
|
||||||
msg = _("Delete the '%(a)s' card type, and its %(b)s?") % dict(
|
self.note, card, template=self.current_template()
|
||||||
a=self.model["tmpls"][idx]["name"], b=_("cards")
|
).render()
|
||||||
)
|
card.set_render_output(output)
|
||||||
if not askUser(msg):
|
return card
|
||||||
return
|
|
||||||
self.mm.remTemplate(self.model, self.cards[idx].template())
|
|
||||||
self.redraw()
|
|
||||||
|
|
||||||
def removeColons(self):
|
|
||||||
# colons in field names conflict with the template language
|
|
||||||
for fld in self.model["flds"]:
|
|
||||||
if ":" in fld["name"]:
|
|
||||||
self.mm.renameField(self.model, fld, fld["name"])
|
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -291,21 +285,15 @@ class CardLayout(QDialog):
|
||||||
l.addWidget(close)
|
l.addWidget(close)
|
||||||
qconnect(close.clicked, self.reject)
|
qconnect(close.clicked, self.reject)
|
||||||
|
|
||||||
# Cards
|
# Reading/writing question/answer/css
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def onCardSelected(self, idx):
|
def current_template(self) -> Dict:
|
||||||
if self.redrawing:
|
return self.templates[self.ord]
|
||||||
return
|
|
||||||
self.card = self.cards[idx]
|
|
||||||
self.ord = idx
|
|
||||||
self.playedAudio = {}
|
|
||||||
self.readCard()
|
|
||||||
self.renderPreview()
|
|
||||||
|
|
||||||
def readCard(self):
|
def fill_fields_from_template(self):
|
||||||
t = self.card.template()
|
t = self.current_template()
|
||||||
self.redrawing = True
|
self.ignore_change_signals = True
|
||||||
self.tform.front.setPlainText(t["qfmt"])
|
self.tform.front.setPlainText(t["qfmt"])
|
||||||
self.tform.css.setPlainText(self.model["css"])
|
self.tform.css.setPlainText(self.model["css"])
|
||||||
self.tform.back.setPlainText(t["afmt"])
|
self.tform.back.setPlainText(t["afmt"])
|
||||||
|
@ -321,17 +309,16 @@ class CardLayout(QDialog):
|
||||||
self.tform.front.setTabStopDistance(tab_width)
|
self.tform.front.setTabStopDistance(tab_width)
|
||||||
self.tform.css.setTabStopDistance(tab_width)
|
self.tform.css.setTabStopDistance(tab_width)
|
||||||
self.tform.back.setTabStopDistance(tab_width)
|
self.tform.back.setTabStopDistance(tab_width)
|
||||||
self.redrawing = False
|
self.ignore_change_signals = False
|
||||||
|
|
||||||
def saveCard(self):
|
def write_edits_to_template_and_redraw(self):
|
||||||
if self.redrawing:
|
if self.ignore_change_signals:
|
||||||
return
|
return
|
||||||
text = self.tform.front.toPlainText()
|
self.changed = True
|
||||||
self.card.template()["qfmt"] = text
|
t = self.current_template()
|
||||||
text = self.tform.css.toPlainText()
|
t["qfmt"] = self.tform.front.toPlainText()
|
||||||
self.card.model()["css"] = text
|
t["afmt"] = self.tform.back.toPlainText()
|
||||||
text = self.tform.back.toPlainText()
|
self.model["css"] = self.tform.css.toPlainText()
|
||||||
self.card.template()["afmt"] = text
|
|
||||||
self.renderPreview()
|
self.renderPreview()
|
||||||
|
|
||||||
# Preview
|
# Preview
|
||||||
|
@ -342,7 +329,7 @@ class CardLayout(QDialog):
|
||||||
def renderPreview(self):
|
def renderPreview(self):
|
||||||
# schedule a preview when timing stops
|
# schedule a preview when timing stops
|
||||||
self.cancelPreviewTimer()
|
self.cancelPreviewTimer()
|
||||||
self._previewTimer = self.mw.progress.timer(500, self._renderPreview, False)
|
self._previewTimer = self.mw.progress.timer(200, self._renderPreview, False)
|
||||||
|
|
||||||
def cancelPreviewTimer(self):
|
def cancelPreviewTimer(self):
|
||||||
if self._previewTimer:
|
if self._previewTimer:
|
||||||
|
@ -352,12 +339,13 @@ class CardLayout(QDialog):
|
||||||
def _renderPreview(self) -> None:
|
def _renderPreview(self) -> None:
|
||||||
self.cancelPreviewTimer()
|
self.cancelPreviewTimer()
|
||||||
|
|
||||||
c = self.card
|
c = self.rendered_card = self.ephemeral_card_for_rendering()
|
||||||
|
|
||||||
ti = self.maybeTextInput
|
ti = self.maybeTextInput
|
||||||
|
|
||||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||||
|
|
||||||
q = ti(self.mw.prepare_card_text_for_display(c.q(reload=True)))
|
q = ti(self.mw.prepare_card_text_for_display(c.q()))
|
||||||
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
||||||
|
|
||||||
a = ti(self.mw.prepare_card_text_for_display(c.a()), type="a")
|
a = ti(self.mw.prepare_card_text_for_display(c.a()), type="a")
|
||||||
|
@ -398,21 +386,43 @@ class CardLayout(QDialog):
|
||||||
# Card operations
|
# Card operations
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def onRename(self):
|
def onRemove(self):
|
||||||
name = getOnlyText(_("New name:"), default=self.card.template()["name"])
|
if len(self.templates) < 2:
|
||||||
if not name:
|
return showInfo(_("At least one card type is required."))
|
||||||
|
template = self.current_template()
|
||||||
|
msg = _("Delete the '%(a)s' card type, and its %(b)s?") % dict(
|
||||||
|
a=template["name"], b=_("cards")
|
||||||
|
)
|
||||||
|
if not askUser(msg):
|
||||||
return
|
return
|
||||||
if name in [
|
|
||||||
c.template()["name"] for c in self.cards if c.template()["ord"] != self.ord
|
self.changed = True
|
||||||
]:
|
self.mm.remTemplate(self.model, template, save=False)
|
||||||
return showWarning(_("That name is already used."))
|
|
||||||
self.card.template()["name"] = name
|
# ensure current ordinal is within bounds
|
||||||
self.redraw()
|
idx = self.ord
|
||||||
|
if idx >= len(self.templates):
|
||||||
|
self.ord = len(self.templates) - 1
|
||||||
|
|
||||||
|
self.redraw_everything()
|
||||||
|
|
||||||
|
def onRename(self):
|
||||||
|
template = self.current_template()
|
||||||
|
name = getOnlyText(_("New name:"), default=template["name"])
|
||||||
|
if not name.strip():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.changed = True
|
||||||
|
template["name"] = name
|
||||||
|
self.redraw_everything()
|
||||||
|
|
||||||
def onReorder(self):
|
def onReorder(self):
|
||||||
n = len(self.cards)
|
n = len(self.templates)
|
||||||
cur = self.card.template()["ord"] + 1 # type: ignore
|
template = self.current_template()
|
||||||
pos = getOnlyText(_("Enter new card position (1...%s):") % n, default=str(cur))
|
current_pos = self.templates.index(template) + 1
|
||||||
|
pos = getOnlyText(
|
||||||
|
_("Enter new card position (1...%s):") % n, default=str(current_pos)
|
||||||
|
)
|
||||||
if not pos:
|
if not pos:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -421,18 +431,19 @@ class CardLayout(QDialog):
|
||||||
return
|
return
|
||||||
if pos < 1 or pos > n:
|
if pos < 1 or pos > n:
|
||||||
return
|
return
|
||||||
if pos == cur:
|
if pos == current_pos:
|
||||||
return
|
return
|
||||||
pos -= 1
|
new_idx = pos - 1
|
||||||
self.mm.moveTemplate(self.model, self.card.template(), pos)
|
self.changed = True
|
||||||
self.ord = pos
|
self.mm.moveTemplate(self.model, template, new_idx, save=False)
|
||||||
self.redraw()
|
self.ord = new_idx
|
||||||
|
self.redraw_everything()
|
||||||
|
|
||||||
def _newCardName(self):
|
def _newCardName(self):
|
||||||
n = len(self.cards) + 1
|
n = len(self.templates) + 1
|
||||||
while 1:
|
while 1:
|
||||||
name = _("Card %d") % n
|
name = _("Card %d") % n
|
||||||
if name not in [c.template()["name"] for c in self.cards]:
|
if name not in [t["name"] for t in self.templates]:
|
||||||
break
|
break
|
||||||
n += 1
|
n += 1
|
||||||
return name
|
return name
|
||||||
|
@ -449,19 +460,20 @@ class CardLayout(QDialog):
|
||||||
)
|
)
|
||||||
if not askUser(txt):
|
if not askUser(txt):
|
||||||
return
|
return
|
||||||
|
self.changed = True
|
||||||
name = self._newCardName()
|
name = self._newCardName()
|
||||||
t = self.mm.newTemplate(name)
|
t = self.mm.newTemplate(name)
|
||||||
old = self.card.template()
|
old = self.current_template()
|
||||||
t["qfmt"] = old["qfmt"]
|
t["qfmt"] = old["qfmt"]
|
||||||
t["afmt"] = old["afmt"]
|
t["afmt"] = old["afmt"]
|
||||||
self.mm.addTemplate(self.model, t)
|
self.mm.addTemplate(self.model, t, save=False)
|
||||||
self.ord = len(self.cards)
|
self.ord = len(self.templates) - 1
|
||||||
self.redraw()
|
self.redraw_everything()
|
||||||
|
|
||||||
def onFlip(self):
|
def onFlip(self):
|
||||||
old = self.card.template()
|
old = self.current_template()
|
||||||
self._flipQA(old, old)
|
self._flipQA(old, old)
|
||||||
self.redraw()
|
self.redraw_everything()
|
||||||
|
|
||||||
def _flipQA(self, src, dst):
|
def _flipQA(self, src, dst):
|
||||||
m = re.match("(?s)(.+)<hr id=answer>(.+)", src["afmt"])
|
m = re.match("(?s)(.+)<hr id=answer>(.+)", src["afmt"])
|
||||||
|
@ -474,6 +486,7 @@ adjust the template manually to switch the question and answer."""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
self.changed = True
|
||||||
dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"]
|
dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"]
|
||||||
dst["qfmt"] = m.group(2).strip()
|
dst["qfmt"] = m.group(2).strip()
|
||||||
return True
|
return True
|
||||||
|
@ -496,7 +509,7 @@ adjust the template manually to switch the question and answer."""
|
||||||
|
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
|
|
||||||
t = self.card.template()
|
t = self.current_template()
|
||||||
if t["did"]:
|
if t["did"]:
|
||||||
s = _(" (on)")
|
s = _(" (on)")
|
||||||
else:
|
else:
|
||||||
|
@ -513,7 +526,7 @@ adjust the template manually to switch the question and answer."""
|
||||||
d = QDialog()
|
d = QDialog()
|
||||||
f = aqt.forms.browserdisp.Ui_Dialog()
|
f = aqt.forms.browserdisp.Ui_Dialog()
|
||||||
f.setupUi(d)
|
f.setupUi(d)
|
||||||
t = self.card.template()
|
t = self.current_template()
|
||||||
f.qfmt.setText(t.get("bqfmt", ""))
|
f.qfmt.setText(t.get("bqfmt", ""))
|
||||||
f.afmt.setText(t.get("bafmt", ""))
|
f.afmt.setText(t.get("bafmt", ""))
|
||||||
if t.get("bfont"):
|
if t.get("bfont"):
|
||||||
|
@ -524,7 +537,8 @@ adjust the template manually to switch the question and answer."""
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
def onBrowserDisplayOk(self, f):
|
def onBrowserDisplayOk(self, f):
|
||||||
t = self.card.template()
|
t = self.current_template()
|
||||||
|
self.changed = True
|
||||||
t["bqfmt"] = f.qfmt.text().strip()
|
t["bqfmt"] = f.qfmt.text().strip()
|
||||||
t["bafmt"] = f.afmt.text().strip()
|
t["bafmt"] = f.afmt.text().strip()
|
||||||
if f.overrideFont.isChecked():
|
if f.overrideFont.isChecked():
|
||||||
|
@ -538,7 +552,7 @@ adjust the template manually to switch the question and answer."""
|
||||||
def onTargetDeck(self):
|
def onTargetDeck(self):
|
||||||
from aqt.tagedit import TagEdit
|
from aqt.tagedit import TagEdit
|
||||||
|
|
||||||
t = self.card.template()
|
t = self.current_template()
|
||||||
d = QDialog(self)
|
d = QDialog(self)
|
||||||
d.setWindowTitle("Anki")
|
d.setWindowTitle("Anki")
|
||||||
d.setMinimumWidth(400)
|
d.setMinimumWidth(400)
|
||||||
|
@ -548,7 +562,7 @@ adjust the template manually to switch the question and answer."""
|
||||||
"""\
|
"""\
|
||||||
Enter deck to place new %s cards in, or leave blank:"""
|
Enter deck to place new %s cards in, or leave blank:"""
|
||||||
)
|
)
|
||||||
% self.card.template()["name"]
|
% self.current_template()["name"]
|
||||||
)
|
)
|
||||||
lab.setWordWrap(True)
|
lab.setWordWrap(True)
|
||||||
l.addWidget(lab)
|
l.addWidget(lab)
|
||||||
|
@ -563,6 +577,7 @@ Enter deck to place new %s cards in, or leave blank:"""
|
||||||
l.addWidget(bb)
|
l.addWidget(bb)
|
||||||
d.setLayout(l)
|
d.setLayout(l)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
self.changed = True
|
||||||
if not te.text().strip():
|
if not te.text().strip():
|
||||||
t["did"] = None
|
t["did"] = None
|
||||||
else:
|
else:
|
||||||
|
@ -606,42 +621,45 @@ Enter deck to place new %s cards in, or leave blank:"""
|
||||||
field,
|
field,
|
||||||
)
|
)
|
||||||
widg.setPlainText(t)
|
widg.setPlainText(t)
|
||||||
self.saveCard()
|
self.changed = True
|
||||||
|
self.write_edits_to_template_and_redraw()
|
||||||
|
|
||||||
# Closing & Help
|
# Closing & Help
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def accept(self) -> None:
|
def accept(self) -> None:
|
||||||
try:
|
def save():
|
||||||
self.mm.save(self.model)
|
self.mm.save(self.model)
|
||||||
except TemplateError as e:
|
|
||||||
# fixme: i18n
|
|
||||||
showWarning("Unable to save changes: " + str(e))
|
|
||||||
return
|
|
||||||
|
|
||||||
self.mw.reset()
|
def on_done(fut):
|
||||||
self.cleanup()
|
try:
|
||||||
return QDialog.accept(self)
|
fut.result()
|
||||||
|
except TemplateError as e:
|
||||||
|
showWarning("Unable to save changes: " + str(e))
|
||||||
|
return
|
||||||
|
self.mw.reset()
|
||||||
|
tooltip("Changes saved.", parent=self.mw)
|
||||||
|
self.cleanup()
|
||||||
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
self.mw.taskman.with_progress(save, on_done)
|
||||||
|
|
||||||
def reject(self) -> None:
|
def reject(self) -> None:
|
||||||
# discard mutations - in the future we should load a fresh
|
if self.changed:
|
||||||
# copy at the start instead
|
if not askUser("Discard changes?"):
|
||||||
self.mm._remove_from_cache(self.model["id"])
|
return
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
return QDialog.reject(self)
|
return QDialog.reject(self)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
self.cancelPreviewTimer()
|
self.cancelPreviewTimer()
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
# fixme
|
|
||||||
if self.addMode:
|
|
||||||
# remove the filler fields we added
|
|
||||||
for name in self.emptyFields:
|
|
||||||
self.note[name] = ""
|
|
||||||
self.mw.col.db.execute("delete from notes where id = ?", self.note.id)
|
|
||||||
saveGeom(self, "CardLayout")
|
saveGeom(self, "CardLayout")
|
||||||
self.pform.frontWeb = None
|
self.pform.frontWeb = None
|
||||||
self.pform.backWeb = None
|
self.pform.backWeb = None
|
||||||
|
self.model = None
|
||||||
|
self.rendered_card = None
|
||||||
|
self.mw = None
|
||||||
|
|
||||||
def onHelp(self):
|
def onHelp(self):
|
||||||
openHelp("templates")
|
openHelp("templates")
|
||||||
|
|
|
@ -353,7 +353,7 @@ class Editor:
|
||||||
else:
|
else:
|
||||||
ord = 0
|
ord = 0
|
||||||
CardLayout(
|
CardLayout(
|
||||||
self.mw, self.note, ord=ord, parent=self.parentWindow, addMode=self.addMode
|
self.mw, self.note, ord=ord, parent=self.parentWindow,
|
||||||
)
|
)
|
||||||
if isWin:
|
if isWin:
|
||||||
self.parentWindow.activateWindow()
|
self.parentWindow.activateWindow()
|
||||||
|
|
|
@ -18,6 +18,7 @@ class FieldDialog(QDialog):
|
||||||
self.col = self.mw.col
|
self.col = self.mw.col
|
||||||
self.mm = self.mw.col.models
|
self.mm = self.mw.col.models
|
||||||
self.model = nt
|
self.model = nt
|
||||||
|
self.mm._remove_from_cache(self.model["id"])
|
||||||
self.mw.checkpoint(_("Fields"))
|
self.mw.checkpoint(_("Fields"))
|
||||||
self.form = aqt.forms.fields.Ui_Dialog()
|
self.form = aqt.forms.fields.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
|
@ -163,7 +164,6 @@ class FieldDialog(QDialog):
|
||||||
fld["rtl"] = f.rtl.isChecked()
|
fld["rtl"] = f.rtl.isChecked()
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
self.mm._remove_from_cache(self.model["id"])
|
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
|
|
|
@ -188,7 +188,7 @@ class Models(QDialog):
|
||||||
from aqt.clayout import CardLayout
|
from aqt.clayout import CardLayout
|
||||||
|
|
||||||
n = self._tmpNote()
|
n = self._tmpNote()
|
||||||
CardLayout(self.mw, n, ord=0, parent=self, addMode=True)
|
CardLayout(self.mw, n, ord=0, parent=self)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -21,7 +21,10 @@ use crate::{
|
||||||
media::sync::MediaSyncProgress,
|
media::sync::MediaSyncProgress,
|
||||||
media::MediaManager,
|
media::MediaManager,
|
||||||
notes::{Note, NoteID},
|
notes::{Note, NoteID},
|
||||||
notetype::{all_stock_notetypes, NoteType, NoteTypeID, NoteTypeSchema11, RenderCardOutput},
|
notetype::{
|
||||||
|
all_stock_notetypes, CardTemplateSchema11, NoteType, NoteTypeID, NoteTypeSchema11,
|
||||||
|
RenderCardOutput,
|
||||||
|
},
|
||||||
sched::cutoff::local_minutes_west_for_stamp,
|
sched::cutoff::local_minutes_west_for_stamp,
|
||||||
sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span},
|
sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span},
|
||||||
search::SortMode,
|
search::SortMode,
|
||||||
|
@ -216,9 +219,6 @@ impl Backend {
|
||||||
Ok(match ival {
|
Ok(match ival {
|
||||||
Value::SchedTimingToday(_) => OValue::SchedTimingToday(self.sched_timing_today()?),
|
Value::SchedTimingToday(_) => OValue::SchedTimingToday(self.sched_timing_today()?),
|
||||||
Value::DeckTree(input) => OValue::DeckTree(self.deck_tree(input)?),
|
Value::DeckTree(input) => OValue::DeckTree(self.deck_tree(input)?),
|
||||||
Value::RenderExistingCard(input) => {
|
|
||||||
OValue::RenderExistingCard(self.render_existing_card(input)?)
|
|
||||||
}
|
|
||||||
Value::LocalMinutesWest(stamp) => {
|
Value::LocalMinutesWest(stamp) => {
|
||||||
OValue::LocalMinutesWest(local_minutes_west_for_stamp(stamp))
|
OValue::LocalMinutesWest(local_minutes_west_for_stamp(stamp))
|
||||||
}
|
}
|
||||||
|
@ -375,6 +375,12 @@ impl Backend {
|
||||||
self.set_preferences(prefs)?;
|
self.set_preferences(prefs)?;
|
||||||
pb::Empty {}
|
pb::Empty {}
|
||||||
}),
|
}),
|
||||||
|
Value::RenderExistingCard(input) => {
|
||||||
|
OValue::RenderExistingCard(self.render_existing_card(input)?)
|
||||||
|
}
|
||||||
|
Value::RenderUncommittedCard(input) => {
|
||||||
|
OValue::RenderUncommittedCard(self.render_uncommitted_card(input)?)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,6 +462,23 @@ impl Backend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_uncommitted_card(
|
||||||
|
&self,
|
||||||
|
input: pb::RenderUncommittedCardIn,
|
||||||
|
) -> Result<pb::RenderCardOut> {
|
||||||
|
let schema11: CardTemplateSchema11 = serde_json::from_slice(&input.template)?;
|
||||||
|
let template = schema11.into();
|
||||||
|
let note = input
|
||||||
|
.note
|
||||||
|
.ok_or_else(|| AnkiError::invalid_input("missing note"))?
|
||||||
|
.into();
|
||||||
|
let ord = input.card_ord as u16;
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.render_uncommitted_card(¬e, &template, ord)
|
||||||
|
.map(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> pb::ExtractAvTagsOut {
|
fn extract_av_tags(&self, input: pb::ExtractAvTagsIn) -> pb::ExtractAvTagsOut {
|
||||||
let (text, tags) = extract_av_tags(&input.text, input.question_side);
|
let (text, tags) = extract_av_tags(&input.text, input.question_side);
|
||||||
let pt_tags = tags
|
let pt_tags = tags
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl NoteType {
|
||||||
names.insert(name);
|
names.insert(name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
t.name.push('_');
|
t.name.push('+');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
names.clear();
|
names.clear();
|
||||||
|
@ -95,7 +95,7 @@ impl NoteType {
|
||||||
names.insert(name);
|
names.insert(name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
t.name.push('_');
|
t.name.push('+');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue