mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Enable strict_optional for aqt/editor.py (#3500)
* Enable strict_optional for aqt/editor.py * Fix mypy errors in EditorWebView class * Fix mypy errors in Editor class * DRY * Match same short circuiting behavior * Convention
This commit is contained in:
parent
694a5089a9
commit
a537d349b8
2 changed files with 101 additions and 51 deletions
|
@ -38,6 +38,8 @@ strict_optional = True
|
||||||
strict_optional = True
|
strict_optional = True
|
||||||
[mypy-aqt.operations.*]
|
[mypy-aqt.operations.*]
|
||||||
strict_optional = True
|
strict_optional = True
|
||||||
|
[mypy-aqt.editor]
|
||||||
|
strict_optional = True
|
||||||
[mypy-anki.scheduler.base]
|
[mypy-anki.scheduler.base]
|
||||||
strict_optional = True
|
strict_optional = True
|
||||||
[mypy-anki._backend.rsbridge]
|
[mypy-anki._backend.rsbridge]
|
||||||
|
|
110
qt/aqt/editor.py
110
qt/aqt/editor.py
|
@ -34,7 +34,7 @@ from anki.collection import Config, SearchNode
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.hooks import runFilter
|
from anki.hooks import runFilter
|
||||||
from anki.httpclient import HttpClient
|
from anki.httpclient import HttpClient
|
||||||
from anki.models import NotetypeId, StockNotetype
|
from anki.models import NotetypeDict, NotetypeId, StockNotetype
|
||||||
from anki.notes import Note, NoteFieldsCheckResult, NoteId
|
from anki.notes import Note, NoteFieldsCheckResult, NoteId
|
||||||
from anki.utils import checksum, is_lin, is_mac, is_win, namedtmp
|
from anki.utils import checksum, is_lin, is_mac, is_win, namedtmp
|
||||||
from aqt import AnkiQt, colors, gui_hooks
|
from aqt import AnkiQt, colors, gui_hooks
|
||||||
|
@ -242,12 +242,9 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
rightside: bool = True,
|
rightside: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Assign func to bridge cmd, register shortcut, return button"""
|
"""Assign func to bridge cmd, register shortcut, return button"""
|
||||||
if func:
|
|
||||||
|
|
||||||
def wrapped_func(editor: Editor) -> None:
|
def wrapped_func(editor: Editor) -> None:
|
||||||
self.call_after_note_saved(
|
self.call_after_note_saved(functools.partial(func, editor), keepFocus=True)
|
||||||
functools.partial(func, editor), keepFocus=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self._links[cmd] = wrapped_func
|
self._links[cmd] = wrapped_func
|
||||||
|
|
||||||
|
@ -363,7 +360,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def _onFields(self) -> None:
|
def _onFields(self) -> None:
|
||||||
from aqt.fields import FieldDialog
|
from aqt.fields import FieldDialog
|
||||||
|
|
||||||
FieldDialog(self.mw, self.note.note_type(), parent=self.parentWindow)
|
FieldDialog(self.mw, self.note_type(), parent=self.parentWindow)
|
||||||
|
|
||||||
def onCardLayout(self) -> None:
|
def onCardLayout(self) -> None:
|
||||||
self.call_after_note_saved(self._onCardLayout)
|
self.call_after_note_saved(self._onCardLayout)
|
||||||
|
@ -375,6 +372,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
ord = self.card.ord
|
ord = self.card.ord
|
||||||
else:
|
else:
|
||||||
ord = 0
|
ord = 0
|
||||||
|
|
||||||
|
assert self.note is not None
|
||||||
CardLayout(
|
CardLayout(
|
||||||
self.mw,
|
self.mw,
|
||||||
self.note,
|
self.note,
|
||||||
|
@ -435,7 +434,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
gui_hooks.editor_did_focus_field(self.note, self.currentField)
|
gui_hooks.editor_did_focus_field(self.note, self.currentField)
|
||||||
|
|
||||||
elif cmd.startswith("toggleStickyAll"):
|
elif cmd.startswith("toggleStickyAll"):
|
||||||
model = self.note.note_type()
|
model = self.note_type()
|
||||||
flds = model["flds"]
|
flds = model["flds"]
|
||||||
|
|
||||||
any_sticky = any([fld["sticky"] for fld in flds])
|
any_sticky = any([fld["sticky"] for fld in flds])
|
||||||
|
@ -456,7 +455,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
(type, num) = cmd.split(":", 1)
|
(type, num) = cmd.split(":", 1)
|
||||||
ord = int(num)
|
ord = int(num)
|
||||||
|
|
||||||
model = self.note.note_type()
|
model = self.note_type()
|
||||||
fld = model["flds"][ord]
|
fld = model["flds"][ord]
|
||||||
new_state = not fld["sticky"]
|
new_state = not fld["sticky"]
|
||||||
fld["sticky"] = new_state
|
fld["sticky"] = new_state
|
||||||
|
@ -469,10 +468,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
|
|
||||||
elif cmd.startswith("lastTextColor"):
|
elif cmd.startswith("lastTextColor"):
|
||||||
(_, textColor) = cmd.split(":", 1)
|
(_, textColor) = cmd.split(":", 1)
|
||||||
|
assert self.mw.pm.profile is not None
|
||||||
self.mw.pm.profile["lastTextColor"] = textColor
|
self.mw.pm.profile["lastTextColor"] = textColor
|
||||||
|
|
||||||
elif cmd.startswith("lastHighlightColor"):
|
elif cmd.startswith("lastHighlightColor"):
|
||||||
(_, highlightColor) = cmd.split(":", 1)
|
(_, highlightColor) = cmd.split(":", 1)
|
||||||
|
assert self.mw.pm.profile is not None
|
||||||
self.mw.pm.profile["lastHighlightColor"] = highlightColor
|
self.mw.pm.profile["lastHighlightColor"] = highlightColor
|
||||||
|
|
||||||
elif cmd.startswith("saveTags"):
|
elif cmd.startswith("saveTags"):
|
||||||
|
@ -545,11 +546,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
for fld, val in self.note.items()
|
for fld, val in self.note.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
flds = self.note.note_type()["flds"]
|
note_type = self.note_type()
|
||||||
|
flds = note_type["flds"]
|
||||||
collapsed = [fld["collapsed"] for fld in flds]
|
collapsed = [fld["collapsed"] for fld in flds]
|
||||||
plain_texts = [fld.get("plainText", False) for fld in flds]
|
plain_texts = [fld.get("plainText", False) for fld in flds]
|
||||||
descriptions = [fld.get("description", "") for fld in flds]
|
descriptions = [fld.get("description", "") for fld in flds]
|
||||||
notetype_meta = {"id": self.note.mid, "modTime": self.note.note_type()["mod"]}
|
notetype_meta = {"id": self.note.mid, "modTime": note_type["mod"]}
|
||||||
|
|
||||||
self.widget.show()
|
self.widget.show()
|
||||||
|
|
||||||
|
@ -566,6 +568,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
self.web.setFocus()
|
self.web.setFocus()
|
||||||
gui_hooks.editor_did_load_note(self)
|
gui_hooks.editor_did_load_note(self)
|
||||||
|
|
||||||
|
assert self.mw.pm.profile is not None
|
||||||
text_color = self.mw.pm.profile.get("lastTextColor", "#0000ff")
|
text_color = self.mw.pm.profile.get("lastTextColor", "#0000ff")
|
||||||
highlight_color = self.mw.pm.profile.get("lastHighlightColor", "#0000ff")
|
highlight_color = self.mw.pm.profile.get("lastHighlightColor", "#0000ff")
|
||||||
|
|
||||||
|
@ -590,7 +593,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
sticky = [field["sticky"] for field in self.note.note_type()["flds"]]
|
sticky = [field["sticky"] for field in self.note_type()["flds"]]
|
||||||
js += " setSticky(%s);" % json.dumps(sticky)
|
js += " setSticky(%s);" % json.dumps(sticky)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -607,6 +610,9 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
|
|
||||||
def _save_current_note(self) -> None:
|
def _save_current_note(self) -> None:
|
||||||
"Call after note is updated with data from webview."
|
"Call after note is updated with data from webview."
|
||||||
|
if not self.note:
|
||||||
|
return
|
||||||
|
|
||||||
update_note(parent=self.widget, note=self.note).run_in_background(
|
update_note(parent=self.widget, note=self.note).run_in_background(
|
||||||
initiator=self
|
initiator=self
|
||||||
)
|
)
|
||||||
|
@ -614,7 +620,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def fonts(self) -> list[tuple[str, int, bool]]:
|
def fonts(self) -> list[tuple[str, int, bool]]:
|
||||||
return [
|
return [
|
||||||
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
|
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
|
||||||
for f in self.note.note_type()["flds"]
|
for f in self.note_type()["flds"]
|
||||||
]
|
]
|
||||||
|
|
||||||
def call_after_note_saved(
|
def call_after_note_saved(
|
||||||
|
@ -648,6 +654,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
checkValid = _check_and_update_duplicate_display_async
|
checkValid = _check_and_update_duplicate_display_async
|
||||||
|
|
||||||
def _update_duplicate_display(self, result: NoteFieldsCheckResult.V) -> None:
|
def _update_duplicate_display(self, result: NoteFieldsCheckResult.V) -> None:
|
||||||
|
assert self.note is not None
|
||||||
cols = [""] * len(self.note.fields)
|
cols = [""] * len(self.note.fields)
|
||||||
cloze_hint = ""
|
cloze_hint = ""
|
||||||
if result == NoteFieldsCheckResult.DUPLICATE:
|
if result == NoteFieldsCheckResult.DUPLICATE:
|
||||||
|
@ -665,13 +672,14 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
)
|
)
|
||||||
|
|
||||||
def showDupes(self) -> None:
|
def showDupes(self) -> None:
|
||||||
|
assert self.note is not None
|
||||||
aqt.dialogs.open(
|
aqt.dialogs.open(
|
||||||
"Browser",
|
"Browser",
|
||||||
self.mw,
|
self.mw,
|
||||||
search=(
|
search=(
|
||||||
SearchNode(
|
SearchNode(
|
||||||
dupe=SearchNode.Dupe(
|
dupe=SearchNode.Dupe(
|
||||||
notetype_id=self.note.note_type()["id"],
|
notetype_id=self.note_type()["id"],
|
||||||
first_field=self.note.fields[0],
|
first_field=self.note.fields[0],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -681,7 +689,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def fieldsAreBlank(self, previousNote: Note | None = None) -> bool:
|
def fieldsAreBlank(self, previousNote: Note | None = None) -> bool:
|
||||||
if not self.note:
|
if not self.note:
|
||||||
return True
|
return True
|
||||||
m = self.note.note_type()
|
m = self.note_type()
|
||||||
for c, f in enumerate(self.note.fields):
|
for c, f in enumerate(self.note.fields):
|
||||||
f = f.replace("<br>", "").strip()
|
f = f.replace("<br>", "").strip()
|
||||||
notChangedvalues = {"", "<br>"}
|
notChangedvalues = {"", "<br>"}
|
||||||
|
@ -696,7 +704,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
# prevent any remaining evalWithCallback() events from firing after C++ object deleted
|
# prevent any remaining evalWithCallback() events from firing after C++ object deleted
|
||||||
if self.web:
|
if self.web:
|
||||||
self.web.cleanup()
|
self.web.cleanup()
|
||||||
self.web = None
|
self.web = None # type: ignore
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
|
|
||||||
|
@ -729,9 +737,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
if self.tags.col != self.mw.col:
|
if self.tags.col != self.mw.col:
|
||||||
self.tags.setCol(self.mw.col)
|
self.tags.setCol(self.mw.col)
|
||||||
if not self.tags.text() or not self.addMode:
|
if not self.tags.text() or not self.addMode:
|
||||||
|
assert self.note is not None
|
||||||
self.tags.setText(self.note.string_tags().strip())
|
self.tags.setText(self.note.string_tags().strip())
|
||||||
|
|
||||||
def on_tag_focus_lost(self) -> None:
|
def on_tag_focus_lost(self) -> None:
|
||||||
|
assert self.note is not None
|
||||||
self.note.tags = self.mw.col.tags.split(self.tags.text())
|
self.note.tags = self.mw.col.tags.split(self.tags.text())
|
||||||
gui_hooks.editor_did_update_tags(self.note)
|
gui_hooks.editor_did_update_tags(self.note)
|
||||||
if not self.addMode:
|
if not self.addMode:
|
||||||
|
@ -826,7 +836,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
# Media downloads
|
# Media downloads
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def urlToLink(self, url: str) -> str | None:
|
def urlToLink(self, url: str) -> str:
|
||||||
fname = self.urlToFile(url)
|
fname = self.urlToFile(url)
|
||||||
if not fname:
|
if not fname:
|
||||||
return '<a href="{}">{}</a>'.format(
|
return '<a href="{}">{}</a>'.format(
|
||||||
|
@ -1037,8 +1047,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def current_notetype_is_image_occlusion(self) -> bool:
|
def current_notetype_is_image_occlusion(self) -> bool:
|
||||||
return bool(self.note) and (
|
if not self.note:
|
||||||
self.note.note_type().get("originalStockKind", None)
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.note_type().get("originalStockKind", None)
|
||||||
== StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION
|
== StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1049,6 +1062,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
image_path=image_path, notetype_id=0
|
image_path=image_path, notetype_id=0
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
assert self.note is not None
|
||||||
self.setup_mask_editor_for_existing_note(
|
self.setup_mask_editor_for_existing_note(
|
||||||
note_id=self.note.id, image_path=image_path
|
note_id=self.note.id, image_path=image_path
|
||||||
)
|
)
|
||||||
|
@ -1075,8 +1089,10 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def select_image_from_clipboard_and_occlude(self) -> None:
|
def select_image_from_clipboard_and_occlude(self) -> None:
|
||||||
"""Set up the mask editor for the image in the clipboard."""
|
"""Set up the mask editor for the image in the clipboard."""
|
||||||
|
|
||||||
clipoard = self.mw.app.clipboard()
|
clipboard = self.mw.app.clipboard()
|
||||||
mime = clipoard.mimeData()
|
assert clipboard is not None
|
||||||
|
mime = clipboard.mimeData()
|
||||||
|
assert mime is not None
|
||||||
if not mime.hasImage():
|
if not mime.hasImage():
|
||||||
showWarning(tr.editing_no_image_found_on_clipboard())
|
showWarning(tr.editing_no_image_found_on_clipboard())
|
||||||
return
|
return
|
||||||
|
@ -1160,6 +1176,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
|
|
||||||
@deprecated(info=_js_legacy)
|
@deprecated(info=_js_legacy)
|
||||||
def _onHtmlEdit(self, field: int) -> None:
|
def _onHtmlEdit(self, field: int) -> None:
|
||||||
|
assert self.note is not None
|
||||||
d = QDialog(self.widget, Qt.WindowType.Window)
|
d = QDialog(self.widget, Qt.WindowType.Window)
|
||||||
form = aqt.forms.edithtml.Ui_Dialog()
|
form = aqt.forms.edithtml.Ui_Dialog()
|
||||||
form.setupUi(d)
|
form.setupUi(d)
|
||||||
|
@ -1223,7 +1240,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
@deprecated(info=_js_legacy)
|
@deprecated(info=_js_legacy)
|
||||||
def _onCloze(self) -> None:
|
def _onCloze(self) -> None:
|
||||||
# check that the model is set up for cloze deletion
|
# check that the model is set up for cloze deletion
|
||||||
if self.note.note_type()["type"] != MODEL_CLOZE:
|
if self.note_type()["type"] != MODEL_CLOZE:
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
tooltip(tr.editing_warning_cloze_deletions_will_not_work())
|
tooltip(tr.editing_warning_cloze_deletions_will_not_work())
|
||||||
else:
|
else:
|
||||||
|
@ -1231,7 +1248,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
return
|
return
|
||||||
# find the highest existing cloze
|
# find the highest existing cloze
|
||||||
highest = 0
|
highest = 0
|
||||||
for name, val in list(self.note.items()):
|
assert self.note is not None
|
||||||
|
for _, val in list(self.note.items()):
|
||||||
m = re.findall(r"\{\{c(\d+)::", val)
|
m = re.findall(r"\{\{c(\d+)::", val)
|
||||||
if m:
|
if m:
|
||||||
highest = max(highest, sorted(int(x) for x in m)[-1])
|
highest = max(highest, sorted(int(x) for x in m)[-1])
|
||||||
|
@ -1243,6 +1261,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
self.web.eval("wrap('{{c%d::', '}}');" % highest)
|
self.web.eval("wrap('{{c%d::', '}}');" % highest)
|
||||||
|
|
||||||
def setupForegroundButton(self) -> None:
|
def setupForegroundButton(self) -> None:
|
||||||
|
assert self.mw.pm.profile is not None
|
||||||
self.fcolour = self.mw.pm.profile.get("lastColour", "#00f")
|
self.fcolour = self.mw.pm.profile.get("lastColour", "#00f")
|
||||||
|
|
||||||
# use last colour
|
# use last colour
|
||||||
|
@ -1276,6 +1295,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
@deprecated(info=_js_legacy)
|
@deprecated(info=_js_legacy)
|
||||||
def onColourChanged(self) -> None:
|
def onColourChanged(self) -> None:
|
||||||
self._updateForegroundButton()
|
self._updateForegroundButton()
|
||||||
|
assert self.mw.pm.profile is not None
|
||||||
self.mw.pm.profile["lastColour"] = self.fcolour
|
self.mw.pm.profile["lastColour"] = self.fcolour
|
||||||
|
|
||||||
@deprecated(info=_js_legacy)
|
@deprecated(info=_js_legacy)
|
||||||
|
@ -1300,6 +1320,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
(tr.editing_edit_html(), self.onHtmlEdit, "Ctrl+Shift+X"),
|
(tr.editing_edit_html(), self.onHtmlEdit, "Ctrl+Shift+X"),
|
||||||
):
|
):
|
||||||
a = m.addAction(text)
|
a = m.addAction(text)
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, handler)
|
qconnect(a.triggered, handler)
|
||||||
a.setShortcut(QKeySequence(shortcut))
|
a.setShortcut(QKeySequence(shortcut))
|
||||||
|
|
||||||
|
@ -1387,6 +1408,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude,
|
addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def note_type(self) -> NotetypeDict:
|
||||||
|
assert self.note is not None
|
||||||
|
note_type = self.note.note_type()
|
||||||
|
assert note_type is not None
|
||||||
|
return note_type
|
||||||
|
|
||||||
|
|
||||||
# Pasting, drag & drop, and keyboard layouts
|
# Pasting, drag & drop, and keyboard layouts
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -1403,6 +1430,7 @@ class EditorWebView(AnkiWebView):
|
||||||
self._internal_field_text_for_paste: str | None = None
|
self._internal_field_text_for_paste: str | None = None
|
||||||
self._last_known_clipboard_mime: QMimeData | None = None
|
self._last_known_clipboard_mime: QMimeData | None = None
|
||||||
clip = self.editor.mw.app.clipboard()
|
clip = self.editor.mw.app.clipboard()
|
||||||
|
assert clip is not None
|
||||||
clip.dataChanged.connect(self._on_clipboard_change)
|
clip.dataChanged.connect(self._on_clipboard_change)
|
||||||
gui_hooks.editor_web_view_did_init(self)
|
gui_hooks.editor_web_view_did_init(self)
|
||||||
|
|
||||||
|
@ -1411,7 +1439,7 @@ class EditorWebView(AnkiWebView):
|
||||||
self._internal_field_text_for_paste = None
|
self._internal_field_text_for_paste = None
|
||||||
|
|
||||||
def _on_clipboard_change(self) -> None:
|
def _on_clipboard_change(self) -> None:
|
||||||
self._last_known_clipboard_mime = self.editor.mw.app.clipboard().mimeData()
|
self._last_known_clipboard_mime = self._clipboard().mimeData()
|
||||||
if self._store_field_content_on_next_clipboard_change:
|
if self._store_field_content_on_next_clipboard_change:
|
||||||
# if the flag was set, save the field data
|
# if the flag was set, save the field data
|
||||||
self._internal_field_text_for_paste = self._get_clipboard_html_for_field()
|
self._internal_field_text_for_paste = self._get_clipboard_html_for_field()
|
||||||
|
@ -1423,8 +1451,9 @@ class EditorWebView(AnkiWebView):
|
||||||
self._internal_field_text_for_paste = None
|
self._internal_field_text_for_paste = None
|
||||||
|
|
||||||
def _get_clipboard_html_for_field(self):
|
def _get_clipboard_html_for_field(self):
|
||||||
clip = self.editor.mw.app.clipboard()
|
clip = self._clipboard()
|
||||||
mime = clip.mimeData()
|
mime = clip.mimeData()
|
||||||
|
assert mime is not None
|
||||||
if not mime.hasHtml():
|
if not mime.hasHtml():
|
||||||
return
|
return
|
||||||
return mime.html()
|
return mime.html()
|
||||||
|
@ -1440,6 +1469,7 @@ class EditorWebView(AnkiWebView):
|
||||||
|
|
||||||
def _opened_context_menu_on_image(self) -> bool:
|
def _opened_context_menu_on_image(self) -> bool:
|
||||||
context_menu_request = self.lastContextMenuRequest()
|
context_menu_request = self.lastContextMenuRequest()
|
||||||
|
assert context_menu_request is not None
|
||||||
return (
|
return (
|
||||||
context_menu_request.mediaType()
|
context_menu_request.mediaType()
|
||||||
== context_menu_request.MediaType.MediaTypeImage
|
== context_menu_request.MediaType.MediaTypeImage
|
||||||
|
@ -1455,7 +1485,8 @@ class EditorWebView(AnkiWebView):
|
||||||
|
|
||||||
def _onPaste(self, mode: QClipboard.Mode) -> None:
|
def _onPaste(self, mode: QClipboard.Mode) -> None:
|
||||||
# Since _on_clipboard_change doesn't always trigger properly on macOS, we do a double check if any changes were made before pasting
|
# Since _on_clipboard_change doesn't always trigger properly on macOS, we do a double check if any changes were made before pasting
|
||||||
if self._last_known_clipboard_mime != self.editor.mw.app.clipboard().mimeData():
|
clipboard = self._clipboard()
|
||||||
|
if self._last_known_clipboard_mime != clipboard.mimeData():
|
||||||
self._on_clipboard_change()
|
self._on_clipboard_change()
|
||||||
extended = self._wantsExtendedPaste()
|
extended = self._wantsExtendedPaste()
|
||||||
if html := self._internal_field_text_for_paste:
|
if html := self._internal_field_text_for_paste:
|
||||||
|
@ -1463,7 +1494,8 @@ class EditorWebView(AnkiWebView):
|
||||||
self.editor.doPaste(html, True, extended)
|
self.editor.doPaste(html, True, extended)
|
||||||
else:
|
else:
|
||||||
print("use clipboard")
|
print("use clipboard")
|
||||||
mime = self.editor.mw.app.clipboard().mimeData(mode=mode)
|
mime = clipboard.mimeData(mode=mode)
|
||||||
|
assert mime is not None
|
||||||
html, internal = self._processMime(mime, extended)
|
html, internal = self._processMime(mime, extended)
|
||||||
if html:
|
if html:
|
||||||
self.editor.doPaste(html, internal, extended)
|
self.editor.doPaste(html, internal, extended)
|
||||||
|
@ -1474,12 +1506,15 @@ class EditorWebView(AnkiWebView):
|
||||||
def onMiddleClickPaste(self) -> None:
|
def onMiddleClickPaste(self) -> None:
|
||||||
self._onPaste(QClipboard.Mode.Selection)
|
self._onPaste(QClipboard.Mode.Selection)
|
||||||
|
|
||||||
def dragEnterEvent(self, evt: QDragEnterEvent) -> None:
|
def dragEnterEvent(self, evt: QDragEnterEvent | None) -> None:
|
||||||
|
assert evt is not None
|
||||||
evt.accept()
|
evt.accept()
|
||||||
|
|
||||||
def dropEvent(self, evt: QDropEvent) -> None:
|
def dropEvent(self, evt: QDropEvent | None) -> None:
|
||||||
|
assert evt is not None
|
||||||
extended = self._wantsExtendedPaste()
|
extended = self._wantsExtendedPaste()
|
||||||
mime = evt.mimeData()
|
mime = evt.mimeData()
|
||||||
|
assert mime is not None
|
||||||
cursor_pos = self.mapFromGlobal(QCursor.pos())
|
cursor_pos = self.mapFromGlobal(QCursor.pos())
|
||||||
|
|
||||||
if evt.source() and mime.hasHtml():
|
if evt.source() and mime.hasHtml():
|
||||||
|
@ -1585,12 +1620,13 @@ class EditorWebView(AnkiWebView):
|
||||||
|
|
||||||
return fname
|
return fname
|
||||||
|
|
||||||
def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
|
def contextMenuEvent(self, evt: QContextMenuEvent | None) -> None:
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
if self.hasSelection():
|
if self.hasSelection():
|
||||||
self._add_cut_action(m)
|
self._add_cut_action(m)
|
||||||
self._add_copy_action(m)
|
self._add_copy_action(m)
|
||||||
a = m.addAction(tr.editing_paste())
|
a = m.addAction(tr.editing_paste())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, self.onPaste)
|
qconnect(a.triggered, self.onPaste)
|
||||||
if self._opened_context_menu_on_image():
|
if self._opened_context_menu_on_image():
|
||||||
self._add_image_menu(m)
|
self._add_image_menu(m)
|
||||||
|
@ -1599,26 +1635,38 @@ class EditorWebView(AnkiWebView):
|
||||||
|
|
||||||
def _add_cut_action(self, menu: QMenu) -> None:
|
def _add_cut_action(self, menu: QMenu) -> None:
|
||||||
a = menu.addAction(tr.editing_cut())
|
a = menu.addAction(tr.editing_cut())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, self.onCut)
|
qconnect(a.triggered, self.onCut)
|
||||||
|
|
||||||
def _add_copy_action(self, menu: QMenu) -> None:
|
def _add_copy_action(self, menu: QMenu) -> None:
|
||||||
a = menu.addAction(tr.actions_copy())
|
a = menu.addAction(tr.actions_copy())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, self.onCopy)
|
qconnect(a.triggered, self.onCopy)
|
||||||
|
|
||||||
def _add_image_menu(self, menu: QMenu) -> None:
|
def _add_image_menu(self, menu: QMenu) -> None:
|
||||||
a = menu.addAction(tr.editing_copy_image())
|
a = menu.addAction(tr.editing_copy_image())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, self.on_copy_image)
|
qconnect(a.triggered, self.on_copy_image)
|
||||||
|
|
||||||
url = self.lastContextMenuRequest().mediaUrl()
|
context_menu_request = self.lastContextMenuRequest()
|
||||||
|
assert context_menu_request is not None
|
||||||
|
url = context_menu_request.mediaUrl()
|
||||||
file_name = url.fileName()
|
file_name = url.fileName()
|
||||||
path = os.path.join(self.editor.mw.col.media.dir(), file_name)
|
path = os.path.join(self.editor.mw.col.media.dir(), file_name)
|
||||||
a = menu.addAction(tr.editing_open_image())
|
a = menu.addAction(tr.editing_open_image())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, lambda: openFolder(path))
|
qconnect(a.triggered, lambda: openFolder(path))
|
||||||
|
|
||||||
if is_win or is_mac:
|
if is_win or is_mac:
|
||||||
a = menu.addAction(tr.editing_show_in_folder())
|
a = menu.addAction(tr.editing_show_in_folder())
|
||||||
|
assert a is not None
|
||||||
qconnect(a.triggered, lambda: show_in_folder(path))
|
qconnect(a.triggered, lambda: show_in_folder(path))
|
||||||
|
|
||||||
|
def _clipboard(self) -> QClipboard:
|
||||||
|
clipboard = self.editor.mw.app.clipboard()
|
||||||
|
assert clipboard is not None
|
||||||
|
return clipboard
|
||||||
|
|
||||||
|
|
||||||
# QFont returns "Kozuka Gothic Pro L" but WebEngine expects "Kozuka Gothic Pro Light"
|
# QFont returns "Kozuka Gothic Pro L" but WebEngine expects "Kozuka Gothic Pro Light"
|
||||||
# - there may be other cases like a trailing 'Bold' that need fixing, but will
|
# - there may be other cases like a trailing 'Bold' that need fixing, but will
|
||||||
|
@ -1648,7 +1696,7 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting)
|
||||||
|
|
||||||
|
|
||||||
def set_cloze_button(editor: Editor) -> None:
|
def set_cloze_button(editor: Editor) -> None:
|
||||||
action = "show" if editor.note.note_type()["type"] == MODEL_CLOZE else "hide"
|
action = "show" if editor.note_type()["type"] == MODEL_CLOZE else "hide"
|
||||||
editor.web.eval(
|
editor.web.eval(
|
||||||
'require("anki/ui").loaded.then(() =>'
|
'require("anki/ui").loaded.then(() =>'
|
||||||
f'require("anki/NoteEditor").instances[0].toolbar.toolbar.{action}("cloze")'
|
f'require("anki/NoteEditor").instances[0].toolbar.toolbar.{action}("cloze")'
|
||||||
|
|
Loading…
Reference in a new issue