diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index d4eadfaee..ce4d4925c 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -677,7 +677,7 @@ where id in %s""" return did = self.col.decks.id(ret.name) - set_card_deck(mw=self.mw, card_ids=cids, deck_id=did) + set_card_deck(parent=self, card_ids=cids, deck_id=did).run_in_background() # legacy @@ -696,7 +696,7 @@ where id in %s""" return add_tags_to_notes( parent=self, note_ids=self.selected_notes(), space_separated_tags=tags - ).run(handler=self) + ).run_in_background(initiator=self) @ensure_editor_saved_on_trigger def remove_tags_from_selected_notes(self, tags: Optional[str] = None) -> None: @@ -708,7 +708,7 @@ where id in %s""" remove_tags_from_notes( parent=self, note_ids=self.selected_notes(), space_separated_tags=tags - ).run(handler=self) + ).run_in_background(initiator=self) def _prompt_for_tags(self, prompt: str) -> Optional[str]: (tags, ok) = getTag(self, self.col, prompt) @@ -719,7 +719,7 @@ where id in %s""" @ensure_editor_saved_on_trigger def clear_unused_tags(self) -> None: - clear_unused_tags(parent=self).run() + clear_unused_tags(parent=self).run_in_background() addTags = add_tags_to_selected_notes deleteTags = remove_tags_from_selected_notes @@ -760,7 +760,9 @@ where id in %s""" if flag == self.card.user_flag(): flag = 0 - set_card_flag(mw=self.mw, card_ids=self.selected_cards(), flag=flag) + set_card_flag( + parent=self, card_ids=self.selected_cards(), flag=flag + ).run_in_background() def _update_flags_menu(self) -> None: flag = self.card and self.card.user_flag() @@ -859,7 +861,7 @@ where id in %s""" ###################################################################### def undo(self) -> None: - undo(mw=self.mw, parent=self) + undo(parent=self) def onUndoState(self, on: bool) -> None: self.form.actionUndo.setEnabled(on) diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 1d2f58ec5..74455ac4a 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -278,7 +278,9 @@ class DeckBrowser: if not new_name or new_name == deck.name: return else: - rename_deck(mw=self.mw, deck_id=did, new_name=new_name) + rename_deck( + parent=self.mw, deck_id=did, new_name=new_name + ).run_in_background() self.mw.query_op(lambda: self.mw.col.get_deck(did), success=prompt) @@ -293,18 +295,20 @@ class DeckBrowser: if node: node.collapsed = not node.collapsed set_deck_collapsed( - mw=self.mw, + parent=self.mw, deck_id=did, collapsed=node.collapsed, scope=DeckCollapseScope.REVIEWER, - ) + ).run_in_background() self._renderPage(reuse=True) def _handle_drag_and_drop(self, source: DeckId, target: DeckId) -> None: - reparent_decks(mw=self.mw, parent=self.mw, deck_ids=[source], new_parent=target) + reparent_decks( + parent=self.mw, deck_ids=[source], new_parent=target + ).run_in_background() def _delete(self, did: DeckId) -> None: - remove_decks(mw=self.mw, parent=self.mw, deck_ids=[did]) + remove_decks(parent=self.mw, deck_ids=[did]).run_in_background() # Top buttons ###################################################################### @@ -335,7 +339,8 @@ class DeckBrowser: openLink(f"{aqt.appShared}decks/") def _on_create(self) -> None: - add_deck_dialog(mw=self.mw, parent=self.mw) + if op := add_deck_dialog(parent=self.mw): + op.run_in_background() ###################################################################### diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 7dedba7dd..a977940e9 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -21,7 +21,6 @@ from typing import ( List, Literal, Optional, - Protocol, Sequence, TextIO, Tuple, @@ -40,15 +39,7 @@ import aqt.toolbar import aqt.webview from anki import hooks from anki._backend import RustBackend as _RustBackend -from anki.collection import ( - Collection, - Config, - OpChanges, - OpChangesAfterUndo, - OpChangesWithCount, - OpChangesWithId, - UndoStatus, -) +from anki.collection import Collection, Config, OpChanges, UndoStatus from anki.decks import DeckDict, DeckId from anki.hooks import runHook from anki.notes import NoteId @@ -1198,7 +1189,7 @@ title="%s" %s>%s""" % ( def undo(self) -> None: "Call collection_ops.py:undo() directly instead." - undo(mw=self, parent=self) + undo(parent=self) def update_undo_actions(self, status: Optional[UndoStatus] = None) -> None: """Update menu text and enable/disable menu item as appropriate. diff --git a/qt/aqt/operations/__init__.py b/qt/aqt/operations/__init__.py index eb9098ef2..360be305d 100644 --- a/qt/aqt/operations/__init__.py +++ b/qt/aqt/operations/__init__.py @@ -77,11 +77,13 @@ class CollectionOp(Generic[ResultWithChanges]): self._success = success return self - def failure(self, failure: Optional[CollectionOpFailureCallback]) -> CollectionOp[ResultWithChanges]: + def failure( + self, failure: Optional[CollectionOpFailureCallback] + ) -> CollectionOp[ResultWithChanges]: self._failure = failure return self - def run(self, *, handler: Optional[object] = None) -> None: + def run_in_background(self, *, initiator: Optional[object] = None) -> None: aqt.mw._increase_background_ops() def wrapped_op() -> ResultWithChanges: @@ -110,7 +112,7 @@ class CollectionOp(Generic[ResultWithChanges]): status = aqt.mw.col.undo_status() aqt.mw._update_undo_actions_for_status_and_save(status) # fire change hooks - self._fire_change_hooks_after_op_performed(result, handler) + self._fire_change_hooks_after_op_performed(result, initiator) aqt.mw.taskman.with_progress(wrapped_op, wrapped_done) diff --git a/qt/aqt/operations/card.py b/qt/aqt/operations/card.py index fd994c475..2e7f0ac19 100644 --- a/qt/aqt/operations/card.py +++ b/qt/aqt/operations/card.py @@ -3,28 +3,25 @@ from __future__ import annotations -from typing import Optional, Sequence +from typing import Sequence from anki.cards import CardId +from anki.collection import OpChanges from anki.decks import DeckId -from aqt import AnkiQt -from aqt.main import PerformOpOptionalSuccessCallback +from aqt.operations import CollectionOp +from aqt.qt import QWidget -def set_card_deck(*, mw: AnkiQt, card_ids: Sequence[CardId], deck_id: DeckId) -> None: - mw.perform_op(lambda: mw.col.set_deck(card_ids, deck_id)) +def set_card_deck( + *, parent: QWidget, card_ids: Sequence[CardId], deck_id: DeckId +) -> CollectionOp[OpChanges]: + return CollectionOp(parent, lambda col: col.set_deck(card_ids, deck_id)) def set_card_flag( *, - mw: AnkiQt, + parent: QWidget, card_ids: Sequence[CardId], flag: int, - handler: Optional[object] = None, - success: PerformOpOptionalSuccessCallback = None, -) -> None: - mw.perform_op( - lambda: mw.col.set_user_flag_for_cards(flag, card_ids), - handler=handler, - success=success, - ) +) -> CollectionOp[OpChanges]: + return CollectionOp(parent, lambda col: col.set_user_flag_for_cards(flag, card_ids)) diff --git a/qt/aqt/operations/collection.py b/qt/aqt/operations/collection.py index 95923c055..7d86f3cb7 100644 --- a/qt/aqt/operations/collection.py +++ b/qt/aqt/operations/collection.py @@ -3,34 +3,34 @@ from __future__ import annotations -import aqt -from anki.collection import LegacyCheckpoint, LegacyReviewUndo, OpChangesAfterUndo +from anki.collection import LegacyCheckpoint, LegacyReviewUndo from anki.errors import UndoEmpty from anki.types import assert_exhaustive from aqt import gui_hooks +from aqt.operations import CollectionOp from aqt.qt import QWidget from aqt.utils import showInfo, showWarning, tooltip, tr -def undo(*, mw: aqt.AnkiQt, parent: QWidget) -> None: +def undo(*, parent: QWidget) -> None: "Undo the last operation, and refresh the UI." - def on_success(out: OpChangesAfterUndo) -> None: - mw.update_undo_actions(out.new_status) - tooltip(tr.undo_action_undone(action=out.operation), parent=parent) - def on_failure(exc: Exception) -> None: if isinstance(exc, UndoEmpty): # backend has no undo, but there may be a checkpoint # or v1/v2 review waiting - _legacy_undo(mw=mw, parent=parent) + _legacy_undo(parent=parent) else: showWarning(str(exc), parent=parent) - mw.perform_op(mw.col.undo, success=on_success, failure=on_failure) + CollectionOp(parent, lambda col: col.undo()).success( + lambda out: tooltip(tr.undo_action_undone(action=out.operation), parent=parent) + ).failure(on_failure).run_in_background() -def _legacy_undo(*, mw: aqt.AnkiQt, parent: QWidget) -> None: +def _legacy_undo(*, parent: QWidget) -> None: + from aqt import mw + reviewing = mw.state == "review" just_refresh_reviewer = False diff --git a/qt/aqt/operations/deck.py b/qt/aqt/operations/deck.py index 4081626cf..d4d98adeb 100644 --- a/qt/aqt/operations/deck.py +++ b/qt/aqt/operations/deck.py @@ -5,81 +5,74 @@ from __future__ import annotations from typing import Optional, Sequence +from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithId from anki.decks import DeckCollapseScope, DeckId -from aqt import AnkiQt, QWidget -from aqt.main import PerformOpOptionalSuccessCallback +from aqt import QWidget +from aqt.operations import CollectionOp from aqt.utils import getOnlyText, tooltip, tr def remove_decks( *, - mw: AnkiQt, parent: QWidget, deck_ids: Sequence[DeckId], -) -> None: - mw.perform_op( - lambda: mw.col.decks.remove(deck_ids), - success=lambda out: tooltip( - tr.browsing_cards_deleted(count=out.count), parent=parent - ), +) -> CollectionOp[OpChangesWithCount]: + return CollectionOp(parent, lambda col: col.decks.remove(deck_ids)).success( + lambda out: tooltip(tr.browsing_cards_deleted(count=out.count), parent=parent) ) def reparent_decks( - *, mw: AnkiQt, parent: QWidget, deck_ids: Sequence[DeckId], new_parent: DeckId -) -> None: - mw.perform_op( - lambda: mw.col.decks.reparent(deck_ids=deck_ids, new_parent=new_parent), - success=lambda out: tooltip( + *, parent: QWidget, deck_ids: Sequence[DeckId], new_parent: DeckId +) -> CollectionOp[OpChangesWithCount]: + return CollectionOp( + parent, lambda col: col.decks.reparent(deck_ids=deck_ids, new_parent=new_parent) + ).success( + lambda out: tooltip( tr.browsing_reparented_decks(count=out.count), parent=parent - ), + ) ) def rename_deck( *, - mw: AnkiQt, + parent: QWidget, deck_id: DeckId, new_name: str, -) -> None: - mw.perform_op( - lambda: mw.col.decks.rename(deck_id, new_name), +) -> CollectionOp[OpChanges]: + return CollectionOp( + parent, + lambda col: col.decks.rename(deck_id, new_name), ) def add_deck_dialog( *, - mw: AnkiQt, parent: QWidget, default_text: str = "", - success: PerformOpOptionalSuccessCallback = None, -) -> None: +) -> Optional[CollectionOp[OpChangesWithId]]: if name := getOnlyText( tr.decks_new_deck_name(), default=default_text, parent=parent ).strip(): - add_deck(mw=mw, name=name, success=success) + return add_deck(parent=parent, name=name) + else: + return None -def add_deck( - *, mw: AnkiQt, name: str, success: PerformOpOptionalSuccessCallback = None -) -> None: - mw.perform_op( - lambda: mw.col.decks.add_normal_deck_with_name(name), - success=success, - ) +def add_deck(*, parent: QWidget, name: str) -> CollectionOp[OpChangesWithId]: + return CollectionOp(parent, lambda col: col.decks.add_normal_deck_with_name(name)) def set_deck_collapsed( *, - mw: AnkiQt, + parent: QWidget, deck_id: DeckId, collapsed: bool, scope: DeckCollapseScope.V, - handler: Optional[object] = None, -) -> None: - mw.perform_op( - lambda: mw.col.decks.set_collapsed( +) -> CollectionOp[OpChanges]: + return CollectionOp( + parent, + lambda col: col.decks.set_collapsed( deck_id=deck_id, collapsed=collapsed, scope=scope ), - handler=handler, ) diff --git a/qt/aqt/operations/tag.py b/qt/aqt/operations/tag.py index 6c2ea7611..cd33e2f17 100644 --- a/qt/aqt/operations/tag.py +++ b/qt/aqt/operations/tag.py @@ -5,9 +5,9 @@ from __future__ import annotations from typing import Sequence -from anki.collection import OpChangesWithCount +from anki.collection import OpChanges, OpChangesWithCount from anki.notes import NoteId -from aqt import AnkiQt, QWidget +from aqt import QWidget from aqt.operations import CollectionOp from aqt.utils import showInfo, tooltip, tr @@ -17,7 +17,7 @@ def add_tags_to_notes( parent: QWidget, note_ids: Sequence[NoteId], space_separated_tags: str, -) -> CollectionOp: +) -> CollectionOp[OpChangesWithCount]: return CollectionOp( parent, lambda col: col.tags.bulk_add(note_ids, space_separated_tags) ).success( @@ -30,7 +30,7 @@ def remove_tags_from_notes( parent: QWidget, note_ids: Sequence[NoteId], space_separated_tags: str, -) -> CollectionOp: +) -> CollectionOp[OpChangesWithCount]: return CollectionOp( parent, lambda col: col.tags.bulk_remove(note_ids, space_separated_tags) ).success( @@ -38,7 +38,7 @@ def remove_tags_from_notes( ) -def clear_unused_tags(*, parent: QWidget) -> CollectionOp: +def clear_unused_tags(*, parent: QWidget) -> CollectionOp[OpChangesWithCount]: return CollectionOp(parent, lambda col: col.tags.clear_unused_tags()).success( lambda out: tooltip( tr.browsing_removed_unused_tags_count(count=out.count), parent=parent @@ -51,7 +51,7 @@ def rename_tag( parent: QWidget, current_name: str, new_name: str, -) -> CollectionOp: +) -> CollectionOp[OpChangesWithCount]: def success(out: OpChangesWithCount) -> None: if out.count: tooltip(tr.browsing_notes_updated(count=out.count), parent=parent) @@ -66,7 +66,7 @@ def rename_tag( def remove_tags_from_all_notes( *, parent: QWidget, space_separated_tags: str -) -> CollectionOp: +) -> CollectionOp[OpChangesWithCount]: return CollectionOp( parent, lambda col: col.tags.remove(space_separated_tags=space_separated_tags) ).success( @@ -76,7 +76,7 @@ def remove_tags_from_all_notes( def reparent_tags( *, parent: QWidget, tags: Sequence[str], new_parent: str -) -> CollectionOp: +) -> CollectionOp[OpChangesWithCount]: return CollectionOp( parent, lambda col: col.tags.reparent(tags=tags, new_parent=new_parent) ).success( @@ -84,7 +84,9 @@ def reparent_tags( ) -def set_tag_collapsed(*, parent: QWidget, tag: str, collapsed: bool) -> CollectionOp: +def set_tag_collapsed( + *, parent: QWidget, tag: str, collapsed: bool +) -> CollectionOp[OpChanges]: return CollectionOp( parent, lambda col: col.tags.set_collapsed(tag=tag, collapsed=collapsed) ) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index cebbf24a3..0e3bf90c8 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -835,13 +835,9 @@ time = %(time)d; else: flag = desired_flag - set_card_flag( - mw=self.mw, - card_ids=[self.card.id], - flag=flag, - handler=self, - success=redraw_flag, - ) + set_card_flag(parent=self.mw, card_ids=[self.card.id], flag=flag).success( + redraw_flag + ).run_in_background(initiator=self) def toggle_mark_on_current_note(self) -> None: def redraw_mark(out: OpChangesWithCount) -> None: @@ -852,13 +848,13 @@ time = %(time)d; if note.has_tag(MARKED_TAG): remove_tags_from_notes( parent=self.mw, note_ids=[note.id], space_separated_tags=MARKED_TAG - ).success(redraw_mark).run(handler=self) + ).success(redraw_mark).run_in_background(initiator=self) else: add_tags_to_notes( parent=self.mw, note_ids=[note.id], space_separated_tags=MARKED_TAG, - ).success(redraw_mark).run(handler=self) + ).success(redraw_mark).run_in_background(initiator=self) def on_set_due(self) -> None: if self.mw.state != "review" or not self.card: diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index 77cf27a75..2a9c78bea 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -631,8 +631,8 @@ class SidebarTreeView(QTreeView): new_parent = DeckId(target.id) reparent_decks( - mw=self.mw, parent=self.browser, deck_ids=deck_ids, new_parent=new_parent - ) + parent=self.browser, deck_ids=deck_ids, new_parent=new_parent + ).run_in_background() return True @@ -652,7 +652,9 @@ class SidebarTreeView(QTreeView): else: new_parent = target.full_name - reparent_tags(parent=self.browser, tags=tags, new_parent=new_parent).run() + reparent_tags( + parent=self.browser, tags=tags, new_parent=new_parent + ).run_in_background() return True @@ -948,7 +950,7 @@ class SidebarTreeView(QTreeView): full_name = head + node.name return lambda expanded: set_tag_collapsed( parent=self, tag=full_name, collapsed=not expanded - ).run() + ).run_in_background() for node in nodes: item = SidebarItem( @@ -993,11 +995,12 @@ class SidebarTreeView(QTreeView): ) -> None: def toggle_expand(node: DeckTreeNode) -> Callable[[bool], None]: return lambda expanded: set_deck_collapsed( - mw=self.mw, + parent=self, deck_id=DeckId(node.deck_id), collapsed=not expanded, scope=DeckCollapseScope.BROWSER, - handler=self, + ).run_in_background( + initiator=self, ) for node in nodes: @@ -1192,15 +1195,15 @@ class SidebarTreeView(QTreeView): return rename_deck( - mw=self.mw, + parent=self, deck_id=deck_id, new_name=full_name, - ) + ).run_in_background() self.mw.query_op(lambda: self.mw.col.get_deck(deck_id), success=after_fetch) def delete_decks(self, _item: SidebarItem) -> None: - remove_decks(mw=self.mw, parent=self.browser, deck_ids=self._selected_decks()) + remove_decks(parent=self, deck_ids=self._selected_decks()).run_in_background() # Tags ########################### @@ -1209,7 +1212,9 @@ class SidebarTreeView(QTreeView): tags = self.mw.col.tags.join(self._selected_tags()) item.name = "..." - remove_tags_from_all_notes(parent=self.browser, space_separated_tags=tags).run() + remove_tags_from_all_notes( + parent=self.browser, space_separated_tags=tags + ).run_in_background() def rename_tag(self, item: SidebarItem, new_name: str) -> None: if not new_name or new_name == item.name: @@ -1227,7 +1232,7 @@ class SidebarTreeView(QTreeView): parent=self.browser, current_name=old_name, new_name=new_name, - ).run() + ).run_in_background() # Saved searches #################################### diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index e028e6718..092011791 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -175,4 +175,6 @@ class StudyDeck(QDialog): QDialog.accept(self) - add_deck_dialog(mw=self.mw, parent=self, default_text=default, success=success) + add_deck_dialog(parent=self, default_text=default).success( + success + ).run_in_background()