add booleans for various screens to OpChanges

The backend knows exactly which op has executed, and it saves us having
to re-implement this logic on each client.

Fixes the browser table refreshing when toggling decks.
This commit is contained in:
Damien Elmes 2021-04-05 14:28:56 +10:00
parent f6ec5928ae
commit a18bb2af12
12 changed files with 67 additions and 34 deletions

View file

@ -133,7 +133,7 @@ class Browser(QMainWindow):
self.table.op_executed(changes, meta, focused) self.table.op_executed(changes, meta, focused)
self.sidebar.op_executed(changes, meta, focused) self.sidebar.op_executed(changes, meta, focused)
if changes.note or changes.notetype: if changes.note or changes.notetype:
if meta.handled_by is not self.editor: if meta.handler is not self.editor:
# fixme: this will leave the splitter shown, but with no current # fixme: this will leave the splitter shown, but with no current
# note being edited # note being edited
note = self.editor.note note = self.editor.note

View file

@ -32,21 +32,17 @@ class EditCurrent(QDialog):
self.show() self.show()
def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None: def on_operation_did_execute(self, changes: OpChanges, meta: OpMeta) -> None:
if not (changes.note or changes.notetype): if changes.editor and meta.handler is not self.editor:
return # reload note
if meta.handled_by is self.editor: note = self.editor.note
return try:
note.load()
except NotFoundError:
# note's been deleted
self.cleanup_and_close()
return
# reload note self.editor.set_note(note)
note = self.editor.note
try:
note.load()
except NotFoundError:
# note's been deleted
self.cleanup_and_close()
return
self.editor.set_note(note)
def cleanup_and_close(self) -> None: def cleanup_and_close(self) -> None:
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute) gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)

View file

@ -100,7 +100,7 @@ class Editor:
redrawing. redrawing.
The editor will cause that hook to be fired when it saves changes. To avoid 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.handled_by an unwanted refresh, the parent widget should check if meta.handler
corresponds to this editor instance, and ignore the change if it does. corresponds to this editor instance, and ignore the change if it does.
""" """
@ -558,7 +558,7 @@ class Editor:
def _save_current_note(self) -> None: def _save_current_note(self) -> None:
"Call after note is updated with data from webview." "Call after note is updated with data from webview."
update_note(mw=self.mw, note=self.note, handled_by=self) update_note(mw=self.mw, note=self.note, handler=self)
def fonts(self) -> List[Tuple[str, int, bool]]: def fonts(self) -> List[Tuple[str, int, bool]]:
return [ return [

View file

@ -9,8 +9,8 @@ from typing import Optional
class OpMeta: class OpMeta:
"""Metadata associated with an operation. """Metadata associated with an operation.
The `handled_by` field can be used by screens to ignore change The `handler` field can be used by screens to ignore change
events they initiated themselves, if they have already made events they initiated themselves, if they have already made
the required changes.""" the required changes."""
handled_by: Optional[object] = None handler: Optional[object] = None

View file

@ -77,11 +77,11 @@ def set_deck_collapsed(
deck_id: DeckId, deck_id: DeckId,
collapsed: bool, collapsed: bool,
scope: DeckCollapseScope.V, scope: DeckCollapseScope.V,
handled_by: Optional[object] = None, handler: Optional[object] = None,
) -> None: ) -> None:
mw.perform_op( mw.perform_op(
lambda: mw.col.decks.set_collapsed( lambda: mw.col.decks.set_collapsed(
deck_id=deck_id, collapsed=collapsed, scope=scope deck_id=deck_id, collapsed=collapsed, scope=scope
), ),
meta=OpMeta(handled_by=handled_by), meta=OpMeta(handler=handler),
) )

View file

@ -22,10 +22,10 @@ def add_note(
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success) mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
def update_note(*, mw: AnkiQt, note: Note, handled_by: Optional[object]) -> None: def update_note(*, mw: AnkiQt, note: Note, handler: Optional[object]) -> None:
mw.perform_op( mw.perform_op(
lambda: mw.col.update_note(note), lambda: mw.col.update_note(note),
meta=OpMeta(handled_by=handled_by), meta=OpMeta(handler=handler),
) )

View file

@ -420,11 +420,8 @@ class SidebarTreeView(QTreeView):
# Refreshing # Refreshing
########################### ###########################
def op_executed(self, op: OpChanges, meta: OpMeta, focused: bool) -> None: def op_executed(self, changes: OpChanges, meta: OpMeta, focused: bool) -> None:
if meta.handled_by is self: if changes.browser_sidebar and not meta.handler is self:
return
if op.tag or op.notetype or op.deck:
self._refresh_needed = True self._refresh_needed = True
if focused: if focused:
self.refresh_if_needed() self.refresh_if_needed()
@ -984,7 +981,7 @@ class SidebarTreeView(QTreeView):
deck_id=DeckId(node.deck_id), deck_id=DeckId(node.deck_id),
collapsed=not expanded, collapsed=not expanded,
scope=DeckCollapseScope.BROWSER, scope=DeckCollapseScope.BROWSER,
handled_by=self, handler=self,
) )
for node in nodes: for node in nodes:

View file

@ -180,9 +180,8 @@ class Table:
def redraw_cells(self) -> None: def redraw_cells(self) -> None:
self._model.redraw_cells() self._model.redraw_cells()
def op_executed(self, op: OpChanges, meta: OpMeta, focused: bool) -> None: def op_executed(self, changes: OpChanges, meta: OpMeta, focused: bool) -> None:
print("op executed") if changes.browser_table:
if op.card or op.note or op.deck or op.notetype:
self._model.empty_cache() self._model.empty_cache()
if focused: if focused:
self.redraw_cells() self.redraw_cells()

View file

@ -1513,6 +1513,11 @@ message OpChanges {
bool tag = 5; bool tag = 5;
bool notetype = 6; bool notetype = 6;
bool preference = 7; bool preference = 7;
bool browser_table = 8;
bool browser_sidebar = 9;
bool editor = 10;
bool study_queues = 11;
} }
message UndoStatus { message UndoStatus {

View file

@ -31,6 +31,10 @@ impl From<OpChanges> for pb::OpChanges {
tag: c.changes.tag, tag: c.changes.tag,
notetype: c.changes.notetype, notetype: c.changes.notetype,
preference: c.changes.preference, preference: c.changes.preference,
browser_table: c.requires_browser_table_redraw(),
browser_sidebar: c.requires_browser_sidebar_redraw(),
editor: c.requires_editor_redraw(),
study_queues: c.requires_study_queue_rebuild(),
} }
} }
} }

View file

@ -105,3 +105,36 @@ impl<T> OpOutput<T> {
} }
} }
} }
impl OpChanges {
// These routines should return true even if the GUI may have
// special handling for an action, since we need to do the right
// thing when undoing, and if multiple windows of the same type are
// open. For example, while toggling the expand/collapse state
// in the sidebar will not normally trigger a full sidebar refresh,
// requires_browser_sidebar_redraw() should still return true.
pub fn requires_browser_table_redraw(&self) -> bool {
let c = &self.changes;
c.card
|| c.notetype
|| (c.note && self.op != Op::AddNote)
|| (c.deck && self.op != Op::ExpandCollapse)
}
pub fn requires_browser_sidebar_redraw(&self) -> bool {
let c = &self.changes;
c.tag || c.deck || c.notetype
}
pub fn requires_editor_redraw(&self) -> bool {
let c = &self.changes;
c.note || c.notetype
}
pub fn requires_study_queue_rebuild(&self) -> bool {
let c = &self.changes;
!matches!(self.op, Op::AnswerCard | Op::ExpandCollapse)
&& (c.card || c.deck || c.preference)
}
}

View file

@ -140,8 +140,7 @@ impl Collection {
} }
pub(crate) fn maybe_clear_study_queues_after_op(&mut self, op: OpChanges) { pub(crate) fn maybe_clear_study_queues_after_op(&mut self, op: OpChanges) {
if op.op != Op::AnswerCard && (op.changes.card || op.changes.deck || op.changes.preference) if op.requires_study_queue_rebuild() {
{
self.state.card_queues = None; self.state.card_queues = None;
} }
} }