mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
prevent editor from refreshing itself after a save
- add after_hooks arg to perform_op() - when refreshing browse screen, just redraws cells, and handle editor update in Browser instead of the model
This commit is contained in:
parent
6b0fe4b381
commit
3ad86f1852
6 changed files with 95 additions and 54 deletions
|
@ -104,7 +104,7 @@ class AddCards(QDialog):
|
|||
self.historyButton = b
|
||||
|
||||
def setAndFocusNote(self, note: Note) -> None:
|
||||
self.editor.setNote(note, focusTo=0)
|
||||
self.editor.set_note(note, focusTo=0)
|
||||
|
||||
def show_notetype_selector(self) -> None:
|
||||
self.editor.saveNow(self.notetype_chooser.choose_notetype)
|
||||
|
|
|
@ -209,14 +209,21 @@ class DataModel(QAbstractTableModel):
|
|||
finally:
|
||||
self.endReset()
|
||||
|
||||
def redraw_cells(self) -> None:
|
||||
"Update cell contents, without changing search count/columns/sorting."
|
||||
if not self.cards:
|
||||
return
|
||||
top_left = self.index(0, 0)
|
||||
bottom_right = self.index(len(self.cards)-1, len(self.activeCols)-1)
|
||||
self.dataChanged.emit(top_left, bottom_right)
|
||||
|
||||
def reset(self) -> None:
|
||||
self.beginReset()
|
||||
self.endReset()
|
||||
self.refresh_needed = False
|
||||
|
||||
# caller must have called editor.saveNow() before calling this or .reset()
|
||||
def beginReset(self) -> None:
|
||||
self.browser.editor.setNote(None, hide=False)
|
||||
self.browser.editor.set_note(None, hide=False)
|
||||
self.browser.mw.progress.start()
|
||||
self.saveSelection()
|
||||
self.beginResetModel()
|
||||
|
@ -299,7 +306,8 @@ class DataModel(QAbstractTableModel):
|
|||
|
||||
def refresh_if_needed(self) -> None:
|
||||
if self.refresh_needed:
|
||||
self.reset()
|
||||
self.redraw_cells()
|
||||
self.refresh_needed = False
|
||||
|
||||
# Column data
|
||||
######################################################################
|
||||
|
@ -507,6 +515,11 @@ class Browser(QMainWindow):
|
|||
def on_operation_did_execute(self, changes: OpChanges) -> None:
|
||||
self.setUpdatesEnabled(True)
|
||||
self.model.op_executed(changes, current_top_level_widget() == self)
|
||||
if (changes.note or changes.notetype) and not self.editor.is_updating_note():
|
||||
note = self.editor.note
|
||||
if note:
|
||||
note.load()
|
||||
self.editor.set_note(note)
|
||||
|
||||
def on_focus_change(self, new: Optional[QWidget], old: Optional[QWidget]) -> None:
|
||||
if current_top_level_widget() == self:
|
||||
|
@ -836,11 +849,11 @@ QTableView {{ gridline-color: {grid} }}
|
|||
self.form.splitter.widget(1).setVisible(bool(show))
|
||||
|
||||
if not show:
|
||||
self.editor.setNote(None)
|
||||
self.editor.set_note(None)
|
||||
self.singleCard = False
|
||||
self._renderPreview()
|
||||
else:
|
||||
self.editor.setNote(self.card.note(reload=True), focusTo=self.focusTo)
|
||||
self.editor.set_note(self.card.note(reload=True), focusTo=self.focusTo)
|
||||
self.focusTo = None
|
||||
self.editor.card = self.card
|
||||
self.singleCard = True
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import aqt.editor
|
||||
from anki.collection import OpChanges
|
||||
from anki.errors import NotFoundError
|
||||
from aqt import gui_hooks
|
||||
from aqt.main import ResetReason
|
||||
from aqt.qt import *
|
||||
from aqt.utils import TR, disable_help_button, restoreGeom, saveGeom, tooltip, tr
|
||||
from aqt.utils import TR, disable_help_button, restoreGeom, saveGeom, tr
|
||||
|
||||
|
||||
class EditCurrent(QDialog):
|
||||
|
@ -23,33 +25,38 @@ class EditCurrent(QDialog):
|
|||
)
|
||||
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
|
||||
self.editor.card = self.mw.reviewer.card
|
||||
self.editor.setNote(self.mw.reviewer.card.note(), focusTo=0)
|
||||
self.editor.set_note(self.mw.reviewer.card.note(), focusTo=0)
|
||||
restoreGeom(self, "editcurrent")
|
||||
gui_hooks.state_did_reset.append(self.onReset)
|
||||
self.mw.requireReset(reason=ResetReason.EditCurrentInit, context=self)
|
||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||
self.show()
|
||||
# reset focus after open, taking care not to retain webview
|
||||
# pylint: disable=unnecessary-lambda
|
||||
self.mw.progress.timer(100, lambda: self.editor.web.setFocus(), False)
|
||||
|
||||
def onReset(self) -> None:
|
||||
# lazy approach for now: throw away edits
|
||||
try:
|
||||
n = self.editor.note
|
||||
n.load() # reload in case the model changed
|
||||
except:
|
||||
# card's been deleted
|
||||
gui_hooks.state_did_reset.remove(self.onReset)
|
||||
self.editor.setNote(None)
|
||||
self.mw.reset()
|
||||
aqt.dialogs.markClosed("EditCurrent")
|
||||
self.close()
|
||||
def on_operation_did_execute(self, changes: OpChanges) -> None:
|
||||
if not (changes.note or changes.notetype):
|
||||
return
|
||||
self.editor.setNote(n)
|
||||
if self.editor.is_updating_note():
|
||||
return
|
||||
|
||||
# reload 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:
|
||||
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
|
||||
self.editor.cleanup()
|
||||
saveGeom(self, "editcurrent")
|
||||
aqt.dialogs.markClosed("EditCurrent")
|
||||
QDialog.reject(self)
|
||||
|
||||
def reopen(self, mw: aqt.AnkiQt) -> None:
|
||||
tooltip("Please finish editing the existing card first.")
|
||||
self.onReset()
|
||||
if card := self.mw.reviewer.card:
|
||||
self.editor.set_note(card.note())
|
||||
|
||||
def reject(self) -> None:
|
||||
self.saveAndClose()
|
||||
|
@ -58,20 +65,7 @@ class EditCurrent(QDialog):
|
|||
self.editor.saveNow(self._saveAndClose)
|
||||
|
||||
def _saveAndClose(self) -> None:
|
||||
gui_hooks.state_did_reset.remove(self.onReset)
|
||||
r = self.mw.reviewer
|
||||
try:
|
||||
r.card.load()
|
||||
except:
|
||||
# card was removed by clayout
|
||||
pass
|
||||
else:
|
||||
self.mw.reviewer.cardQueue.append(self.mw.reviewer.card)
|
||||
self.editor.cleanup()
|
||||
self.mw.moveToState("review")
|
||||
saveGeom(self, "editcurrent")
|
||||
aqt.dialogs.markClosed("EditCurrent")
|
||||
QDialog.reject(self)
|
||||
self.cleanup_and_close()
|
||||
|
||||
def closeWithCallback(self, onsuccess: Callable[[], None]) -> None:
|
||||
def callback() -> None:
|
||||
|
@ -79,3 +73,5 @@ class EditCurrent(QDialog):
|
|||
onsuccess()
|
||||
|
||||
self.editor.saveNow(callback)
|
||||
|
||||
onReset = on_operation_did_execute
|
||||
|
|
|
@ -90,8 +90,16 @@ _html = """
|
|||
</div>
|
||||
"""
|
||||
|
||||
# caller is responsible for resetting note on reset
|
||||
class Editor:
|
||||
"""The screen that embeds an editing widget should listen for changes via
|
||||
the `operation_did_execute` hook, and call set_note() when the editor needs
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, mw: AnkiQt, widget: QWidget, parentWindow: QWidget, addMode: bool = False
|
||||
) -> None:
|
||||
|
@ -101,6 +109,7 @@ 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()
|
||||
|
@ -491,7 +500,7 @@ class Editor:
|
|||
# Setting/unsetting the current note
|
||||
######################################################################
|
||||
|
||||
def setNote(
|
||||
def set_note(
|
||||
self, note: Optional[Note], hide: bool = True, focusTo: Optional[int] = None
|
||||
) -> None:
|
||||
"Make NOTE the current note."
|
||||
|
@ -543,7 +552,14 @@ class Editor:
|
|||
|
||||
def _save_current_note(self) -> None:
|
||||
"Call after note is updated with data from webview."
|
||||
update_note(mw=self.mw, note=self.note)
|
||||
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
|
||||
|
||||
def fonts(self) -> List[Tuple[str, int, bool]]:
|
||||
return [
|
||||
|
@ -596,10 +612,14 @@ class Editor:
|
|||
return True
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self.setNote(None)
|
||||
self.set_note(None)
|
||||
# prevent any remaining evalWithCallback() events from firing after C++ object deleted
|
||||
self.web = None
|
||||
|
||||
# legacy
|
||||
|
||||
setNote = set_note
|
||||
|
||||
# HTML editing
|
||||
######################################################################
|
||||
|
||||
|
|
|
@ -722,6 +722,7 @@ class AnkiQt(QMainWindow):
|
|||
*,
|
||||
success: PerformOpOptionalSuccessCallback = None,
|
||||
failure: Optional[Callable[[Exception], Any]] = None,
|
||||
after_hooks: Optional[Callable[[], None]] = None,
|
||||
) -> None:
|
||||
"""Run the provided operation on a background thread.
|
||||
|
||||
|
@ -740,10 +741,14 @@ class AnkiQt(QMainWindow):
|
|||
Be careful not to call any UI routines in `op`, as that may crash Qt.
|
||||
This includes things select .selectedCards() in the browse screen.
|
||||
|
||||
on_success() will be called with the return value of op().
|
||||
success() will be called with the return value of op().
|
||||
|
||||
If op() throws an exception, it will be shown in a popup, or
|
||||
passed to on_exception() if it is provided.
|
||||
passed to failure() if it is provided.
|
||||
|
||||
after_hooks() will be called after hooks are fired, if it is provided.
|
||||
Components can use this to ignore change notices generated by operations
|
||||
they invoke themselves.
|
||||
"""
|
||||
|
||||
gui_hooks.operation_will_execute()
|
||||
|
@ -769,11 +774,13 @@ 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)
|
||||
self._fire_change_hooks_after_op_performed(result, after_hooks)
|
||||
|
||||
self.taskman.with_progress(op, wrapped_done)
|
||||
|
||||
def _fire_change_hooks_after_op_performed(self, result: ResultWithChanges) -> None:
|
||||
def _fire_change_hooks_after_op_performed(
|
||||
self, result: ResultWithChanges, after_hooks: Optional[Callable[[], None]]
|
||||
) -> None:
|
||||
if isinstance(result, OpChanges):
|
||||
changes = result
|
||||
else:
|
||||
|
@ -786,6 +793,8 @@ class AnkiQt(QMainWindow):
|
|||
# fire legacy hook so old code notices changes
|
||||
if self.col.op_made_changes(changes):
|
||||
gui_hooks.state_did_reset()
|
||||
if after_hooks:
|
||||
after_hooks()
|
||||
|
||||
def _synthesize_op_did_execute_from_reset(self) -> None:
|
||||
"""Fire the `operation_did_execute` hook with everything marked as changed,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Sequence
|
||||
from typing import Callable, Optional, Sequence
|
||||
|
||||
from anki.lang import TR
|
||||
from anki.notes import Note
|
||||
|
@ -23,8 +23,11 @@ def add_note(
|
|||
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
|
||||
|
||||
|
||||
def update_note(*, mw: AnkiQt, note: Note) -> None:
|
||||
mw.perform_op(lambda: mw.col.update_note(note))
|
||||
def update_note(*, mw: AnkiQt, note: Note, after_hooks: Callable[[], None]) -> None:
|
||||
mw.perform_op(
|
||||
lambda: mw.col.update_note(note),
|
||||
after_hooks=after_hooks,
|
||||
)
|
||||
|
||||
|
||||
def remove_notes(
|
||||
|
|
Loading…
Reference in a new issue