Add back legacy code as separate screens

This commit is contained in:
Abdo 2025-07-21 22:30:25 +03:00
parent c91f943f29
commit 7dac2fc4ff
11 changed files with 2383 additions and 99 deletions

View file

@ -125,9 +125,11 @@ from aqt import stats, about, preferences, mediasync # isort:skip
class DialogManager:
_dialogs: dict[str, list] = {
"AddCards": [addcards.AddCards, None],
"NewAddCards": [addcards.NewAddCards, None],
"AddonsDialog": [addons.AddonsDialog, None],
"Browser": [browser.Browser, None],
"EditCurrent": [editcurrent.EditCurrent, None],
"NewEditCurrent": [editcurrent.NewEditCurrent, None],
"FilteredDeckConfigDialog": [filtered_deck.FilteredDeckConfigDialog, None],
"DeckStats": [stats.DeckStats, None],
"NewDeckStats": [stats.NewDeckStats, None],

View file

@ -14,6 +14,7 @@ from anki.models import NotetypeId
from anki.notes import Note, NoteId
from anki.utils import html_to_text_line, is_mac
from aqt import AnkiQt, gui_hooks
from aqt.addcards_legacy import *
from aqt.deckchooser import DeckChooser
from aqt.notetypechooser import NotetypeChooser
from aqt.qt import *
@ -30,7 +31,7 @@ from aqt.utils import (
)
class AddCards(QMainWindow):
class NewAddCards(QMainWindow):
def __init__(self, mw: AnkiQt) -> None:
super().__init__(None, Qt.WindowType.Window)
self._close_event_has_cleaned_up = False
@ -79,7 +80,7 @@ class AddCards(QMainWindow):
self.setAndFocusNote(new_note)
def setupEditor(self) -> None:
self.editor = aqt.editor.Editor(
self.editor = aqt.editor.NewEditor(
self.mw,
self.form.fieldsArea,
self,
@ -244,7 +245,7 @@ class AddCards(QMainWindow):
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
self.mw.maybeReset()
saveGeom(self, "add")
aqt.dialogs.markClosed("AddCards")
aqt.dialogs.markClosed("NewAddCards")
self._close_event_has_cleaned_up = True
self.mw.deferred_delete_and_garbage_collect(self)
self.close()

414
qt/aqt/addcards_legacy.py Normal file
View file

@ -0,0 +1,414 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from collections.abc import Callable
import aqt.editor
import aqt.forms
from anki._legacy import deprecated
from anki.collection import OpChanges, SearchNode
from anki.decks import DeckId
from anki.models import NotetypeId
from anki.notes import Note, NoteFieldsCheckResult, NoteId
from anki.utils import html_to_text_line, is_mac
from aqt import AnkiQt, gui_hooks
from aqt.deckchooser import DeckChooser
from aqt.notetypechooser import NotetypeChooser
from aqt.operations.note import add_note
from aqt.qt import *
from aqt.sound import av_player
from aqt.utils import (
HelpPage,
add_close_shortcut,
ask_user_dialog,
askUser,
downArrow,
openHelp,
restoreGeom,
saveGeom,
shortcut,
showWarning,
tooltip,
tr,
)
class AddCards(QMainWindow):
def __init__(self, mw: AnkiQt) -> None:
super().__init__(None, Qt.WindowType.Window)
self._close_event_has_cleaned_up = False
self.mw = mw
self.col = mw.col
form = aqt.forms.addcards.Ui_Dialog()
form.setupUi(self)
self.form = form
self.setWindowTitle(tr.actions_add())
self.setMinimumHeight(300)
self.setMinimumWidth(400)
self.setup_choosers()
self.setupEditor()
add_close_shortcut(self)
self._load_new_note()
self.setupButtons()
self.history: list[NoteId] = []
self._last_added_note: Note | None = None
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
restoreGeom(self, "add")
gui_hooks.add_cards_did_init(self)
if not is_mac:
self.setMenuBar(None)
self.show()
def set_deck(self, deck_id: DeckId) -> None:
self.deck_chooser.selected_deck_id = deck_id
def set_note_type(self, note_type_id: NotetypeId) -> None:
self.notetype_chooser.selected_notetype_id = note_type_id
def set_note(self, note: Note, deck_id: DeckId | None = None) -> None:
"""Set tags, field contents and notetype according to `note`. Deck is set
to `deck_id` or the deck last used with the notetype.
"""
self.notetype_chooser.selected_notetype_id = note.mid
if deck_id or (deck_id := self.col.default_deck_for_notetype(note.mid)):
self.deck_chooser.selected_deck_id = deck_id
new_note = self._new_note()
new_note.fields = note.fields[:]
new_note.tags = note.tags[:]
self.editor.orig_note_id = note.id
self.setAndFocusNote(new_note)
def setupEditor(self) -> None:
self.editor = aqt.editor.Editor(
self.mw,
self.form.fieldsArea,
self,
editor_mode=aqt.editor.EditorMode.ADD_CARDS,
)
def setup_choosers(self) -> None:
defaults = self.col.defaults_for_adding(
current_review_card=self.mw.reviewer.card
)
self.notetype_chooser = NotetypeChooser(
mw=self.mw,
widget=self.form.modelArea,
starting_notetype_id=NotetypeId(defaults.notetype_id),
on_button_activated=self.show_notetype_selector,
on_notetype_changed=self.on_notetype_change,
)
self.deck_chooser = DeckChooser(
self.mw,
self.form.deckArea,
starting_deck_id=DeckId(defaults.deck_id),
on_deck_changed=self.on_deck_changed,
)
def reopen(self, mw: AnkiQt) -> None:
if not self.editor.fieldsAreBlank():
return
defaults = self.col.defaults_for_adding(
current_review_card=self.mw.reviewer.card
)
self.set_note_type(NotetypeId(defaults.notetype_id))
self.set_deck(DeckId(defaults.deck_id))
def helpRequested(self) -> None:
openHelp(HelpPage.ADDING_CARD_AND_NOTE)
def setupButtons(self) -> None:
bb = self.form.buttonBox
ar = QDialogButtonBox.ButtonRole.ActionRole
# add
self.addButton = bb.addButton(tr.actions_add(), ar)
qconnect(self.addButton.clicked, self.add_current_note)
self.addButton.setShortcut(QKeySequence("Ctrl+Return"))
# qt5.14+ doesn't handle numpad enter on Windows
self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self)
qconnect(self.compat_add_shorcut.activated, self.addButton.click)
self.addButton.setToolTip(shortcut(tr.adding_add_shortcut_ctrlandenter()))
# close
self.closeButton = QPushButton(tr.actions_close())
self.closeButton.setAutoDefault(False)
bb.addButton(self.closeButton, QDialogButtonBox.ButtonRole.RejectRole)
qconnect(self.closeButton.clicked, self.close)
# help
self.helpButton = QPushButton(tr.actions_help(), clicked=self.helpRequested) # type: ignore
self.helpButton.setAutoDefault(False)
bb.addButton(self.helpButton, QDialogButtonBox.ButtonRole.HelpRole)
# history
b = bb.addButton(f"{tr.adding_history()} {downArrow()}", ar)
if is_mac:
sc = "Ctrl+Shift+H"
else:
sc = "Ctrl+H"
b.setShortcut(QKeySequence(sc))
b.setToolTip(tr.adding_shortcut(val=shortcut(sc)))
qconnect(b.clicked, self.onHistory)
b.setEnabled(False)
self.historyButton = b
def setAndFocusNote(self, note: Note) -> None:
self.editor.set_note(note, focusTo=0)
def show_notetype_selector(self) -> None:
self.editor.call_after_note_saved(self.notetype_chooser.choose_notetype)
def on_deck_changed(self, deck_id: int) -> None:
gui_hooks.add_cards_did_change_deck(deck_id)
def on_notetype_change(
self, notetype_id: NotetypeId, update_deck: bool = True
) -> None:
# need to adjust current deck?
if update_deck:
if deck_id := self.col.default_deck_for_notetype(notetype_id):
self.deck_chooser.selected_deck_id = deck_id
# only used for detecting changed sticky fields on close
self._last_added_note = None
# copy fields into new note with the new notetype
old_note = self.editor.note
new_note = self._new_note()
if old_note:
old_field_names = list(old_note.keys())
new_field_names = list(new_note.keys())
copied_field_names = set()
for f in new_note.note_type()["flds"]:
field_name = f["name"]
# copy identical non-empty fields
if field_name in old_field_names and old_note[field_name]:
new_note[field_name] = old_note[field_name]
copied_field_names.add(field_name)
new_idx = 0
for old_idx, old_field_value in enumerate(old_field_names):
# skip previously copied identical fields in new note
while (
new_idx < len(new_field_names)
and new_field_names[new_idx] in copied_field_names
):
new_idx += 1
if new_idx >= len(new_field_names):
break
# copy non-empty old fields
if (
old_field_value not in copied_field_names
and old_note.fields[old_idx]
):
new_note.fields[new_idx] = old_note.fields[old_idx]
new_idx += 1
new_note.tags = old_note.tags
# and update editor state
self.editor.note = new_note
self.editor.loadNote(
focusTo=min(self.editor.last_field_index or 0, len(new_note.fields) - 1)
)
gui_hooks.addcards_did_change_note_type(
self, old_note.note_type(), new_note.note_type()
)
def _load_new_note(self, sticky_fields_from: Note | None = None) -> None:
note = self._new_note()
if old_note := sticky_fields_from:
flds = note.note_type()["flds"]
# copy fields from old note
if old_note:
for n in range(min(len(note.fields), len(old_note.fields))):
if flds[n]["sticky"]:
note.fields[n] = old_note.fields[n]
# and tags
note.tags = old_note.tags
self.setAndFocusNote(note)
def on_operation_did_execute(
self, changes: OpChanges, handler: object | None
) -> None:
if (changes.notetype or changes.deck) and handler is not self.editor:
self.on_notetype_change(
NotetypeId(
self.col.defaults_for_adding(
current_review_card=self.mw.reviewer.card
).notetype_id
),
update_deck=False,
)
def _new_note(self) -> Note:
return self.col.new_note(
self.col.models.get(self.notetype_chooser.selected_notetype_id)
)
def addHistory(self, note: Note) -> None:
self.history.insert(0, note.id)
self.history = self.history[:15]
self.historyButton.setEnabled(True)
def onHistory(self) -> None:
m = QMenu(self)
for nid in self.history:
if self.col.find_notes(self.col.build_search_string(SearchNode(nid=nid))):
note = self.col.get_note(nid)
fields = note.fields
txt = html_to_text_line(", ".join(fields))
if len(txt) > 30:
txt = f"{txt[:30]}..."
line = tr.adding_edit(val=txt)
line = gui_hooks.addcards_will_add_history_entry(line, note)
line = line.replace("&", "&&")
# In qt action "&i" means "underline i, trigger this line when i is pressed".
# except for "&&" which is replaced by a single "&"
a = m.addAction(line)
qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid))
else:
a = m.addAction(tr.adding_note_deleted())
a.setEnabled(False)
gui_hooks.add_cards_will_show_history_menu(self, m)
m.exec(self.historyButton.mapToGlobal(QPoint(0, 0)))
def editHistory(self, nid: NoteId) -> None:
aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),))
def add_current_note(self) -> None:
if self.editor.current_notetype_is_image_occlusion():
self.editor.update_occlusions_field()
self.editor.call_after_note_saved(self._add_current_note)
self.editor.reset_image_occlusion()
else:
self.editor.call_after_note_saved(self._add_current_note)
def _add_current_note(self) -> None:
note = self.editor.note
if not self._note_can_be_added(note):
return
target_deck_id = self.deck_chooser.selected_deck_id
def on_success(changes: OpChanges) -> None:
# only used for detecting changed sticky fields on close
self._last_added_note = note
self.addHistory(note)
tooltip(tr.adding_added(), period=500)
av_player.stop_and_clear_queue()
self._load_new_note(sticky_fields_from=note)
gui_hooks.add_cards_did_add_note(note)
add_note(parent=self, note=note, target_deck_id=target_deck_id).success(
on_success
).run_in_background()
def _note_can_be_added(self, note: Note) -> bool:
result = note.fields_check()
# no problem, duplicate, and confirmed cloze cases
problem = None
if result == NoteFieldsCheckResult.EMPTY:
if self.editor.current_notetype_is_image_occlusion():
problem = tr.notetypes_no_occlusion_created2()
else:
problem = tr.adding_the_first_field_is_empty()
elif result == NoteFieldsCheckResult.MISSING_CLOZE:
if not askUser(tr.adding_you_have_a_cloze_deletion_note()):
return False
elif result == NoteFieldsCheckResult.NOTETYPE_NOT_CLOZE:
problem = tr.adding_cloze_outside_cloze_notetype()
elif result == NoteFieldsCheckResult.FIELD_NOT_CLOZE:
problem = tr.adding_cloze_outside_cloze_field()
# filter problem through add-ons
problem = gui_hooks.add_cards_will_add_note(problem, note)
if problem is not None:
showWarning(problem, help=HelpPage.ADDING_CARD_AND_NOTE)
return False
optional_problems: list[str] = []
gui_hooks.add_cards_might_add_note(optional_problems, note)
if not all(askUser(op) for op in optional_problems):
return False
return True
def keyPressEvent(self, evt: QKeyEvent) -> None:
if evt.key() == Qt.Key.Key_Escape:
self.close()
else:
super().keyPressEvent(evt)
def closeEvent(self, evt: QCloseEvent) -> None:
if self._close_event_has_cleaned_up:
evt.accept()
return
self.ifCanClose(self._close)
evt.ignore()
def _close(self) -> None:
self.editor.cleanup()
self.notetype_chooser.cleanup()
self.deck_chooser.cleanup()
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
self.mw.maybeReset()
saveGeom(self, "add")
aqt.dialogs.markClosed("AddCards")
self._close_event_has_cleaned_up = True
self.mw.deferred_delete_and_garbage_collect(self)
self.close()
def ifCanClose(self, onOk: Callable) -> None:
def callback(choice: int) -> None:
if choice == 0:
onOk()
def afterSave() -> None:
if self.editor.fieldsAreBlank(self._last_added_note):
return onOk()
ask_user_dialog(
tr.adding_discard_current_input(),
callback=callback,
buttons=[
QMessageBox.StandardButton.Discard,
(tr.adding_keep_editing(), QMessageBox.ButtonRole.RejectRole),
],
)
self.editor.call_after_note_saved(afterSave)
def closeWithCallback(self, cb: Callable[[], None]) -> None:
def doClose() -> None:
self._close()
cb()
self.ifCanClose(doClose)
# legacy aliases
@property
def deckChooser(self) -> DeckChooser:
if getattr(self, "form", None):
# show this warning only after Qt form has been initialized,
# or PyQt's introspection triggers it
print("deckChooser is deprecated; use deck_chooser instead")
return self.deck_chooser
addCards = add_current_note
_addCards = _add_current_note
onModelChange = on_notetype_change
@deprecated(info="obsolete")
def addNote(self, note: Note) -> None:
pass
@deprecated(info="does nothing; will go away")
def removeTempNote(self, note: Note) -> None:
pass

View file

@ -27,7 +27,6 @@ from anki.scheduler.base import ScheduleCardsAsNew
from anki.tags import MARKED_TAG
from anki.utils import is_mac
from aqt import AnkiQt, gui_hooks
from aqt.editor import Editor, EditorWebView
from aqt.errors import show_exception
from aqt.exporting import ExportDialog as LegacyExportDialog
from aqt.import_export.exporting import ExportDialog
@ -77,7 +76,7 @@ from aqt.utils import (
tr,
)
from ..addcards import AddCards
from ..addcards import NewAddCards as AddCards
from ..changenotetype import change_notetype_dialog
from .card_info import BrowserCardInfo
from .find_and_replace import FindAndReplaceDialog
@ -111,7 +110,7 @@ class MockModel:
class Browser(QMainWindow):
mw: AnkiQt
col: Collection
editor: Editor | None
editor: aqt.editor.NewEditor | None
table: Table
def __init__(
@ -267,7 +266,7 @@ class Browser(QMainWindow):
return None
def add_card(self, deck_id: DeckId):
add_cards = cast(AddCards, aqt.dialogs.open("AddCards", self.mw))
add_cards = cast(AddCards, aqt.dialogs.open("NewAddCards", self.mw))
add_cards.set_deck(deck_id)
if note_type_id := self.get_active_note_type_id():
@ -392,7 +391,7 @@ class Browser(QMainWindow):
add_ellipsis_to_action_label(f.action_forget)
add_ellipsis_to_action_label(f.action_grade_now)
def _editor_web_view(self) -> EditorWebView:
def _editor_web_view(self) -> aqt.editor.NewEditorWebView:
assert self.editor is not None
editor_web_view = self.editor.web
assert editor_web_view is not None
@ -592,12 +591,14 @@ class Browser(QMainWindow):
def setupEditor(self) -> None:
QShortcut(QKeySequence("Ctrl+Shift+P"), self, self.onTogglePreview)
def add_preview_button(editor: Editor) -> None:
def add_preview_button(
editor: aqt.editor.Editor | aqt.editor.NewEditor,
) -> None:
editor._links["preview"] = lambda _editor: self.onTogglePreview()
gui_hooks.editor_did_init.remove(add_preview_button)
gui_hooks.editor_did_init.append(add_preview_button)
self.editor = aqt.editor.Editor(
self.editor = aqt.editor.NewEditor(
self.mw,
self.form.fieldsArea,
self,
@ -806,7 +807,7 @@ class Browser(QMainWindow):
assert current_card is not None
deck_id = current_card.current_deck_id()
aqt.dialogs.open("AddCards", self.mw).set_note(note, deck_id)
aqt.dialogs.open("NewAddCards", self.mw).set_note(note, deck_id)
@no_arg_trigger
@skip_if_selection_is_empty
@ -1264,3 +1265,4 @@ class Browser(QMainWindow):
line_edit = self.form.searchEdit.lineEdit()
assert line_edit is not None
return line_edit
return line_edit

View file

@ -7,11 +7,12 @@ from collections.abc import Callable
import aqt.editor
from anki.collection import OpChanges
from aqt import gui_hooks
from aqt.editcurrent_legacy import *
from aqt.qt import *
from aqt.utils import add_close_shortcut, restoreGeom, saveGeom, tr
class EditCurrent(QMainWindow):
class NewEditCurrent(QMainWindow):
def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(None, Qt.WindowType.Window)
self.mw = mw
@ -22,7 +23,7 @@ class EditCurrent(QMainWindow):
self.setMinimumWidth(250)
if not is_mac:
self.setMenuBar(None)
self.editor = aqt.editor.Editor(
self.editor = aqt.editor.NewEditor(
self.mw,
self.form.fieldsArea,
self,
@ -46,7 +47,7 @@ class EditCurrent(QMainWindow):
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
self.editor.cleanup()
saveGeom(self, "editcurrent")
aqt.dialogs.markClosed("EditCurrent")
aqt.dialogs.markClosed("NewEditCurrent")
def reopen(self, mw: aqt.AnkiQt) -> None:
if card := self.mw.reviewer.card:

View file

@ -0,0 +1,94 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from collections.abc import Callable
import aqt.editor
from anki.collection import OpChanges
from anki.errors import NotFoundError
from aqt import gui_hooks
from aqt.qt import *
from aqt.utils import add_close_shortcut, restoreGeom, saveGeom, tr
class EditCurrent(QMainWindow):
def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(None, Qt.WindowType.Window)
self.mw = mw
self.form = aqt.forms.editcurrent.Ui_Dialog()
self.form.setupUi(self)
self.setWindowTitle(tr.editing_edit_current())
self.setMinimumHeight(400)
self.setMinimumWidth(250)
if not is_mac:
self.setMenuBar(None)
self.editor = aqt.editor.Editor(
self.mw,
self.form.fieldsArea,
self,
editor_mode=aqt.editor.EditorMode.EDIT_CURRENT,
)
assert self.mw.reviewer.card is not None
self.editor.card = self.mw.reviewer.card
self.editor.set_note(self.mw.reviewer.card.note(), focusTo=0)
restoreGeom(self, "editcurrent")
self.buttonbox = QDialogButtonBox(Qt.Orientation.Horizontal)
self.form.verticalLayout.insertWidget(1, self.buttonbox)
self.buttonbox.addButton(QDialogButtonBox.StandardButton.Close)
qconnect(self.buttonbox.rejected, self.close)
close_button = self.buttonbox.button(QDialogButtonBox.StandardButton.Close)
assert close_button is not None
close_button.setShortcut(QKeySequence("Ctrl+Return"))
add_close_shortcut(self)
# qt5.14+ doesn't handle numpad enter on Windows
self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self)
qconnect(self.compat_add_shorcut.activated, close_button.click)
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
self.show()
def on_operation_did_execute(
self, changes: OpChanges, handler: object | None
) -> None:
if changes.note_text and handler is not self.editor:
# reload note
note = self.editor.note
try:
assert note is not None
note.load()
except NotFoundError:
# note's been deleted
self.cleanup()
self.close()
return
self.editor.set_note(note)
def cleanup(self) -> None:
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
self.editor.cleanup()
saveGeom(self, "editcurrent")
aqt.dialogs.markClosed("EditCurrent")
def reopen(self, mw: aqt.AnkiQt) -> None:
if card := self.mw.reviewer.card:
self.editor.card = card
self.editor.set_note(card.note())
def closeEvent(self, evt: QCloseEvent | None) -> None:
self.editor.call_after_note_saved(self.cleanup)
def _saveAndClose(self) -> None:
self.cleanup()
self.mw.deferred_delete_and_garbage_collect(self)
self.close()
def closeWithCallback(self, onsuccess: Callable[[], None]) -> None:
def callback() -> None:
self._saveAndClose()
onsuccess()
self.editor.call_after_note_saved(callback)
onReset = on_operation_did_execute
onReset = on_operation_did_execute

View file

@ -10,7 +10,6 @@ import mimetypes
import os
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum
from random import randrange
from typing import Any
@ -21,58 +20,16 @@ from anki.models import NotetypeId
from anki.notes import Note, NoteId
from anki.utils import is_win
from aqt import AnkiQt, gui_hooks
from aqt.editor_legacy import *
from aqt.qt import *
from aqt.sound import av_player
from aqt.utils import shortcut, showWarning
from aqt.webview import AnkiWebView, AnkiWebViewKind
pics = ("jpg", "jpeg", "png", "gif", "svg", "webp", "ico", "avif")
audio = (
"3gp",
"aac",
"avi",
"flac",
"flv",
"m4a",
"mkv",
"mov",
"mp3",
"mp4",
"mpeg",
"mpg",
"oga",
"ogg",
"ogv",
"ogx",
"opus",
"spx",
"swf",
"wav",
"webm",
)
class EditorMode(Enum):
ADD_CARDS = 0
EDIT_CURRENT = 1
BROWSER = 2
class EditorState(Enum):
"""
Current input state of the editing UI.
"""
INITIAL = -1
FIELDS = 0
IO_PICKER = 1
IO_MASKS = 2
IO_FIELDS = 3
def on_editor_ready(func: Callable) -> Callable:
@functools.wraps(func)
def decorated(self: Editor, *args: Any, **kwargs: Any) -> None:
def decorated(self: NewEditor, *args: Any, **kwargs: Any) -> None:
if self._ready:
func(self, *args, **kwargs)
else:
@ -96,7 +53,7 @@ class NoteInfo:
self.mid = NotetypeId(int(self.mid))
class Editor:
class NewEditor:
"""The screen that embeds an editing widget should listen for changes via
the `operation_did_execute` hook, and call set_note() when the editor needs
redrawing.
@ -152,7 +109,7 @@ class Editor:
self.outerLayout = l
def add_webview(self) -> None:
self.web = EditorWebView(self.widget, self)
self.web = NewEditorWebView(self.widget, self)
self.web.set_bridge_command(self.onBridgeCmd, self)
self.web.hide_while_preserving_layout()
self.outerLayout.addWidget(self.web, 1)
@ -213,7 +170,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
self,
icon: str | None,
cmd: str,
func: Callable[[Editor], None],
func: Callable[[NewEditor], None],
tip: str = "",
label: str = "",
id: str | None = None,
@ -224,7 +181,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
) -> str:
"""Assign func to bridge cmd, register shortcut, return button"""
def wrapped_func(editor: Editor) -> None:
def wrapped_func(editor: NewEditor) -> None:
self.call_after_note_saved(functools.partial(func, editor), keepFocus=True)
self._links[cmd] = wrapped_func
@ -553,11 +510,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def _init_links(self) -> None:
self._links: dict[str, Callable] = dict(
fields=Editor.onFields,
cards=Editor.onCardLayout,
paste=Editor.onPaste,
cut=Editor.onCut,
copy=Editor.onCopy,
fields=NewEditor.onFields,
cards=NewEditor.onCardLayout,
paste=NewEditor.onPaste,
cut=NewEditor.onCut,
copy=NewEditor.onCopy,
)
def get_note_info(self, on_done: Callable[[NoteInfo], None]) -> None:
@ -571,8 +528,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
######################################################################
class EditorWebView(AnkiWebView):
def __init__(self, parent: QWidget, editor: Editor) -> None:
class NewEditorWebView(AnkiWebView):
def __init__(self, parent: QWidget, editor: NewEditor) -> None:
AnkiWebView.__init__(self, kind=AnkiWebViewKind.EDITOR)
self.editor = editor
self.setAcceptDrops(True)
@ -592,3 +549,4 @@ class EditorWebView(AnkiWebView):
def onPaste(self) -> None:
self.triggerPageAction(QWebEnginePage.WebAction.Paste)
self.triggerPageAction(QWebEnginePage.WebAction.Paste)

1790
qt/aqt/editor_legacy.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1280,14 +1280,20 @@ title="{}" {}>{}</button>""".format(
# Other menu operations
##########################################################################
def _open_new_or_legacy_dialog(self, name: str, *args: Any, **kwargs: Any) -> None:
want_old = KeyboardModifiersPressed().shift
if not want_old:
name = f"New{name}"
aqt.dialogs.open(name, self, *args, **kwargs)
def onAddCard(self) -> None:
aqt.dialogs.open("AddCards", self)
self._open_new_or_legacy_dialog("AddCards")
def onBrowse(self) -> None:
aqt.dialogs.open("Browser", self, card=self.reviewer.card)
def onEditCurrent(self) -> None:
aqt.dialogs.open("EditCurrent", self)
self._open_new_or_legacy_dialog("EditCurrent")
def onOverview(self) -> None:
self.moveToState("overview")
@ -1296,11 +1302,7 @@ title="{}" {}>{}</button>""".format(
deck = self._selectedDeck()
if not deck:
return
want_old = KeyboardModifiersPressed().shift
if want_old:
aqt.dialogs.open("DeckStats", self)
else:
aqt.dialogs.open("NewDeckStats", self)
self._open_new_or_legacy_dialog("DeckStats", self)
def onPrefs(self) -> None:
aqt.dialogs.open("Preferences", self)

View file

@ -608,10 +608,10 @@ def editor_op_changes_request(endpoint: str) -> bytes:
response.ParseFromString(output)
def handle_on_main() -> None:
from aqt.editor import Editor
from aqt.editor import NewEditor
handler = aqt.mw.app.activeWindow()
if handler and isinstance(getattr(handler, "editor", None), Editor):
if handler and isinstance(getattr(handler, "editor", None), NewEditor):
handler = handler.editor # type: ignore
on_op_finished(aqt.mw, response, handler)
@ -808,10 +808,10 @@ def close_add_cards() -> bytes:
req.ParseFromString(request.data)
def handle_on_main() -> None:
from aqt.addcards import AddCards
from aqt.addcards import NewAddCards
window = aqt.mw.app.activeWindow()
if isinstance(window, AddCards):
if isinstance(window, NewAddCards):
window._close_if_user_wants_to_discard_changes(req.val)
aqt.mw.taskman.run_on_main(lambda: QTimer.singleShot(0, handle_on_main))
@ -820,10 +820,10 @@ def close_add_cards() -> bytes:
def close_edit_current() -> bytes:
def handle_on_main() -> None:
from aqt.editcurrent import EditCurrent
from aqt.editcurrent import NewEditCurrent
window = aqt.mw.app.activeWindow()
if isinstance(window, EditCurrent):
if isinstance(window, NewEditCurrent):
window.close()
aqt.mw.taskman.run_on_main(lambda: QTimer.singleShot(0, handle_on_main))
@ -1070,3 +1070,5 @@ def _extract_dynamic_get_request(path: str) -> DynamicRequest | None:
return legacy_page_data
else:
return None
return None
return None

View file

@ -1008,12 +1008,15 @@ hooks = [
###################
Hook(
name="add_cards_will_show_history_menu",
args=["addcards: aqt.addcards.AddCards", "menu: QMenu"],
args=[
"addcards: aqt.addcards.AddCards | aqt.addcards.NewAddCards",
"menu: QMenu",
],
legacy_hook="AddCards.onHistory",
),
Hook(
name="add_cards_did_init",
args=["addcards: aqt.addcards.AddCards"],
args=["addcards: aqt.addcards.AddCards | aqt.addcards.NewAddCards"],
),
Hook(
name="add_cards_did_add_note",
@ -1068,7 +1071,7 @@ hooks = [
Hook(
name="addcards_did_change_note_type",
args=[
"addcards: aqt.addcards.AddCards",
"addcards: aqt.addcards.AddCards | aqt.addcards.NewAddCards",
"old: anki.models.NoteType",
"new: anki.models.NoteType",
],
@ -1087,20 +1090,26 @@ hooks = [
###################
Hook(
name="editor_did_init_left_buttons",
args=["buttons: list[str]", "editor: aqt.editor.Editor"],
args=["buttons: list[str]", "editor: aqt.editor.Editor | aqt.editor.NewEditor"],
),
Hook(
name="editor_did_init_buttons",
args=["buttons: list[str]", "editor: aqt.editor.Editor"],
args=["buttons: list[str]", "editor: aqt.editor.Editor | aqt.editor.NewEditor"],
),
Hook(
name="editor_did_init_shortcuts",
args=["shortcuts: list[tuple]", "editor: aqt.editor.Editor"],
args=[
"shortcuts: list[tuple]",
"editor: aqt.editor.Editor | aqt.editor.NewEditor",
],
legacy_hook="setupEditorShortcuts",
),
Hook(
name="editor_will_show_context_menu",
args=["editor_webview: aqt.editor.EditorWebView", "menu: QMenu"],
args=[
"editor_webview: aqt.editor.EditorWebView | aqt.editor.NewEditorWebView",
"menu: QMenu",
],
legacy_hook="EditorWebView.contextMenuEvent",
),
Hook(
@ -1121,7 +1130,7 @@ hooks = [
),
Hook(
name="editor_did_load_note",
args=["editor: aqt.editor.Editor"],
args=["editor: aqt.editor.Editor | aqt.editor.NewEditor"],
legacy_hook="loadNote",
),
Hook(
@ -1131,7 +1140,7 @@ hooks = [
),
Hook(
name="editor_will_munge_html",
args=["txt: str", "editor: aqt.editor.Editor"],
args=["txt: str", "editor: aqt.editor.Editor | aqt.editor.NewEditor"],
return_type="str",
doc="""Allows manipulating the text that will be saved by the editor""",
),
@ -1143,15 +1152,21 @@ hooks = [
),
Hook(
name="editor_web_view_did_init",
args=["editor_web_view: aqt.editor.EditorWebView"],
args=[
"editor_web_view: aqt.editor.EditorWebView | aqt.editor.NewEditorWebView"
],
),
Hook(
name="editor_did_init",
args=["editor: aqt.editor.Editor"],
args=["editor: aqt.editor.Editor | aqt.editor.NewEditor"],
),
Hook(
name="editor_will_load_note",
args=["js: str", "note: anki.notes.Note", "editor: aqt.editor.Editor"],
args=[
"js: str",
"note: anki.notes.Note",
"editor: aqt.editor.Editor | aqt.editor.NewEditor",
],
return_type="str",
doc="""Allows changing the javascript commands to load note before
executing it and do change in the QT editor.""",
@ -1159,7 +1174,7 @@ hooks = [
Hook(
name="editor_did_paste",
args=[
"editor: aqt.editor.Editor",
"editor: aqt.editor.Editor | aqt.editor.NewEditor",
"html: str",
"internal: bool",
"extended: bool",
@ -1170,7 +1185,7 @@ hooks = [
name="editor_will_process_mime",
args=[
"mime: QMimeData",
"editor_web_view: aqt.editor.EditorWebView",
"editor_web_view: aqt.editor.EditorWebView | aqt.editor.NewEditorWebView",
"internal: bool",
"extended: bool",
"drop_event: bool",
@ -1194,7 +1209,7 @@ hooks = [
Hook(
name="editor_state_did_change",
args=[
"editor: aqt.editor.Editor",
"editor: aqt.editor.Editor | aqt.editor.NewEditor",
"new_state: aqt.editor.EditorState",
"old_state: aqt.editor.EditorState",
],
@ -1203,7 +1218,10 @@ hooks = [
),
Hook(
name="editor_mask_editor_did_load_image",
args=["editor: aqt.editor.Editor", "path_or_nid: str | anki.notes.NoteId"],
args=[
"editor: aqt.editor.Editor | aqt.editor.NewEditor",
"path_or_nid: str | anki.notes.NoteId",
],
doc="""Called when the image occlusion mask editor has completed
loading an image.