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