mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Add back legacy code as separate screens
This commit is contained in:
parent
c91f943f29
commit
7dac2fc4ff
11 changed files with 2383 additions and 99 deletions
|
@ -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],
|
||||
|
|
|
@ -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
414
qt/aqt/addcards_legacy.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
94
qt/aqt/editcurrent_legacy.py
Normal file
94
qt/aqt/editcurrent_legacy.py
Normal 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
|
|
@ -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
1790
qt/aqt/editor_legacy.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in a new issue