diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py index 28600545c..634e58752 100644 --- a/qt/aqt/deckoptions.py +++ b/qt/aqt/deckoptions.py @@ -14,6 +14,7 @@ from aqt.qt import * from aqt.utils import ( KeyboardModifiersPressed, addCloseShortcut, + ask_user_dialog, disable_help_button, restoreGeom, saveGeom, @@ -33,6 +34,8 @@ class DeckOptionsDialog(QDialog): self.mw = mw self._deck = deck self._setup_ui() + self._close_event_has_cleaned_up = False + self._ready = False def _setup_ui(self) -> None: self.setWindowModality(Qt.WindowModality.ApplicationModal) @@ -57,8 +60,44 @@ class DeckOptionsDialog(QDialog): def _on_bridge_cmd(self, cmd: str) -> None: if cmd == "deckOptionsReady": + self.ready = True gui_hooks.deck_options_did_load(self) + def closeEvent(self, evt: QCloseEvent) -> None: + if self._close_event_has_cleaned_up: + evt.accept() + return + evt.ignore() + self.if_can_close() + + def _close(self): + """Close. Ensure the closeEvent is not ignored.""" + self._close_event_has_cleaned_up = True + self.close() + + def if_can_close(self): + """Close if there was no modification. Otherwise ask for confirmation first.""" + + def callbackWithUserChoice(choice: int): + if choice == 0: + # The user accepted to discard current input. + self._close() + + def if_can_close_callback_with_data_information(has_modified_dataData: bool): + if has_modified_dataData: + ask_user_dialog( + tr.card_templates_discard_changes(), + callback=callbackWithUserChoice, + buttons=[ + QMessageBox.StandardButton.Discard, + (tr.adding_keep_editing(), QMessageBox.ButtonRole.RejectRole), + ], + ) + else: + self._close() + + self.has_modified_data(if_can_close_callback_with_data_information) + def reject(self) -> None: self.mw.col.set_wants_abort() self.web.cleanup() @@ -66,6 +105,15 @@ class DeckOptionsDialog(QDialog): saveGeom(self, self.TITLE) QDialog.reject(self) + def has_modified_data(self, callback: Callable[[bool], None]): + """Calls `callback` with the information of whether any deck options are modified.""" + if self.ready: + self.web.evalWithCallback( + "anki.deckOptionsPendingChanges()", callback + ) + else: + callback(False) + def confirm_deck_then_display_options(active_card: Card | None = None) -> None: decks = [aqt.mw.col.decks.current()] diff --git a/ts/routes/deck-options/[deckId]/+page.svelte b/ts/routes/deck-options/[deckId]/+page.svelte index 11af70f71..e3a5f6432 100644 --- a/ts/routes/deck-options/[deckId]/+page.svelte +++ b/ts/routes/deck-options/[deckId]/+page.svelte @@ -11,6 +11,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let data: PageData; let page: DeckOptionsPage; + globalThis.anki ||= {}; + globalThis.anki.deckOptionsPendingChanges = () => { + return data.state.isModified(); + }; onMount(() => { globalThis.$deckOptions = new Promise((resolve, _reject) => { resolve(page); diff --git a/ts/routes/deck-options/lib.ts b/ts/routes/deck-options/lib.ts index d95b1e1aa..5df94ecfb 100644 --- a/ts/routes/deck-options/lib.ts +++ b/ts/routes/deck-options/lib.ts @@ -292,6 +292,10 @@ export class DeckOptionsState { }); return list; } + + isModified(): boolean { + return this.removedConfigs.length > 0 || this.modifiedConfigs.size > 0; + } } function bytesToObject(bytes: Uint8Array): Record {