more perform_op() tweaks
- pass the handler directly - reviewer special-cases for flags and notes are now applied at call site - drop the kind attribute on OpChanges which is not needed
This commit is contained in:
parent
9c8148ff0d
commit
3f62f54f14
@ -879,11 +879,6 @@ table.review-log {{ {revlog_style} }}
|
||||
assert_exhaustive(self._undo)
|
||||
assert False
|
||||
|
||||
def op_affects_study_queue(self, changes: OpChanges) -> bool:
|
||||
if changes.kind == changes.SET_CARD_FLAG:
|
||||
return False
|
||||
return changes.card or changes.deck or changes.preference
|
||||
|
||||
def op_made_changes(self, changes: OpChanges) -> bool:
|
||||
for field in changes.DESCRIPTOR.fields:
|
||||
if field.name != "kind":
|
||||
|
@ -24,7 +24,6 @@ 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
|
||||
@ -128,12 +127,14 @@ class Browser(QMainWindow):
|
||||
gui_hooks.browser_will_show(self)
|
||||
self.show()
|
||||
|
||||
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
|
||||
def on_operation_did_execute(
|
||||
self, changes: OpChanges, handler: Optional[object]
|
||||
) -> None:
|
||||
focused = current_top_level_widget() == self
|
||||
self.table.op_executed(changes, meta, focused)
|
||||
self.sidebar.op_executed(changes, meta, focused)
|
||||
self.table.op_executed(changes, handler, focused)
|
||||
self.sidebar.op_executed(changes, handler, focused)
|
||||
if changes.note or changes.notetype:
|
||||
if meta.handler is not self.editor:
|
||||
if handler is not self.editor:
|
||||
# fixme: this will leave the splitter shown, but with no current
|
||||
# note being edited
|
||||
note = self.editor.note
|
||||
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
import aqt
|
||||
from anki.collection import OpChanges
|
||||
@ -76,8 +76,10 @@ class DeckBrowser:
|
||||
if self._refresh_needed:
|
||||
self.refresh()
|
||||
|
||||
def op_executed(self, changes: OpChanges, focused: bool) -> bool:
|
||||
if self.mw.col.op_affects_study_queue(changes):
|
||||
def op_executed(
|
||||
self, changes: OpChanges, handler: Optional[object], focused: bool
|
||||
) -> bool:
|
||||
if changes.study_queues:
|
||||
self._refresh_needed = True
|
||||
|
||||
if focused:
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from typing import Optional
|
||||
|
||||
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
|
||||
|
||||
@ -31,8 +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, meta: OpMeta) -> None:
|
||||
if changes.editor and meta.handler is not self.editor:
|
||||
def on_operation_did_execute(
|
||||
self, changes: OpChanges, handler: Optional[object]
|
||||
) -> None:
|
||||
if changes.editor and handler is not self.editor:
|
||||
# reload note
|
||||
note = self.editor.note
|
||||
try:
|
||||
|
@ -100,7 +100,7 @@ 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 check if meta.handler
|
||||
an unwanted refresh, the parent widget should check if handler
|
||||
corresponds to this editor instance, and ignore the change if it does.
|
||||
"""
|
||||
|
||||
|
@ -61,7 +61,6 @@ 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 *
|
||||
@ -773,7 +772,7 @@ class AnkiQt(QMainWindow):
|
||||
success: PerformOpOptionalSuccessCallback = None,
|
||||
failure: PerformOpOptionalFailureCallback = None,
|
||||
after_hooks: Optional[Callable[[], None]] = None,
|
||||
meta: OpMeta = OpMeta(),
|
||||
handler: Optional[object] = None,
|
||||
) -> None:
|
||||
"""Run the provided operation on a background thread.
|
||||
|
||||
@ -827,7 +826,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, meta)
|
||||
self._fire_change_hooks_after_op_performed(result, after_hooks, handler)
|
||||
|
||||
self.taskman.with_progress(op, wrapped_done)
|
||||
|
||||
@ -846,7 +845,7 @@ class AnkiQt(QMainWindow):
|
||||
self,
|
||||
result: ResultWithChanges,
|
||||
after_hooks: Optional[Callable[[], None]],
|
||||
meta: OpMeta,
|
||||
handler: Optional[object],
|
||||
) -> None:
|
||||
if isinstance(result, OpChanges):
|
||||
changes = result
|
||||
@ -856,7 +855,7 @@ class AnkiQt(QMainWindow):
|
||||
# fire new hook
|
||||
print("op changes:")
|
||||
print(changes)
|
||||
gui_hooks.operation_did_execute(changes, meta)
|
||||
gui_hooks.operation_did_execute(changes, handler)
|
||||
# fire legacy hook so old code notices changes
|
||||
if self.col.op_made_changes(changes):
|
||||
gui_hooks.state_did_reset()
|
||||
@ -872,15 +871,17 @@ class AnkiQt(QMainWindow):
|
||||
setattr(op, field.name, True)
|
||||
gui_hooks.operation_did_execute(op, None)
|
||||
|
||||
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
|
||||
def on_operation_did_execute(
|
||||
self, changes: OpChanges, handler: Optional[object]
|
||||
) -> None:
|
||||
"Notify current screen of changes."
|
||||
focused = current_top_level_widget() == self
|
||||
if self.state == "review":
|
||||
dirty = self.reviewer.op_executed(changes, focused)
|
||||
dirty = self.reviewer.op_executed(changes, handler, focused)
|
||||
elif self.state == "overview":
|
||||
dirty = self.overview.op_executed(changes, focused)
|
||||
dirty = self.overview.op_executed(changes, handler, focused)
|
||||
elif self.state == "deckBrowser":
|
||||
dirty = self.deckBrowser.op_executed(changes, focused)
|
||||
dirty = self.deckBrowser.op_executed(changes, handler, focused)
|
||||
else:
|
||||
dirty = False
|
||||
|
||||
|
@ -1,16 +1,2 @@
|
||||
# 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 `handler` field can be used by screens to ignore change
|
||||
events they initiated themselves, if they have already made
|
||||
the required changes."""
|
||||
|
||||
handler: Optional[object] = None
|
||||
|
@ -3,16 +3,28 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from typing import Optional, Sequence
|
||||
|
||||
from anki.cards import CardId
|
||||
from anki.decks import DeckId
|
||||
from aqt import AnkiQt
|
||||
from aqt.main import PerformOpOptionalSuccessCallback
|
||||
|
||||
|
||||
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_flag(*, mw: AnkiQt, card_ids: Sequence[CardId], flag: int) -> None:
|
||||
mw.perform_op(lambda: mw.col.set_user_flag_for_cards(flag, card_ids))
|
||||
def set_card_flag(
|
||||
*,
|
||||
mw: AnkiQt,
|
||||
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,
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ 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
|
||||
|
||||
|
||||
@ -83,5 +82,5 @@ def set_deck_collapsed(
|
||||
lambda: mw.col.decks.set_collapsed(
|
||||
deck_id=deck_id, collapsed=collapsed, scope=scope
|
||||
),
|
||||
meta=OpMeta(handler=handler),
|
||||
handler=handler,
|
||||
)
|
||||
|
@ -9,7 +9,6 @@ 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(
|
||||
@ -25,7 +24,7 @@ def add_note(
|
||||
def update_note(*, mw: AnkiQt, note: Note, handler: Optional[object]) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.update_note(note),
|
||||
meta=OpMeta(handler=handler),
|
||||
handler=handler,
|
||||
)
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Sequence
|
||||
from typing import Callable, Optional, Sequence
|
||||
|
||||
from anki.collection import OpChangesWithCount
|
||||
from anki.notes import NoteId
|
||||
@ -18,9 +18,12 @@ def add_tags_to_notes(
|
||||
note_ids: Sequence[NoteId],
|
||||
space_separated_tags: str,
|
||||
success: PerformOpOptionalSuccessCallback = None,
|
||||
handler: Optional[object] = None,
|
||||
) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.tags.bulk_add(note_ids, space_separated_tags), success=success
|
||||
lambda: mw.col.tags.bulk_add(note_ids, space_separated_tags),
|
||||
success=success,
|
||||
handler=handler,
|
||||
)
|
||||
|
||||
|
||||
@ -30,9 +33,12 @@ def remove_tags_from_notes(
|
||||
note_ids: Sequence[NoteId],
|
||||
space_separated_tags: str,
|
||||
success: PerformOpOptionalSuccessCallback = None,
|
||||
handler: Optional[object] = None,
|
||||
) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.tags.bulk_remove(note_ids, space_separated_tags), success=success
|
||||
lambda: mw.col.tags.bulk_remove(note_ids, space_separated_tags),
|
||||
success=success,
|
||||
handler=handler,
|
||||
)
|
||||
|
||||
|
||||
|
@ -64,8 +64,10 @@ class Overview:
|
||||
if self._refresh_needed:
|
||||
self.refresh()
|
||||
|
||||
def op_executed(self, changes: OpChanges, focused: bool) -> bool:
|
||||
if self.mw.col.op_affects_study_queue(changes):
|
||||
def op_executed(
|
||||
self, changes: OpChanges, handler: Optional[object], focused: bool
|
||||
) -> bool:
|
||||
if changes.study_queues:
|
||||
self._refresh_needed = True
|
||||
|
||||
if focused:
|
||||
|
@ -14,7 +14,7 @@ from PyQt5.QtCore import Qt
|
||||
|
||||
from anki import hooks
|
||||
from anki.cards import Card, CardId
|
||||
from anki.collection import Config, OpChanges
|
||||
from anki.collection import Config, OpChanges, OpChangesWithCount
|
||||
from anki.tags import MARKED_TAG
|
||||
from anki.utils import stripHTML
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
@ -38,7 +38,6 @@ from aqt.webview import AnkiWebView
|
||||
|
||||
|
||||
class RefreshNeeded(Enum):
|
||||
NO = auto()
|
||||
NOTE_TEXT = auto()
|
||||
QUEUES = auto()
|
||||
|
||||
@ -71,7 +70,7 @@ class Reviewer:
|
||||
self._recordedAudio: Optional[str] = None
|
||||
self.typeCorrect: str = None # web init happens before this is set
|
||||
self.state: Optional[str] = None
|
||||
self._refresh_needed = RefreshNeeded.NO
|
||||
self._refresh_needed: Optional[RefreshNeeded] = None
|
||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||
hooks.card_did_leech.append(self.onLeech)
|
||||
|
||||
@ -102,29 +101,25 @@ class Reviewer:
|
||||
self.mw.col.reset()
|
||||
self.nextCard()
|
||||
self.mw.fade_in_webview()
|
||||
self._refresh_needed = RefreshNeeded.NO
|
||||
self._refresh_needed = None
|
||||
elif self._refresh_needed is RefreshNeeded.NOTE_TEXT:
|
||||
self._redraw_current_card()
|
||||
self.mw.fade_in_webview()
|
||||
self._refresh_needed = RefreshNeeded.NO
|
||||
self._refresh_needed = None
|
||||
|
||||
def op_executed(self, changes: OpChanges, focused: bool) -> bool:
|
||||
if changes.note and changes.kind == OpChanges.UPDATE_NOTE_TAGS:
|
||||
self.card.load()
|
||||
self._update_mark_icon()
|
||||
elif changes.card and changes.kind == OpChanges.SET_CARD_FLAG:
|
||||
# fixme: v3 mtime check
|
||||
self.card.load()
|
||||
self._update_flag_icon()
|
||||
elif self.mw.col.op_affects_study_queue(changes):
|
||||
self._refresh_needed = RefreshNeeded.QUEUES
|
||||
elif changes.note or changes.notetype or changes.tag:
|
||||
self._refresh_needed = RefreshNeeded.NOTE_TEXT
|
||||
def op_executed(
|
||||
self, changes: OpChanges, handler: Optional[object], focused: bool
|
||||
) -> bool:
|
||||
if handler is not self:
|
||||
if changes.study_queues:
|
||||
self._refresh_needed = RefreshNeeded.QUEUES
|
||||
elif changes.editor:
|
||||
self._refresh_needed = RefreshNeeded.NOTE_TEXT
|
||||
|
||||
if focused and self._refresh_needed is not RefreshNeeded.NO:
|
||||
if focused and self._refresh_needed:
|
||||
self.refresh_if_needed()
|
||||
|
||||
return self._refresh_needed is not RefreshNeeded.NO
|
||||
return bool(self._refresh_needed)
|
||||
|
||||
def _redraw_current_card(self) -> None:
|
||||
self.card.load()
|
||||
@ -830,23 +825,45 @@ time = %(time)d;
|
||||
self.mw.onDeckConf(self.mw.col.decks.get(self.card.current_deck_id()))
|
||||
|
||||
def set_flag_on_current_card(self, desired_flag: int) -> None:
|
||||
def redraw_flag(out: OpChanges) -> None:
|
||||
self.card.load()
|
||||
self._update_flag_icon()
|
||||
|
||||
# need to toggle off?
|
||||
if self.card.user_flag() == desired_flag:
|
||||
flag = 0
|
||||
else:
|
||||
flag = desired_flag
|
||||
|
||||
set_card_flag(mw=self.mw, card_ids=[self.card.id], flag=flag)
|
||||
set_card_flag(
|
||||
mw=self.mw,
|
||||
card_ids=[self.card.id],
|
||||
flag=flag,
|
||||
handler=self,
|
||||
success=redraw_flag,
|
||||
)
|
||||
|
||||
def toggle_mark_on_current_note(self) -> None:
|
||||
def redraw_mark(out: OpChangesWithCount) -> None:
|
||||
self.card.load()
|
||||
self._update_mark_icon()
|
||||
|
||||
note = self.card.note()
|
||||
if note.has_tag(MARKED_TAG):
|
||||
remove_tags_from_notes(
|
||||
mw=self.mw, note_ids=[note.id], space_separated_tags=MARKED_TAG
|
||||
mw=self.mw,
|
||||
note_ids=[note.id],
|
||||
space_separated_tags=MARKED_TAG,
|
||||
handler=self,
|
||||
success=redraw_mark,
|
||||
)
|
||||
else:
|
||||
add_tags_to_notes(
|
||||
mw=self.mw, note_ids=[note.id], space_separated_tags=MARKED_TAG
|
||||
mw=self.mw,
|
||||
note_ids=[note.id],
|
||||
space_separated_tags=MARKED_TAG,
|
||||
handler=self,
|
||||
success=redraw_mark,
|
||||
)
|
||||
|
||||
def on_set_due(self) -> None:
|
||||
|
@ -16,7 +16,6 @@ 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,
|
||||
@ -420,8 +419,10 @@ class SidebarTreeView(QTreeView):
|
||||
# Refreshing
|
||||
###########################
|
||||
|
||||
def op_executed(self, changes: OpChanges, meta: OpMeta, focused: bool) -> None:
|
||||
if changes.browser_sidebar and not meta.handler is self:
|
||||
def op_executed(
|
||||
self, changes: OpChanges, handler: Optional[object], focused: bool
|
||||
) -> None:
|
||||
if changes.browser_sidebar and not handler is self:
|
||||
self._refresh_needed = True
|
||||
if focused:
|
||||
self.refresh_if_needed()
|
||||
|
@ -29,7 +29,6 @@ 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 (
|
||||
@ -181,7 +180,9 @@ class Table:
|
||||
def redraw_cells(self) -> None:
|
||||
self._model.redraw_cells()
|
||||
|
||||
def op_executed(self, changes: OpChanges, meta: OpMeta, focused: bool) -> None:
|
||||
def op_executed(
|
||||
self, changes: OpChanges, handler: Optional[object], focused: bool
|
||||
) -> None:
|
||||
if changes.browser_table:
|
||||
self._model.mark_cache_stale()
|
||||
if focused:
|
||||
|
@ -459,7 +459,7 @@ hooks = [
|
||||
),
|
||||
Hook(
|
||||
name="operation_did_execute",
|
||||
args=["changes: anki.collection.OpChanges", "meta: aqt.operations.OpMeta"],
|
||||
args=["changes: anki.collection.OpChanges", "handler: Optional[object]"],
|
||||
doc="""Called after an operation completes.
|
||||
Changes can be inspected to determine whether the UI needs updating.
|
||||
|
||||
|
@ -1498,26 +1498,17 @@ message GetQueuedCardsOut {
|
||||
}
|
||||
|
||||
message OpChanges {
|
||||
// this is not an exhaustive list; we can add more cases as we need them
|
||||
enum Kind {
|
||||
OTHER = 0;
|
||||
UPDATE_NOTE_TAGS = 1;
|
||||
SET_CARD_FLAG = 2;
|
||||
UPDATE_NOTE = 3;
|
||||
}
|
||||
bool card = 1;
|
||||
bool note = 2;
|
||||
bool deck = 3;
|
||||
bool tag = 4;
|
||||
bool notetype = 5;
|
||||
bool preference = 6;
|
||||
|
||||
Kind kind = 1;
|
||||
bool card = 2;
|
||||
bool note = 3;
|
||||
bool deck = 4;
|
||||
bool tag = 5;
|
||||
bool notetype = 6;
|
||||
bool preference = 7;
|
||||
|
||||
bool browser_table = 8;
|
||||
bool browser_sidebar = 9;
|
||||
bool editor = 10;
|
||||
bool study_queues = 11;
|
||||
bool browser_table = 7;
|
||||
bool browser_sidebar = 8;
|
||||
bool editor = 9;
|
||||
bool study_queues = 10;
|
||||
}
|
||||
|
||||
message UndoStatus {
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use pb::op_changes::Kind;
|
||||
|
||||
use crate::{
|
||||
backend_proto as pb,
|
||||
ops::OpChanges,
|
||||
@ -10,21 +8,9 @@ use crate::{
|
||||
undo::{UndoOutput, UndoStatus},
|
||||
};
|
||||
|
||||
impl From<Op> for Kind {
|
||||
fn from(o: Op) -> Self {
|
||||
match o {
|
||||
Op::SetFlag => Kind::SetCardFlag,
|
||||
Op::UpdateTag => Kind::UpdateNoteTags,
|
||||
Op::UpdateNote => Kind::UpdateNote,
|
||||
_ => Kind::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpChanges> for pb::OpChanges {
|
||||
fn from(c: OpChanges) -> Self {
|
||||
pb::OpChanges {
|
||||
kind: Kind::from(c.op) as i32,
|
||||
card: c.changes.card,
|
||||
note: c.changes.note,
|
||||
deck: c.changes.deck,
|
||||
|
Loading…
Reference in New Issue
Block a user