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 json
|
2012-12-21 08:51:59 +01:00
|
|
|
import re
|
2021-02-02 14:30:53 +01:00
|
|
|
from concurrent.futures import Future
|
2021-02-01 14:28:21 +01:00
|
|
|
from typing import Any, Dict, List, Match, Optional
|
2013-10-20 03:02:29 +02:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
import aqt
|
2019-12-20 10:19:03 +01:00
|
|
|
from anki.consts import *
|
2021-01-31 06:55:08 +01:00
|
|
|
from anki.errors import TemplateError
|
2020-11-18 16:03:04 +01:00
|
|
|
from anki.lang import without_unicode_isolation
|
2020-05-05 03:10:24 +02:00
|
|
|
from anki.notes import Note
|
|
|
|
from aqt import AnkiQt, gui_hooks
|
2021-02-01 13:08:56 +01:00
|
|
|
from aqt.forms.browserdisp import Ui_Dialog
|
2019-12-20 10:19:03 +01:00
|
|
|
from aqt.qt import *
|
2020-05-15 05:59:44 +02:00
|
|
|
from aqt.schema_change_tracker import ChangeTracker
|
2020-01-25 07:01:16 +01:00
|
|
|
from aqt.sound import av_player, play_clicked_audio
|
2020-01-23 06:08:10 +01:00
|
|
|
from aqt.theme import theme_manager
|
2019-12-23 01:34:10 +01:00
|
|
|
from aqt.utils import (
|
2020-05-14 12:58:45 +02:00
|
|
|
TR,
|
2021-01-25 14:45:47 +01:00
|
|
|
HelpPage,
|
2019-12-23 01:34:10 +01:00
|
|
|
askUser,
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button,
|
2019-12-23 01:34:10 +01:00
|
|
|
downArrow,
|
|
|
|
getOnlyText,
|
|
|
|
openHelp,
|
|
|
|
restoreGeom,
|
2020-05-31 01:31:34 +02:00
|
|
|
restoreSplitter,
|
2019-12-23 01:34:10 +01:00
|
|
|
saveGeom,
|
2020-05-31 01:31:34 +02:00
|
|
|
saveSplitter,
|
2020-05-14 12:58:45 +02:00
|
|
|
shortcut,
|
2019-12-23 01:34:10 +01:00
|
|
|
showInfo,
|
|
|
|
showWarning,
|
2020-05-13 09:24:49 +02:00
|
|
|
tooltip,
|
2020-05-14 12:58:45 +02:00
|
|
|
tr,
|
2019-12-23 01:34:10 +01:00
|
|
|
)
|
2019-12-20 10:19:03 +01:00
|
|
|
from aqt.webview import AnkiWebView
|
|
|
|
|
2020-01-24 02:06:11 +01:00
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
class CardLayout(QDialog):
|
2020-05-05 03:10:24 +02:00
|
|
|
def __init__(
|
2020-05-14 12:58:45 +02:00
|
|
|
self,
|
|
|
|
mw: AnkiQt,
|
|
|
|
note: Note,
|
2021-02-02 14:30:53 +01:00
|
|
|
ord: int = 0,
|
2020-05-14 12:58:45 +02:00
|
|
|
parent: Optional[QWidget] = None,
|
|
|
|
fill_empty: bool = False,
|
2021-02-02 14:30:53 +01:00
|
|
|
) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
QDialog.__init__(self, parent or mw, Qt.Window)
|
2016-07-04 05:22:35 +02:00
|
|
|
mw.setupDialogGC(self)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mw = aqt.mw
|
|
|
|
self.note = note
|
|
|
|
self.ord = ord
|
2020-05-13 09:24:49 +02:00
|
|
|
self.col = self.mw.col.weakref()
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mm = self.mw.col.models
|
|
|
|
self.model = note.model()
|
2020-05-13 09:24:49 +02:00
|
|
|
self.templates = self.model["tmpls"]
|
2020-07-30 14:39:02 +02:00
|
|
|
self.fill_empty_action_toggled = fill_empty
|
2020-07-31 06:47:17 +02:00
|
|
|
self.night_mode_is_enabled = self.mw.pm.night_mode()
|
2020-08-03 05:30:34 +02:00
|
|
|
self.mobile_emulation_enabled = False
|
2020-05-15 06:26:00 +02:00
|
|
|
self.have_autoplayed = False
|
2020-05-13 09:24:49 +02:00
|
|
|
self.mm._remove_from_cache(self.model["id"])
|
2020-11-17 08:42:43 +01:00
|
|
|
self.mw.checkpoint(tr(TR.CARD_TEMPLATES_CARD_TYPES))
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker = ChangeTracker(self.mw)
|
2017-08-08 11:45:31 +02:00
|
|
|
self.setupTopArea()
|
2017-08-08 07:31:36 +02:00
|
|
|
self.setupMainArea()
|
2012-12-21 08:51:59 +01:00
|
|
|
self.setupButtons()
|
2017-08-12 09:29:47 +02:00
|
|
|
self.setupShortcuts()
|
2020-11-17 08:42:43 +01:00
|
|
|
self.setWindowTitle(
|
2020-11-18 16:03:04 +01:00
|
|
|
without_unicode_isolation(
|
|
|
|
tr(TR.CARD_TEMPLATES_CARD_TYPES_FOR, val=self.model["name"])
|
|
|
|
)
|
2020-11-17 08:42:43 +01:00
|
|
|
)
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(self)
|
2012-12-21 08:51:59 +01:00
|
|
|
v1 = QVBoxLayout()
|
2017-08-08 11:45:31 +02:00
|
|
|
v1.addWidget(self.topArea)
|
2017-08-08 07:31:36 +02:00
|
|
|
v1.addWidget(self.mainArea)
|
2012-12-21 08:51:59 +01:00
|
|
|
v1.addLayout(self.buttons)
|
2019-12-23 01:34:10 +01:00
|
|
|
v1.setContentsMargins(12, 12, 12, 12)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.setLayout(v1)
|
2020-02-28 13:34:54 +01:00
|
|
|
gui_hooks.card_layout_will_show(self)
|
2020-05-13 09:24:49 +02:00
|
|
|
self.redraw_everything()
|
2012-12-21 08:51:59 +01:00
|
|
|
restoreGeom(self, "CardLayout")
|
2020-05-31 01:31:34 +02:00
|
|
|
restoreSplitter(self.mainArea, "CardLayoutMainArea")
|
2016-07-07 04:32:27 +02:00
|
|
|
self.setWindowModality(Qt.ApplicationModal)
|
|
|
|
self.show()
|
2017-08-08 11:45:31 +02:00
|
|
|
# take the focus away from the first input area when starting up,
|
|
|
|
# as users tend to accidentally type into the template
|
|
|
|
self.setFocus()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def redraw_everything(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ignore_change_signals = True
|
2017-08-08 11:45:31 +02:00
|
|
|
self.updateTopArea()
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ignore_change_signals = False
|
|
|
|
self.update_current_ordinal_and_redraw(self.ord)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2020-06-25 16:27:52 +02:00
|
|
|
def update_current_ordinal_and_redraw(self, idx: int) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
if self.ignore_change_signals:
|
|
|
|
return
|
|
|
|
self.ord = idx
|
2020-05-15 06:26:00 +02:00
|
|
|
self.have_autoplayed = False
|
2020-05-13 09:24:49 +02:00
|
|
|
self.fill_fields_from_template()
|
|
|
|
self.renderPreview()
|
2017-08-12 09:29:47 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def _isCloze(self) -> bool:
|
2020-05-13 09:24:49 +02:00
|
|
|
return self.model["type"] == MODEL_CLOZE
|
|
|
|
|
|
|
|
# Top area
|
|
|
|
##########################################################################
|
2017-08-12 09:29:47 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setupTopArea(self) -> None:
|
2017-08-08 11:45:31 +02:00
|
|
|
self.topArea = QWidget()
|
2020-05-31 00:54:25 +02:00
|
|
|
self.topArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
|
2017-08-08 11:45:31 +02:00
|
|
|
self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
|
|
|
|
self.topAreaForm.setupUi(self.topArea)
|
2020-11-17 08:42:43 +01:00
|
|
|
self.topAreaForm.templateOptions.setText(
|
|
|
|
tr(TR.ACTIONS_OPTIONS) + " " + downArrow()
|
|
|
|
)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(self.topAreaForm.templateOptions.clicked, self.onMore)
|
2020-05-13 09:24:49 +02:00
|
|
|
qconnect(
|
|
|
|
self.topAreaForm.templatesBox.currentIndexChanged,
|
|
|
|
self.update_current_ordinal_and_redraw,
|
|
|
|
)
|
2020-05-14 09:26:40 +02:00
|
|
|
self.topAreaForm.card_type_label.setText(tr(TR.CARD_TEMPLATES_CARD_TYPE))
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def updateTopArea(self) -> None:
|
2017-08-09 03:11:39 +02:00
|
|
|
self.updateCardNames()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def updateCardNames(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ignore_change_signals = True
|
2017-08-08 11:45:31 +02:00
|
|
|
combo = self.topAreaForm.templatesBox
|
|
|
|
combo.clear()
|
2020-05-13 09:24:49 +02:00
|
|
|
combo.addItems(
|
|
|
|
self._summarizedName(idx, tmpl) for (idx, tmpl) in enumerate(self.templates)
|
|
|
|
)
|
2017-08-08 11:45:31 +02:00
|
|
|
combo.setCurrentIndex(self.ord)
|
2017-08-11 13:04:17 +02:00
|
|
|
combo.setEnabled(not self._isCloze())
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ignore_change_signals = False
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def _summarizedName(self, idx: int, tmpl: Dict) -> str:
|
2020-01-21 23:42:27 +01:00
|
|
|
return "{}: {}: {} -> {}".format(
|
2020-05-13 09:24:49 +02:00
|
|
|
idx + 1,
|
2019-12-23 01:34:10 +01:00
|
|
|
tmpl["name"],
|
|
|
|
self._fieldsOnTemplate(tmpl["qfmt"]),
|
|
|
|
self._fieldsOnTemplate(tmpl["afmt"]),
|
|
|
|
)
|
2017-08-09 03:11:39 +02:00
|
|
|
|
2020-06-25 16:27:52 +02:00
|
|
|
def _fieldsOnTemplate(self, fmt: str) -> str:
|
2017-08-09 03:11:39 +02:00
|
|
|
matches = re.findall("{{[^#/}]+?}}", fmt)
|
2020-05-13 09:24:49 +02:00
|
|
|
chars_allowed = 30
|
|
|
|
field_names: List[str] = []
|
2017-08-09 03:11:39 +02:00
|
|
|
for m in matches:
|
|
|
|
# strip off mustache
|
|
|
|
m = re.sub(r"[{}]", "", m)
|
|
|
|
# strip off modifiers
|
|
|
|
m = m.split(":")[-1]
|
|
|
|
# don't show 'FrontSide'
|
|
|
|
if m == "FrontSide":
|
|
|
|
continue
|
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
field_names.append(m)
|
|
|
|
chars_allowed -= len(m)
|
|
|
|
if chars_allowed <= 0:
|
|
|
|
break
|
2017-08-09 03:11:39 +02:00
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
s = "+".join(field_names)
|
|
|
|
if chars_allowed <= 0:
|
2020-05-05 03:10:24 +02:00
|
|
|
s += "+..."
|
|
|
|
return s
|
2017-08-09 03:11:39 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setupShortcuts(self) -> None:
|
2020-05-14 09:26:40 +02:00
|
|
|
self.tform.front_button.setToolTip(shortcut("Ctrl+1"))
|
|
|
|
self.tform.back_button.setToolTip(shortcut("Ctrl+2"))
|
|
|
|
self.tform.style_button.setToolTip(shortcut("Ctrl+3"))
|
2020-05-18 10:08:57 +02:00
|
|
|
QShortcut( # type: ignore
|
2020-08-31 05:29:28 +02:00
|
|
|
QKeySequence("Ctrl+1"),
|
|
|
|
self,
|
|
|
|
activated=self.tform.front_button.click,
|
2020-05-18 10:08:57 +02:00
|
|
|
)
|
|
|
|
QShortcut( # type: ignore
|
2020-08-31 05:29:28 +02:00
|
|
|
QKeySequence("Ctrl+2"),
|
|
|
|
self,
|
|
|
|
activated=self.tform.back_button.click,
|
2020-05-18 10:08:57 +02:00
|
|
|
)
|
|
|
|
QShortcut( # type: ignore
|
2020-08-31 05:29:28 +02:00
|
|
|
QKeySequence("Ctrl+3"),
|
|
|
|
self,
|
|
|
|
activated=self.tform.style_button.click,
|
2020-05-18 10:08:57 +02:00
|
|
|
)
|
2020-05-13 09:24:49 +02:00
|
|
|
|
2020-05-14 12:58:45 +02:00
|
|
|
# Main area setup
|
2020-05-13 09:24:49 +02:00
|
|
|
##########################################################################
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setupMainArea(self) -> None:
|
2020-05-31 00:54:25 +02:00
|
|
|
split = self.mainArea = QSplitter()
|
|
|
|
split.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
|
|
split.setOrientation(Qt.Horizontal)
|
2012-12-21 08:51:59 +01:00
|
|
|
left = QWidget()
|
2017-08-08 07:31:36 +02:00
|
|
|
tform = self.tform = aqt.forms.template.Ui_Form()
|
2012-12-21 08:51:59 +01:00
|
|
|
tform.setupUi(left)
|
2020-05-31 00:54:25 +02:00
|
|
|
split.addWidget(left)
|
|
|
|
split.setCollapsible(0, False)
|
2020-05-14 09:26:40 +02:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
right = QWidget()
|
2020-05-14 12:58:45 +02:00
|
|
|
self.pform = aqt.forms.preview.Ui_Form()
|
2020-05-04 05:23:08 +02:00
|
|
|
pform = self.pform
|
2012-12-21 08:51:59 +01:00
|
|
|
pform.setupUi(right)
|
2020-05-14 09:26:40 +02:00
|
|
|
pform.preview_front.setText(tr(TR.CARD_TEMPLATES_FRONT_PREVIEW))
|
|
|
|
pform.preview_back.setText(tr(TR.CARD_TEMPLATES_BACK_PREVIEW))
|
|
|
|
pform.preview_box.setTitle(tr(TR.CARD_TEMPLATES_PREVIEW_BOX))
|
2020-05-14 07:24:29 +02:00
|
|
|
|
2020-05-14 12:58:45 +02:00
|
|
|
self.setup_edit_area()
|
|
|
|
self.setup_preview()
|
2020-05-31 00:54:25 +02:00
|
|
|
split.addWidget(right)
|
|
|
|
split.setCollapsible(1, False)
|
2017-08-08 08:02:55 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setup_edit_area(self) -> None:
|
2020-05-14 12:58:45 +02:00
|
|
|
tform = self.tform
|
|
|
|
|
|
|
|
tform.front_button.setText(tr(TR.CARD_TEMPLATES_FRONT_TEMPLATE))
|
|
|
|
tform.back_button.setText(tr(TR.CARD_TEMPLATES_BACK_TEMPLATE))
|
|
|
|
tform.style_button.setText(tr(TR.CARD_TEMPLATES_TEMPLATE_STYLING))
|
|
|
|
tform.groupBox.setTitle(tr(TR.CARD_TEMPLATES_TEMPLATE_BOX))
|
|
|
|
|
|
|
|
cnt = self.mw.col.models.useCount(self.model)
|
|
|
|
self.tform.changes_affect_label.setText(
|
|
|
|
self.col.tr(TR.CARD_TEMPLATES_CHANGES_WILL_AFFECT_NOTES, count=cnt)
|
|
|
|
)
|
|
|
|
|
|
|
|
qconnect(tform.edit_area.textChanged, self.write_edits_to_template_and_redraw)
|
2020-05-19 23:32:47 +02:00
|
|
|
qconnect(tform.front_button.clicked, self.on_editor_toggled)
|
|
|
|
qconnect(tform.back_button.clicked, self.on_editor_toggled)
|
|
|
|
qconnect(tform.style_button.clicked, self.on_editor_toggled)
|
2020-05-14 12:58:45 +02:00
|
|
|
|
2020-05-14 09:26:40 +02:00
|
|
|
self.current_editor_index = 0
|
|
|
|
self.tform.edit_area.setAcceptRichText(False)
|
2020-05-22 01:58:20 +02:00
|
|
|
self.tform.edit_area.setFont(QFont("Courier"))
|
2020-05-14 09:26:40 +02:00
|
|
|
if qtminor < 10:
|
|
|
|
self.tform.edit_area.setTabStopWidth(30)
|
|
|
|
else:
|
|
|
|
tab_width = self.fontMetrics().width(" " * 4)
|
|
|
|
self.tform.edit_area.setTabStopDistance(tab_width)
|
|
|
|
|
2020-05-14 12:58:45 +02:00
|
|
|
widg = tform.search_edit
|
|
|
|
widg.setPlaceholderText("Search")
|
|
|
|
qconnect(widg.textChanged, self.on_search_changed)
|
|
|
|
qconnect(widg.returnPressed, self.on_search_next)
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setup_cloze_number_box(self) -> None:
|
2020-11-17 12:47:47 +01:00
|
|
|
names = (tr(TR.CARD_TEMPLATES_CLOZE, val=n) for n in self.cloze_numbers)
|
2020-05-14 07:24:29 +02:00
|
|
|
self.pform.cloze_number_combo.addItems(names)
|
|
|
|
try:
|
|
|
|
idx = self.cloze_numbers.index(self.ord + 1)
|
|
|
|
self.pform.cloze_number_combo.setCurrentIndex(idx)
|
|
|
|
except ValueError:
|
|
|
|
# invalid cloze
|
|
|
|
pass
|
|
|
|
qconnect(
|
|
|
|
self.pform.cloze_number_combo.currentIndexChanged, self.on_change_cloze
|
|
|
|
)
|
|
|
|
|
|
|
|
def on_change_cloze(self, idx: int) -> None:
|
|
|
|
self.ord = self.cloze_numbers[idx] - 1
|
2020-05-15 06:26:00 +02:00
|
|
|
self.have_autoplayed = False
|
2020-05-14 07:24:29 +02:00
|
|
|
self._renderPreview()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def on_editor_toggled(self) -> None:
|
2020-05-14 09:26:40 +02:00
|
|
|
if self.tform.front_button.isChecked():
|
|
|
|
self.current_editor_index = 0
|
2020-05-14 07:24:29 +02:00
|
|
|
self.pform.preview_front.setChecked(True)
|
2020-05-19 23:32:47 +02:00
|
|
|
self.on_preview_toggled()
|
2020-05-14 10:01:15 +02:00
|
|
|
self.add_field_button.setHidden(False)
|
2020-05-14 09:26:40 +02:00
|
|
|
elif self.tform.back_button.isChecked():
|
|
|
|
self.current_editor_index = 1
|
2020-05-14 07:24:29 +02:00
|
|
|
self.pform.preview_back.setChecked(True)
|
2020-05-19 23:32:47 +02:00
|
|
|
self.on_preview_toggled()
|
2020-05-14 10:01:15 +02:00
|
|
|
self.add_field_button.setHidden(False)
|
2020-05-14 09:26:40 +02:00
|
|
|
else:
|
|
|
|
self.current_editor_index = 2
|
2020-05-14 10:01:15 +02:00
|
|
|
self.add_field_button.setHidden(True)
|
2020-05-14 09:26:40 +02:00
|
|
|
|
|
|
|
self.fill_fields_from_template()
|
2020-05-14 07:24:29 +02:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def on_search_changed(self, text: str) -> None:
|
2020-05-14 09:26:40 +02:00
|
|
|
editor = self.tform.edit_area
|
2020-05-14 07:24:29 +02:00
|
|
|
if not editor.find(text):
|
|
|
|
# try again from top
|
|
|
|
cursor = editor.textCursor()
|
|
|
|
cursor.movePosition(QTextCursor.Start)
|
|
|
|
editor.setTextCursor(cursor)
|
2020-05-22 02:43:28 +02:00
|
|
|
if not editor.find(text):
|
|
|
|
tooltip("No matches found.")
|
2020-05-14 07:24:29 +02:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def on_search_next(self) -> None:
|
2020-05-14 09:26:40 +02:00
|
|
|
text = self.tform.search_edit.text()
|
|
|
|
self.on_search_changed(text)
|
2020-05-14 07:24:29 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setup_preview(self) -> None:
|
2017-08-08 08:02:55 +02:00
|
|
|
pform = self.pform
|
2020-05-14 12:58:45 +02:00
|
|
|
self.preview_web = AnkiWebView(title="card layout")
|
|
|
|
pform.verticalLayout.addWidget(self.preview_web)
|
2020-05-14 07:24:29 +02:00
|
|
|
pform.verticalLayout.setStretch(1, 99)
|
|
|
|
pform.preview_front.isChecked()
|
2020-05-19 23:32:47 +02:00
|
|
|
qconnect(pform.preview_front.clicked, self.on_preview_toggled)
|
|
|
|
qconnect(pform.preview_back.clicked, self.on_preview_toggled)
|
2020-07-30 17:04:50 +02:00
|
|
|
pform.preview_settings.setText(
|
|
|
|
tr(TR.CARD_TEMPLATES_PREVIEW_SETTINGS) + " " + downArrow()
|
|
|
|
)
|
|
|
|
qconnect(pform.preview_settings.clicked, self.on_preview_settings)
|
2020-07-30 14:39:02 +02:00
|
|
|
|
2019-12-23 01:34:10 +01:00
|
|
|
jsinc = [
|
2020-12-28 14:18:07 +01:00
|
|
|
"js/vendor/jquery.min.js",
|
2020-12-31 16:47:20 +01:00
|
|
|
"js/vendor/css_browser_selector.min.js",
|
2020-11-15 11:22:28 +01:00
|
|
|
"js/mathjax.js",
|
2020-11-03 15:49:07 +01:00
|
|
|
"js/vendor/mathjax/tex-chtml.js",
|
2020-11-01 05:26:58 +01:00
|
|
|
"js/reviewer.js",
|
2019-12-23 01:34:10 +01:00
|
|
|
]
|
2020-05-14 12:58:45 +02:00
|
|
|
self.preview_web.stdHtml(
|
2020-08-31 05:29:28 +02:00
|
|
|
self.mw.reviewer.revHtml(),
|
2020-11-01 05:26:58 +01:00
|
|
|
css=["css/reviewer.css"],
|
2020-08-31 05:29:28 +02:00
|
|
|
js=jsinc,
|
|
|
|
context=self,
|
2019-12-23 01:34:10 +01:00
|
|
|
)
|
2020-05-14 12:58:45 +02:00
|
|
|
self.preview_web.set_bridge_command(self._on_bridge_cmd, self)
|
|
|
|
|
|
|
|
if self._isCloze():
|
2020-05-24 01:05:48 +02:00
|
|
|
nums = list(self.note.cloze_numbers_in_fields())
|
2020-05-14 12:58:45 +02:00
|
|
|
if self.ord + 1 not in nums:
|
|
|
|
# current card is empty
|
|
|
|
nums.append(self.ord + 1)
|
|
|
|
self.cloze_numbers = sorted(nums)
|
|
|
|
self.setup_cloze_number_box()
|
|
|
|
else:
|
|
|
|
self.cloze_numbers = []
|
|
|
|
self.pform.cloze_number_combo.setHidden(True)
|
2020-05-14 07:24:29 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def on_fill_empty_action_toggled(self) -> None:
|
2020-07-30 14:39:02 +02:00
|
|
|
self.fill_empty_action_toggled = not self.fill_empty_action_toggled
|
|
|
|
self.on_preview_toggled()
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def on_night_mode_action_toggled(self) -> None:
|
2020-07-31 06:47:17 +02:00
|
|
|
self.night_mode_is_enabled = not self.night_mode_is_enabled
|
2020-07-30 14:39:02 +02:00
|
|
|
self.on_preview_toggled()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def on_mobile_class_action_toggled(self) -> None:
|
2020-08-03 05:30:34 +02:00
|
|
|
self.mobile_emulation_enabled = not self.mobile_emulation_enabled
|
2020-07-30 14:39:02 +02:00
|
|
|
self.on_preview_toggled()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def on_preview_settings(self) -> None:
|
2020-07-30 14:39:02 +02:00
|
|
|
m = QMenu(self)
|
|
|
|
|
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_FILL_EMPTY))
|
|
|
|
a.setCheckable(True)
|
|
|
|
a.setChecked(self.fill_empty_action_toggled)
|
|
|
|
qconnect(a.triggered, self.on_fill_empty_action_toggled)
|
|
|
|
if not self.note_has_empty_field():
|
|
|
|
a.setVisible(False)
|
|
|
|
|
2020-07-31 06:35:18 +02:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_NIGHT_MODE))
|
2020-07-30 14:39:02 +02:00
|
|
|
a.setCheckable(True)
|
2020-07-31 06:47:17 +02:00
|
|
|
a.setChecked(self.night_mode_is_enabled)
|
2020-07-30 14:39:02 +02:00
|
|
|
qconnect(a.triggered, self.on_night_mode_action_toggled)
|
|
|
|
|
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_ADD_MOBILE_CLASS))
|
|
|
|
a.setCheckable(True)
|
2020-08-03 05:30:34 +02:00
|
|
|
a.setChecked(self.mobile_emulation_enabled)
|
2020-07-30 14:39:02 +02:00
|
|
|
qconnect(a.toggled, self.on_mobile_class_action_toggled)
|
|
|
|
|
2020-07-30 17:04:50 +02:00
|
|
|
m.exec_(self.pform.preview_settings.mapToGlobal(QPoint(0, 0)))
|
2020-07-30 14:39:02 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def on_preview_toggled(self) -> None:
|
2020-05-15 06:26:00 +02:00
|
|
|
self.have_autoplayed = False
|
2020-05-14 07:24:29 +02:00
|
|
|
self._renderPreview()
|
2020-01-25 07:01:16 +01:00
|
|
|
|
|
|
|
def _on_bridge_cmd(self, cmd: str) -> Any:
|
|
|
|
if cmd.startswith("play:"):
|
2020-05-13 09:24:49 +02:00
|
|
|
play_clicked_audio(cmd, self.rendered_card)
|
2017-08-08 07:31:36 +02:00
|
|
|
|
2020-05-14 12:58:45 +02:00
|
|
|
def note_has_empty_field(self) -> bool:
|
|
|
|
for field in self.note.fields:
|
|
|
|
if not field.strip():
|
|
|
|
# ignores HTML, but this should suffice
|
|
|
|
return True
|
|
|
|
return False
|
2019-12-30 09:50:00 +01:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
# Buttons
|
|
|
|
##########################################################################
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def setupButtons(self) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
l = self.buttons = QHBoxLayout()
|
2020-11-17 08:42:43 +01:00
|
|
|
help = QPushButton(tr(TR.ACTIONS_HELP))
|
2012-12-21 08:51:59 +01:00
|
|
|
help.setAutoDefault(False)
|
|
|
|
l.addWidget(help)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(help.clicked, self.onHelp)
|
2012-12-21 08:51:59 +01:00
|
|
|
l.addStretch()
|
2020-11-17 08:42:43 +01:00
|
|
|
self.add_field_button = QPushButton(tr(TR.FIELDS_ADD_FIELD))
|
2020-05-14 10:01:15 +02:00
|
|
|
self.add_field_button.setAutoDefault(False)
|
|
|
|
l.addWidget(self.add_field_button)
|
|
|
|
qconnect(self.add_field_button.clicked, self.onAddField)
|
2017-08-08 11:45:31 +02:00
|
|
|
if not self._isCloze():
|
2020-11-17 08:42:43 +01:00
|
|
|
flip = QPushButton(tr(TR.CARD_TEMPLATES_FLIP))
|
2012-12-21 08:51:59 +01:00
|
|
|
flip.setAutoDefault(False)
|
|
|
|
l.addWidget(flip)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(flip.clicked, self.onFlip)
|
2012-12-21 08:51:59 +01:00
|
|
|
l.addStretch()
|
2020-11-17 08:42:43 +01:00
|
|
|
save = QPushButton(tr(TR.ACTIONS_SAVE))
|
2020-04-26 04:34:25 +02:00
|
|
|
save.setAutoDefault(False)
|
2020-08-06 12:22:54 +02:00
|
|
|
save.setShortcut(QKeySequence("Ctrl+Return"))
|
2020-04-26 04:34:25 +02:00
|
|
|
l.addWidget(save)
|
|
|
|
qconnect(save.clicked, self.accept)
|
|
|
|
|
2020-11-17 08:42:43 +01:00
|
|
|
close = QPushButton(tr(TR.ACTIONS_CANCEL))
|
2012-12-21 08:51:59 +01:00
|
|
|
close.setAutoDefault(False)
|
|
|
|
l.addWidget(close)
|
2020-04-26 04:34:25 +02:00
|
|
|
qconnect(close.clicked, self.reject)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
# Reading/writing question/answer/css
|
2012-12-21 08:51:59 +01:00
|
|
|
##########################################################################
|
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
def current_template(self) -> Dict:
|
2020-05-14 07:24:29 +02:00
|
|
|
if self._isCloze():
|
|
|
|
return self.templates[0]
|
2020-05-13 09:24:49 +02:00
|
|
|
return self.templates[self.ord]
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def fill_fields_from_template(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
t = self.current_template()
|
|
|
|
self.ignore_change_signals = True
|
2020-05-14 09:26:40 +02:00
|
|
|
|
|
|
|
if self.current_editor_index == 0:
|
|
|
|
text = t["qfmt"]
|
|
|
|
elif self.current_editor_index == 1:
|
|
|
|
text = t["afmt"]
|
2020-05-05 03:24:33 +02:00
|
|
|
else:
|
2020-05-14 09:26:40 +02:00
|
|
|
text = self.model["css"]
|
|
|
|
|
|
|
|
self.tform.edit_area.setPlainText(text)
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ignore_change_signals = False
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def write_edits_to_template_and_redraw(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
if self.ignore_change_signals:
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2020-05-14 09:26:40 +02:00
|
|
|
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker.mark_basic()
|
2020-05-14 09:26:40 +02:00
|
|
|
|
|
|
|
text = self.tform.edit_area.toPlainText()
|
|
|
|
|
|
|
|
if self.current_editor_index == 0:
|
2020-05-14 12:58:45 +02:00
|
|
|
self.current_template()["qfmt"] = text
|
2020-05-14 09:26:40 +02:00
|
|
|
elif self.current_editor_index == 1:
|
2020-05-14 12:58:45 +02:00
|
|
|
self.current_template()["afmt"] = text
|
2020-05-14 09:26:40 +02:00
|
|
|
else:
|
|
|
|
self.model["css"] = text
|
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
self.renderPreview()
|
|
|
|
|
|
|
|
# Preview
|
|
|
|
##########################################################################
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
_previewTimer: Optional[QTimer] = None
|
2017-08-08 08:02:55 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def renderPreview(self) -> None:
|
2017-08-08 08:02:55 +02:00
|
|
|
# schedule a preview when timing stops
|
|
|
|
self.cancelPreviewTimer()
|
2020-05-13 09:24:49 +02:00
|
|
|
self._previewTimer = self.mw.progress.timer(200, self._renderPreview, False)
|
2017-08-08 08:02:55 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def cancelPreviewTimer(self) -> None:
|
2017-08-08 08:02:55 +02:00
|
|
|
if self._previewTimer:
|
|
|
|
self._previewTimer.stop()
|
|
|
|
self._previewTimer = None
|
|
|
|
|
2020-01-15 22:41:23 +01:00
|
|
|
def _renderPreview(self) -> None:
|
2017-08-08 08:02:55 +02:00
|
|
|
self.cancelPreviewTimer()
|
|
|
|
|
2021-02-02 16:47:25 +01:00
|
|
|
c = self.rendered_card = self.note.ephemeral_card(
|
|
|
|
self.ord, fill_empty=self.fill_empty_action_toggled
|
|
|
|
)
|
2012-12-21 08:51:59 +01:00
|
|
|
ti = self.maybeTextInput
|
2017-12-04 03:20:56 +01:00
|
|
|
|
2020-07-31 06:47:17 +02:00
|
|
|
bodyclass = theme_manager.body_classes_for_card_ord(
|
|
|
|
c.ord, self.night_mode_is_enabled
|
|
|
|
)
|
2017-09-06 05:02:00 +02:00
|
|
|
|
2020-05-14 07:24:29 +02:00
|
|
|
if self.pform.preview_front.isChecked():
|
|
|
|
q = ti(self.mw.prepare_card_text_for_display(c.q()))
|
|
|
|
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
|
|
|
text = q
|
|
|
|
else:
|
|
|
|
a = ti(self.mw.prepare_card_text_for_display(c.a()), type="a")
|
|
|
|
a = gui_hooks.card_will_show(a, c, "clayoutAnswer")
|
|
|
|
text = a
|
2017-08-08 08:02:55 +02:00
|
|
|
|
2017-08-10 13:39:04 +02:00
|
|
|
# use _showAnswer to avoid the longer delay
|
2020-05-14 12:58:45 +02:00
|
|
|
self.preview_web.eval("_showAnswer(%s,'%s');" % (json.dumps(text), bodyclass))
|
2020-08-03 05:30:34 +02:00
|
|
|
self.preview_web.eval(
|
|
|
|
f"_emulateMobile({json.dumps(self.mobile_emulation_enabled)});"
|
|
|
|
)
|
2017-08-08 08:02:55 +02:00
|
|
|
|
2020-05-15 06:26:00 +02:00
|
|
|
if not self.have_autoplayed:
|
|
|
|
self.have_autoplayed = True
|
|
|
|
|
|
|
|
if c.autoplay():
|
2020-10-14 02:10:16 +02:00
|
|
|
AnkiWebView.setPlaybackRequiresGesture(False)
|
2020-05-15 06:26:00 +02:00
|
|
|
if self.pform.preview_front.isChecked():
|
|
|
|
audio = c.question_av_tags()
|
|
|
|
else:
|
|
|
|
audio = c.answer_av_tags()
|
|
|
|
av_player.play_tags(audio)
|
|
|
|
else:
|
2020-10-14 02:10:16 +02:00
|
|
|
AnkiWebView.setPlaybackRequiresGesture(True)
|
2020-05-15 06:26:00 +02:00
|
|
|
av_player.clear_queue_and_maybe_interrupt()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2017-08-09 03:11:39 +02:00
|
|
|
self.updateCardNames()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def maybeTextInput(self, txt: str, type: str = "q") -> str:
|
2013-05-23 04:11:25 +02:00
|
|
|
if "[[type:" not in txt:
|
|
|
|
return txt
|
2013-05-20 11:21:14 +02:00
|
|
|
origLen = len(txt)
|
|
|
|
txt = txt.replace("<hr id=answer>", "")
|
|
|
|
hadHR = origLen != len(txt)
|
2019-12-23 01:34:10 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def answerRepl(match: Match) -> str:
|
2016-05-12 06:45:35 +02:00
|
|
|
res = self.mw.reviewer.correct("exomple", "an example")
|
2013-05-20 11:21:14 +02:00
|
|
|
if hadHR:
|
|
|
|
res = "<hr id=answer>" + res
|
|
|
|
return res
|
2019-12-23 01:34:10 +01:00
|
|
|
|
2020-05-05 03:10:24 +02:00
|
|
|
repl: Union[str, Callable]
|
|
|
|
|
2019-12-23 01:34:10 +01:00
|
|
|
if type == "q":
|
2018-08-28 03:01:07 +02:00
|
|
|
repl = "<input id='typeans' type=text value='exomple' readonly='readonly'>"
|
2013-05-20 11:21:14 +02:00
|
|
|
repl = "<center>%s</center>" % repl
|
2012-12-21 08:51:59 +01:00
|
|
|
else:
|
2013-05-20 11:21:14 +02:00
|
|
|
repl = answerRepl
|
2019-03-04 08:03:43 +01:00
|
|
|
return re.sub(r"\[\[type:.+?\]\]", repl, txt)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
# Card operations
|
|
|
|
######################################################################
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onRemove(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
if len(self.templates) < 2:
|
2021-02-01 14:28:21 +01:00
|
|
|
showInfo(tr(TR.CARD_TEMPLATES_AT_LEAST_ONE_CARD_TYPE_IS))
|
|
|
|
return
|
2020-05-13 09:24:49 +02:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def get_count() -> int:
|
2020-05-15 07:28:07 +02:00
|
|
|
return self.mm.template_use_count(self.model["id"], self.ord)
|
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
def on_done(fut: Future) -> None:
|
2020-05-15 07:28:07 +02:00
|
|
|
card_cnt = fut.result()
|
|
|
|
|
|
|
|
template = self.current_template()
|
2020-11-18 00:22:27 +01:00
|
|
|
cards = tr(TR.CARD_TEMPLATES_CARD_COUNT, count=card_cnt)
|
2020-11-22 05:57:53 +01:00
|
|
|
msg = tr(
|
|
|
|
TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND,
|
|
|
|
template=template["name"],
|
|
|
|
cards=cards,
|
2020-05-15 07:28:07 +02:00
|
|
|
)
|
|
|
|
if not askUser(msg):
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self.change_tracker.mark_schema():
|
|
|
|
return
|
|
|
|
|
|
|
|
self.onRemoveInner(template)
|
|
|
|
|
|
|
|
self.mw.taskman.with_progress(get_count, on_done)
|
|
|
|
|
2021-02-02 15:00:29 +01:00
|
|
|
def onRemoveInner(self, template: Dict) -> None:
|
2020-05-15 05:59:44 +02:00
|
|
|
self.mm.remove_template(self.model, template)
|
2020-05-13 09:24:49 +02:00
|
|
|
|
|
|
|
# ensure current ordinal is within bounds
|
|
|
|
idx = self.ord
|
|
|
|
if idx >= len(self.templates):
|
|
|
|
self.ord = len(self.templates) - 1
|
|
|
|
|
|
|
|
self.redraw_everything()
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onRename(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
template = self.current_template()
|
2020-11-17 08:42:43 +01:00
|
|
|
name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=template["name"]).replace(
|
|
|
|
'"', ""
|
|
|
|
)
|
2020-05-13 09:24:49 +02:00
|
|
|
if not name.strip():
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2020-05-13 09:24:49 +02:00
|
|
|
|
|
|
|
template["name"] = name
|
|
|
|
self.redraw_everything()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onReorder(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
n = len(self.templates)
|
|
|
|
template = self.current_template()
|
|
|
|
current_pos = self.templates.index(template) + 1
|
2021-02-01 11:23:48 +01:00
|
|
|
pos_txt = getOnlyText(
|
2020-11-17 12:47:47 +01:00
|
|
|
tr(TR.CARD_TEMPLATES_ENTER_NEW_CARD_POSITION_1, val=n),
|
2020-11-17 08:42:43 +01:00
|
|
|
default=str(current_pos),
|
2020-05-13 09:24:49 +02:00
|
|
|
)
|
2021-02-01 11:23:48 +01:00
|
|
|
if not pos_txt:
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
|
|
|
try:
|
2021-02-01 11:23:48 +01:00
|
|
|
pos = int(pos_txt)
|
2012-12-21 08:51:59 +01:00
|
|
|
except ValueError:
|
|
|
|
return
|
|
|
|
if pos < 1 or pos > n:
|
|
|
|
return
|
2020-05-13 09:24:49 +02:00
|
|
|
if pos == current_pos:
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2020-05-13 09:24:49 +02:00
|
|
|
new_idx = pos - 1
|
2020-05-15 05:59:44 +02:00
|
|
|
if not self.change_tracker.mark_schema():
|
|
|
|
return
|
|
|
|
self.mm.reposition_template(self.model, template, new_idx)
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ord = new_idx
|
|
|
|
self.redraw_everything()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def _newCardName(self) -> str:
|
2020-05-13 09:24:49 +02:00
|
|
|
n = len(self.templates) + 1
|
2012-12-21 08:51:59 +01:00
|
|
|
while 1:
|
2020-12-16 03:09:33 +01:00
|
|
|
name = without_unicode_isolation(tr(TR.CARD_TEMPLATES_CARD, val=n))
|
2020-05-13 09:24:49 +02:00
|
|
|
if name not in [t["name"] for t in self.templates]:
|
2012-12-21 08:51:59 +01:00
|
|
|
break
|
|
|
|
n += 1
|
|
|
|
return name
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onAddCard(self) -> None:
|
2017-08-08 11:45:31 +02:00
|
|
|
cnt = self.mw.col.models.useCount(self.model)
|
2020-11-18 01:52:13 +01:00
|
|
|
txt = tr(TR.CARD_TEMPLATES_THIS_WILL_CREATE_CARD_PROCEED, count=cnt)
|
2017-08-08 11:45:31 +02:00
|
|
|
if not askUser(txt):
|
|
|
|
return
|
2020-05-15 05:59:44 +02:00
|
|
|
if not self.change_tracker.mark_schema():
|
|
|
|
return
|
2012-12-21 08:51:59 +01:00
|
|
|
name = self._newCardName()
|
|
|
|
t = self.mm.newTemplate(name)
|
2020-05-13 09:24:49 +02:00
|
|
|
old = self.current_template()
|
2019-12-23 01:34:10 +01:00
|
|
|
t["qfmt"] = old["qfmt"]
|
|
|
|
t["afmt"] = old["afmt"]
|
2020-05-15 05:59:44 +02:00
|
|
|
self.mm.add_template(self.model, t)
|
2020-05-13 09:24:49 +02:00
|
|
|
self.ord = len(self.templates) - 1
|
|
|
|
self.redraw_everything()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onFlip(self) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
old = self.current_template()
|
2012-12-21 08:51:59 +01:00
|
|
|
self._flipQA(old, old)
|
2020-05-13 09:24:49 +02:00
|
|
|
self.redraw_everything()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-02 15:00:29 +01:00
|
|
|
def _flipQA(self, src: Dict, dst: Dict) -> None:
|
2019-12-23 01:34:10 +01:00
|
|
|
m = re.match("(?s)(.+)<hr id=answer>(.+)", src["afmt"])
|
2012-12-21 08:51:59 +01:00
|
|
|
if not m:
|
2020-11-18 02:32:22 +01:00
|
|
|
showInfo(tr(TR.CARD_TEMPLATES_ANKI_COULDNT_FIND_THE_LINE_BETWEEN))
|
2012-12-21 08:51:59 +01:00
|
|
|
return
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker.mark_basic()
|
2019-12-23 01:34:10 +01:00
|
|
|
dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"]
|
|
|
|
dst["qfmt"] = m.group(2).strip()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def onMore(self) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
m = QMenu(self)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
|
|
|
if not self._isCloze():
|
2020-11-17 08:42:43 +01:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_ADD_CARD_TYPE))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onAddCard)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2020-11-17 08:42:43 +01:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_REMOVE_CARD_TYPE))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onRemove)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2020-11-17 08:42:43 +01:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_RENAME_CARD_TYPE))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onRename)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2020-11-17 08:42:43 +01:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_REPOSITION_CARD_TYPE))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onReorder)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
|
|
|
m.addSeparator()
|
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
t = self.current_template()
|
2019-12-23 01:34:10 +01:00
|
|
|
if t["did"]:
|
2020-11-17 08:42:43 +01:00
|
|
|
s = tr(TR.CARD_TEMPLATES_ON)
|
2012-12-21 08:51:59 +01:00
|
|
|
else:
|
2020-11-17 08:42:43 +01:00
|
|
|
s = tr(TR.CARD_TEMPLATES_OFF)
|
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_DECK_OVERRIDE) + s)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onTargetDeck)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2020-11-17 08:42:43 +01:00
|
|
|
a = m.addAction(tr(TR.CARD_TEMPLATES_BROWSER_APPEARANCE))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(a.triggered, self.onBrowserDisplay)
|
2017-08-08 11:45:31 +02:00
|
|
|
|
2019-12-23 01:34:10 +01:00
|
|
|
m.exec_(self.topAreaForm.templateOptions.mapToGlobal(QPoint(0, 0)))
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def onBrowserDisplay(self) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
d = QDialog()
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(d)
|
2012-12-21 08:51:59 +01:00
|
|
|
f = aqt.forms.browserdisp.Ui_Dialog()
|
|
|
|
f.setupUi(d)
|
2020-05-13 09:24:49 +02:00
|
|
|
t = self.current_template()
|
2019-12-23 01:34:10 +01:00
|
|
|
f.qfmt.setText(t.get("bqfmt", ""))
|
|
|
|
f.afmt.setText(t.get("bafmt", ""))
|
2017-07-29 08:09:00 +02:00
|
|
|
if t.get("bfont"):
|
|
|
|
f.overrideFont.setChecked(True)
|
2019-12-23 01:34:10 +01:00
|
|
|
f.font.setCurrentFont(QFont(t.get("bfont", "Arial")))
|
|
|
|
f.fontSize.setValue(t.get("bsize", 12))
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(f.buttonBox.accepted, lambda: self.onBrowserDisplayOk(f))
|
2012-12-21 08:51:59 +01:00
|
|
|
d.exec_()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def onBrowserDisplayOk(self, f: Ui_Dialog) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
t = self.current_template()
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker.mark_basic()
|
2019-12-23 01:34:10 +01:00
|
|
|
t["bqfmt"] = f.qfmt.text().strip()
|
|
|
|
t["bafmt"] = f.afmt.text().strip()
|
2017-07-29 08:09:00 +02:00
|
|
|
if f.overrideFont.isChecked():
|
2019-12-23 01:34:10 +01:00
|
|
|
t["bfont"] = f.font.currentFont().family()
|
|
|
|
t["bsize"] = f.fontSize.value()
|
2017-07-29 08:09:00 +02:00
|
|
|
else:
|
2018-02-26 01:21:12 +01:00
|
|
|
for key in ("bfont", "bsize"):
|
|
|
|
if key in t:
|
|
|
|
del t[key]
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onTargetDeck(self) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
from aqt.tagedit import TagEdit
|
2019-12-23 01:34:10 +01:00
|
|
|
|
2020-05-13 09:24:49 +02:00
|
|
|
t = self.current_template()
|
2012-12-21 08:51:59 +01:00
|
|
|
d = QDialog(self)
|
|
|
|
d.setWindowTitle("Anki")
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(d)
|
2012-12-21 08:51:59 +01:00
|
|
|
d.setMinimumWidth(400)
|
|
|
|
l = QVBoxLayout()
|
2019-12-23 01:34:10 +01:00
|
|
|
lab = QLabel(
|
2020-11-18 02:32:22 +01:00
|
|
|
tr(TR.CARD_TEMPLATES_ENTER_DECK_TO_PLACE_NEW, val="%s")
|
2020-05-13 09:24:49 +02:00
|
|
|
% self.current_template()["name"]
|
2019-12-23 01:34:10 +01:00
|
|
|
)
|
2012-12-21 08:51:59 +01:00
|
|
|
lab.setWordWrap(True)
|
|
|
|
l.addWidget(lab)
|
|
|
|
te = TagEdit(d, type=1)
|
|
|
|
te.setCol(self.col)
|
|
|
|
l.addWidget(te)
|
2019-12-23 01:34:10 +01:00
|
|
|
if t["did"]:
|
|
|
|
te.setText(self.col.decks.get(t["did"])["name"])
|
2012-12-21 08:51:59 +01:00
|
|
|
te.selectAll()
|
|
|
|
bb = QDialogButtonBox(QDialogButtonBox.Close)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(bb.rejected, d.close)
|
2012-12-21 08:51:59 +01:00
|
|
|
l.addWidget(bb)
|
|
|
|
d.setLayout(l)
|
|
|
|
d.exec_()
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker.mark_basic()
|
2012-12-21 08:51:59 +01:00
|
|
|
if not te.text().strip():
|
2019-12-23 01:34:10 +01:00
|
|
|
t["did"] = None
|
2012-12-21 08:51:59 +01:00
|
|
|
else:
|
2019-12-23 01:34:10 +01:00
|
|
|
t["did"] = self.col.decks.id(te.text())
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onAddField(self) -> None:
|
2012-12-21 08:51:59 +01:00
|
|
|
diag = QDialog(self)
|
|
|
|
form = aqt.forms.addfield.Ui_Dialog()
|
|
|
|
form.setupUi(diag)
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(diag)
|
2019-12-23 01:34:10 +01:00
|
|
|
fields = [f["name"] for f in self.model["flds"]]
|
2012-12-21 08:51:59 +01:00
|
|
|
form.fields.addItems(fields)
|
2020-05-14 10:01:15 +02:00
|
|
|
form.fields.setCurrentRow(0)
|
2012-12-21 08:51:59 +01:00
|
|
|
form.font.setCurrentFont(QFont("Arial"))
|
|
|
|
form.size.setValue(20)
|
|
|
|
if not diag.exec_():
|
|
|
|
return
|
2020-05-14 10:01:15 +02:00
|
|
|
row = form.fields.currentIndex().row()
|
|
|
|
if row >= 0:
|
|
|
|
self._addField(
|
2020-08-31 05:29:28 +02:00
|
|
|
fields[row],
|
|
|
|
form.font.currentFont().family(),
|
|
|
|
form.size.value(),
|
2020-05-14 10:01:15 +02:00
|
|
|
)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-02 15:00:29 +01:00
|
|
|
def _addField(self, field: str, font: str, size: int) -> None:
|
2020-05-14 10:01:15 +02:00
|
|
|
text = self.tform.edit_area.toPlainText()
|
|
|
|
text += "\n<div style='font-family: %s; font-size: %spx;'>{{%s}}</div>\n" % (
|
2019-12-23 01:34:10 +01:00
|
|
|
font,
|
|
|
|
size,
|
|
|
|
field,
|
|
|
|
)
|
2020-05-14 10:01:15 +02:00
|
|
|
self.tform.edit_area.setPlainText(text)
|
2020-05-15 05:59:44 +02:00
|
|
|
self.change_tracker.mark_basic()
|
2020-05-13 09:24:49 +02:00
|
|
|
self.write_edits_to_template_and_redraw()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
# Closing & Help
|
|
|
|
######################################################################
|
|
|
|
|
2020-04-26 04:34:25 +02:00
|
|
|
def accept(self) -> None:
|
2021-02-01 14:28:21 +01:00
|
|
|
def save() -> None:
|
2020-04-26 04:34:25 +02:00
|
|
|
self.mm.save(self.model)
|
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
def on_done(fut: Future) -> None:
|
2020-05-13 09:24:49 +02:00
|
|
|
try:
|
|
|
|
fut.result()
|
|
|
|
except TemplateError as e:
|
2020-07-29 04:42:09 +02:00
|
|
|
showWarning(str(e))
|
2020-05-13 09:24:49 +02:00
|
|
|
return
|
|
|
|
self.mw.reset()
|
2020-07-31 04:41:43 +02:00
|
|
|
tooltip(tr(TR.CARD_TEMPLATES_CHANGES_SAVED), parent=self.parent())
|
2020-05-13 09:24:49 +02:00
|
|
|
self.cleanup()
|
2020-05-22 02:47:14 +02:00
|
|
|
gui_hooks.sidebar_should_refresh_notetypes()
|
2020-05-13 09:24:49 +02:00
|
|
|
return QDialog.accept(self)
|
|
|
|
|
|
|
|
self.mw.taskman.with_progress(save, on_done)
|
2020-04-26 04:34:25 +02:00
|
|
|
|
|
|
|
def reject(self) -> None:
|
2020-05-15 05:59:44 +02:00
|
|
|
if self.change_tracker.changed():
|
2020-07-31 04:41:43 +02:00
|
|
|
if not askUser(tr(TR.CARD_TEMPLATES_DISCARD_CHANGES)):
|
2020-05-13 09:24:49 +02:00
|
|
|
return
|
2020-04-26 04:34:25 +02:00
|
|
|
self.cleanup()
|
|
|
|
return QDialog.reject(self)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2020-04-26 04:34:25 +02:00
|
|
|
def cleanup(self) -> None:
|
2017-08-08 08:02:55 +02:00
|
|
|
self.cancelPreviewTimer()
|
2020-01-20 11:10:38 +01:00
|
|
|
av_player.stop_and_clear_queue()
|
2012-12-21 08:51:59 +01:00
|
|
|
saveGeom(self, "CardLayout")
|
2020-05-31 01:31:34 +02:00
|
|
|
saveSplitter(self.mainArea, "CardLayoutMainArea")
|
2020-05-14 12:58:45 +02:00
|
|
|
self.preview_web = None
|
2020-05-13 09:24:49 +02:00
|
|
|
self.model = None
|
|
|
|
self.rendered_card = None
|
|
|
|
self.mw = None
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def onHelp(self) -> None:
|
2021-01-25 14:45:47 +01:00
|
|
|
openHelp(HelpPage.TEMPLATES)
|