mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
allow ops to pass metadata into perform_op()
Instances can pass handled_by=self to more easily ignore events they initiate. Fixes ugly refresh when expanding/collapsing decks, but we're still refreshing the card/notes area unnecessarily in that case.
This commit is contained in:
parent
3adf03f9cb
commit
f6ec5928ae
10 changed files with 61 additions and 33 deletions
|
@ -24,6 +24,7 @@ from aqt.editor import Editor
|
|||
from aqt.exporting import ExportDialog
|
||||
from aqt.find_and_replace import FindAndReplaceDialog
|
||||
from aqt.main import ResetReason
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.operations.card import set_card_deck, set_card_flag
|
||||
from aqt.operations.collection import undo
|
||||
from aqt.operations.note import remove_notes
|
||||
|
@ -127,12 +128,12 @@ class Browser(QMainWindow):
|
|||
gui_hooks.browser_will_show(self)
|
||||
self.show()
|
||||
|
||||
def on_operation_did_execute(self, changes: OpChanges) -> None:
|
||||
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
|
||||
focused = current_top_level_widget() == self
|
||||
self.table.op_executed(changes, focused)
|
||||
self.sidebar.op_executed(changes, focused)
|
||||
self.table.op_executed(changes, meta, focused)
|
||||
self.sidebar.op_executed(changes, meta, focused)
|
||||
if changes.note or changes.notetype:
|
||||
if not self.editor.is_updating_note():
|
||||
if meta.handled_by is not self.editor:
|
||||
# fixme: this will leave the splitter shown, but with no current
|
||||
# note being edited
|
||||
note = self.editor.note
|
||||
|
|
|
@ -5,6 +5,7 @@ import aqt.editor
|
|||
from anki.collection import OpChanges
|
||||
from anki.errors import NotFoundError
|
||||
from aqt import gui_hooks
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.qt import *
|
||||
from aqt.utils import disable_help_button, restoreGeom, saveGeom, tr
|
||||
|
||||
|
@ -30,10 +31,10 @@ class EditCurrent(QDialog):
|
|||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||
self.show()
|
||||
|
||||
def on_operation_did_execute(self, changes: OpChanges) -> None:
|
||||
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
|
||||
if not (changes.note or changes.notetype):
|
||||
return
|
||||
if self.editor.is_updating_note():
|
||||
if meta.handled_by is self.editor:
|
||||
return
|
||||
|
||||
# reload note
|
||||
|
|
|
@ -100,8 +100,8 @@ class Editor:
|
|||
redrawing.
|
||||
|
||||
The editor will cause that hook to be fired when it saves changes. To avoid
|
||||
an unwanted refresh, the parent widget should call editor.is_updating_note(),
|
||||
and avoid re-setting the note if it returns true.
|
||||
an unwanted refresh, the parent widget should check if meta.handled_by
|
||||
corresponds to this editor instance, and ignore the change if it does.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
@ -113,7 +113,6 @@ class Editor:
|
|||
self.note: Optional[Note] = None
|
||||
self.addMode = addMode
|
||||
self.currentField: Optional[int] = None
|
||||
self._is_updating_note = False
|
||||
# current card, for card layout
|
||||
self.card: Optional[Card] = None
|
||||
self.setupOuter()
|
||||
|
@ -559,14 +558,7 @@ class Editor:
|
|||
|
||||
def _save_current_note(self) -> None:
|
||||
"Call after note is updated with data from webview."
|
||||
self._is_updating_note = True
|
||||
update_note(mw=self.mw, note=self.note, after_hooks=self._after_updating_note)
|
||||
|
||||
def _after_updating_note(self) -> None:
|
||||
self._is_updating_note = False
|
||||
|
||||
def is_updating_note(self) -> bool:
|
||||
return self._is_updating_note
|
||||
update_note(mw=self.mw, note=self.note, handled_by=self)
|
||||
|
||||
def fonts(self) -> List[Tuple[str, int, bool]]:
|
||||
return [
|
||||
|
|
|
@ -61,6 +61,7 @@ from aqt.emptycards import show_empty_cards
|
|||
from aqt.legacy import install_pylib_legacy
|
||||
from aqt.mediacheck import check_media_db
|
||||
from aqt.mediasync import MediaSyncer
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.operations.collection import undo
|
||||
from aqt.profiles import ProfileManager as ProfileManagerType
|
||||
from aqt.qt import *
|
||||
|
@ -772,6 +773,7 @@ class AnkiQt(QMainWindow):
|
|||
success: PerformOpOptionalSuccessCallback = None,
|
||||
failure: PerformOpOptionalFailureCallback = None,
|
||||
after_hooks: Optional[Callable[[], None]] = None,
|
||||
meta: OpMeta = OpMeta(),
|
||||
) -> None:
|
||||
"""Run the provided operation on a background thread.
|
||||
|
||||
|
@ -825,7 +827,7 @@ class AnkiQt(QMainWindow):
|
|||
status = self.col.undo_status()
|
||||
self._update_undo_actions_for_status_and_save(status)
|
||||
# fire change hooks
|
||||
self._fire_change_hooks_after_op_performed(result, after_hooks)
|
||||
self._fire_change_hooks_after_op_performed(result, after_hooks, meta)
|
||||
|
||||
self.taskman.with_progress(op, wrapped_done)
|
||||
|
||||
|
@ -841,7 +843,10 @@ class AnkiQt(QMainWindow):
|
|||
assert self._background_op_count >= 0
|
||||
|
||||
def _fire_change_hooks_after_op_performed(
|
||||
self, result: ResultWithChanges, after_hooks: Optional[Callable[[], None]]
|
||||
self,
|
||||
result: ResultWithChanges,
|
||||
after_hooks: Optional[Callable[[], None]],
|
||||
meta: OpMeta,
|
||||
) -> None:
|
||||
if isinstance(result, OpChanges):
|
||||
changes = result
|
||||
|
@ -851,7 +856,7 @@ class AnkiQt(QMainWindow):
|
|||
# fire new hook
|
||||
print("op changes:")
|
||||
print(changes)
|
||||
gui_hooks.operation_did_execute(changes)
|
||||
gui_hooks.operation_did_execute(changes, meta)
|
||||
# fire legacy hook so old code notices changes
|
||||
if self.col.op_made_changes(changes):
|
||||
gui_hooks.state_did_reset()
|
||||
|
@ -865,9 +870,9 @@ class AnkiQt(QMainWindow):
|
|||
for field in op.DESCRIPTOR.fields:
|
||||
if field.name != "kind":
|
||||
setattr(op, field.name, True)
|
||||
gui_hooks.operation_did_execute(op)
|
||||
gui_hooks.operation_did_execute(op, None)
|
||||
|
||||
def on_operation_did_execute(self, changes: OpChanges) -> None:
|
||||
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
|
||||
"Notify current screen of changes."
|
||||
focused = current_top_level_widget() == self
|
||||
if self.state == "review":
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpMeta:
|
||||
"""Metadata associated with an operation.
|
||||
|
||||
The `handled_by` field can be used by screens to ignore change
|
||||
events they initiated themselves, if they have already made
|
||||
the required changes."""
|
||||
|
||||
handled_by: Optional[object] = None
|
|
@ -3,11 +3,12 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Sequence
|
||||
from typing import Callable, Optional, Sequence
|
||||
|
||||
from anki.decks import DeckCollapseScope, DeckId
|
||||
from aqt import AnkiQt, QWidget
|
||||
from aqt.main import PerformOpOptionalSuccessCallback
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.utils import getOnlyText, tooltip, tr
|
||||
|
||||
|
||||
|
@ -71,10 +72,16 @@ def add_deck(
|
|||
|
||||
|
||||
def set_deck_collapsed(
|
||||
*, mw: AnkiQt, deck_id: DeckId, collapsed: bool, scope: DeckCollapseScope.V
|
||||
*,
|
||||
mw: AnkiQt,
|
||||
deck_id: DeckId,
|
||||
collapsed: bool,
|
||||
scope: DeckCollapseScope.V,
|
||||
handled_by: Optional[object] = None,
|
||||
) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.decks.set_collapsed(
|
||||
deck_id=deck_id, collapsed=collapsed, scope=scope
|
||||
)
|
||||
),
|
||||
meta=OpMeta(handled_by=handled_by),
|
||||
)
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Sequence
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from anki.decks import DeckId
|
||||
from anki.notes import Note, NoteId
|
||||
from aqt import AnkiQt
|
||||
from aqt.main import PerformOpOptionalSuccessCallback
|
||||
from aqt.operations import OpMeta
|
||||
|
||||
|
||||
def add_note(
|
||||
|
@ -21,10 +22,10 @@ def add_note(
|
|||
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
|
||||
|
||||
|
||||
def update_note(*, mw: AnkiQt, note: Note, after_hooks: Callable[[], None]) -> None:
|
||||
def update_note(*, mw: AnkiQt, note: Note, handled_by: Optional[object]) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.update_note(note),
|
||||
after_hooks=after_hooks,
|
||||
meta=OpMeta(handled_by=handled_by),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ from anki.types import assert_exhaustive
|
|||
from aqt import colors, gui_hooks
|
||||
from aqt.clayout import CardLayout
|
||||
from aqt.models import Models
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.operations.deck import (
|
||||
remove_decks,
|
||||
rename_deck,
|
||||
|
@ -419,7 +420,10 @@ class SidebarTreeView(QTreeView):
|
|||
# Refreshing
|
||||
###########################
|
||||
|
||||
def op_executed(self, op: OpChanges, focused: bool) -> None:
|
||||
def op_executed(self, op: OpChanges, meta: OpMeta, focused: bool) -> None:
|
||||
if meta.handled_by is self:
|
||||
return
|
||||
|
||||
if op.tag or op.notetype or op.deck:
|
||||
self._refresh_needed = True
|
||||
if focused:
|
||||
|
@ -980,6 +984,7 @@ class SidebarTreeView(QTreeView):
|
|||
deck_id=DeckId(node.deck_id),
|
||||
collapsed=not expanded,
|
||||
scope=DeckCollapseScope.BROWSER,
|
||||
handled_by=self,
|
||||
)
|
||||
|
||||
for node in nodes:
|
||||
|
|
|
@ -29,6 +29,7 @@ from anki.errors import NotFoundError
|
|||
from anki.notes import Note, NoteId
|
||||
from anki.utils import ids2str, isWin
|
||||
from aqt import colors, gui_hooks
|
||||
from aqt.operations import OpMeta
|
||||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
|
@ -179,7 +180,7 @@ class Table:
|
|||
def redraw_cells(self) -> None:
|
||||
self._model.redraw_cells()
|
||||
|
||||
def op_executed(self, op: OpChanges, focused: bool) -> None:
|
||||
def op_executed(self, op: OpChanges, meta: OpMeta, focused: bool) -> None:
|
||||
print("op executed")
|
||||
if op.card or op.note or op.deck or op.notetype:
|
||||
self._model.empty_cache()
|
||||
|
|
|
@ -26,6 +26,7 @@ from anki.hooks import runFilter, runHook
|
|||
from anki.models import NotetypeDict
|
||||
from aqt.qt import QDialog, QEvent, QMenu, QWidget
|
||||
from aqt.tagedit import TagEdit
|
||||
import aqt.operations
|
||||
"""
|
||||
|
||||
# Hook list
|
||||
|
@ -458,9 +459,7 @@ hooks = [
|
|||
),
|
||||
Hook(
|
||||
name="operation_did_execute",
|
||||
args=[
|
||||
"changes: anki.collection.OpChanges",
|
||||
],
|
||||
args=["changes: anki.collection.OpChanges", "meta: aqt.operations.OpMeta"],
|
||||
doc="""Called after an operation completes.
|
||||
Changes can be inspected to determine whether the UI needs updating.
|
||||
|
||||
|
|
Loading…
Reference in a new issue