various redraw fixes

- need to drop cardObjs cache when updating cells
- stop listening on editor_did_* hooks. unfocus_field and typing_timer
are covered by operation_did_execute on note save already, and the
user potentially has editors open in other windows as well
- distinguish between card queue refresh and note text redraw in review
screen again
- update preview window when note updated
- defer setUpdatesEnabled(True) until we receive focus again, as it
causes cells to redraw. We might want to use our own flag to prevent
updating in the model instead of using Qt for this
This commit is contained in:
Damien Elmes 2021-03-16 18:30:54 +10:00
parent 3f87f7bf5c
commit 017005a4f8
3 changed files with 43 additions and 43 deletions

View file

@ -17,7 +17,6 @@ from anki.consts import *
from anki.errors import InvalidInput, NotFoundError
from anki.lang import without_unicode_isolation
from anki.models import NoteType
from anki.notes import Note
from anki.stats import CardStats
from anki.utils import htmlToTextLine, ids2str, isMac, isWin
from aqt import AnkiQt, colors, gui_hooks
@ -112,15 +111,6 @@ class DataModel(QAbstractTableModel):
self.cardObjs[id] = card
return self.cardObjs[id]
def refreshNote(self, note: Note) -> None:
refresh = False
for c in note.cards():
if c.id in self.cardObjs:
del self.cardObjs[c.id]
refresh = True
if refresh:
self.layoutChanged.emit() # type: ignore
# Model interface
######################################################################
@ -214,8 +204,9 @@ class DataModel(QAbstractTableModel):
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)
bottom_right = self.index(len(self.cards) - 1, len(self.activeCols) - 1)
self.cardObjs = {}
self.dataChanged.emit(top_left, bottom_right) # type: ignore
def reset(self) -> None:
self.beginReset()
@ -513,16 +504,22 @@ class Browser(QMainWindow):
self.setUpdatesEnabled(False)
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)
focused = current_top_level_widget() == self
if focused:
self.setUpdatesEnabled(True)
self.model.op_executed(changes, focused)
if changes.note or changes.notetype:
if not self.editor.is_updating_note():
note = self.editor.note
if note:
note.load()
self.editor.set_note(note)
self._renderPreview()
def on_focus_change(self, new: Optional[QWidget], old: Optional[QWidget]) -> None:
if current_top_level_widget() == self:
self.setUpdatesEnabled(True)
self.model.refresh_if_needed()
def setupMenus(self) -> None:
@ -860,13 +857,6 @@ QTableView {{ gridline-color: {grid} }}
self._updateFlagsMenu()
gui_hooks.browser_did_change_row(self)
def refreshCurrentCard(self, note: Note) -> None:
self.model.refreshNote(note)
self._renderPreview()
def onLoadNote(self, editor: Editor) -> None:
self.refreshCurrentCard(editor.note)
def currentRow(self) -> int:
idx = self.form.tableView.selectionModel().currentIndex()
return idx.row()
@ -1452,9 +1442,6 @@ where id in %s"""
def setupHooks(self) -> None:
gui_hooks.undo_state_did_change.append(self.onUndoState)
gui_hooks.editor_did_fire_typing_timer.append(self.refreshCurrentCard)
gui_hooks.editor_did_load_note.append(self.onLoadNote)
gui_hooks.editor_did_unfocus_field.append(self.on_unfocus_field)
gui_hooks.sidebar_should_refresh_decks.append(self.on_item_added)
gui_hooks.sidebar_should_refresh_notetypes.append(self.on_item_added)
gui_hooks.operation_will_execute.append(self.on_operation_will_execute)
@ -1463,18 +1450,12 @@ where id in %s"""
def teardownHooks(self) -> None:
gui_hooks.undo_state_did_change.remove(self.onUndoState)
gui_hooks.editor_did_fire_typing_timer.remove(self.refreshCurrentCard)
gui_hooks.editor_did_load_note.remove(self.onLoadNote)
gui_hooks.editor_did_unfocus_field.remove(self.on_unfocus_field)
gui_hooks.sidebar_should_refresh_decks.remove(self.on_item_added)
gui_hooks.sidebar_should_refresh_notetypes.remove(self.on_item_added)
gui_hooks.operation_will_execute.remove(self.on_operation_will_execute)
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
gui_hooks.focus_did_change.remove(self.on_focus_change)
def on_unfocus_field(self, changed: bool, note: Note, field_idx: int) -> None:
self.refreshCurrentCard(note)
# covers the tag, note and deck case
def on_item_added(self, item: Any = None) -> None:
self.sidebar.refresh()

View file

@ -90,6 +90,7 @@ _html = """
</div>
"""
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

View file

@ -7,6 +7,7 @@ import html
import json
import re
import unicodedata as ucd
from enum import Enum, auto
from typing import Any, Callable, List, Match, Optional, Sequence, Tuple, Union
from PyQt5.QtCore import Qt
@ -41,6 +42,12 @@ from aqt.utils import (
from aqt.webview import AnkiWebView
class RefreshNeeded(Enum):
NO = auto()
NOTE_TEXT = auto()
QUEUES = auto()
class ReviewerBottomBar:
def __init__(self, reviewer: Reviewer) -> None:
self.reviewer = reviewer
@ -69,7 +76,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 = False
self._refresh_needed = RefreshNeeded.NO
self.bottom = BottomBar(mw, mw.bottomWeb)
hooks.card_did_leech.append(self.onLeech)
@ -78,7 +85,7 @@ class Reviewer:
self.web.set_bridge_command(self._linkHandler, self)
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
self._reps: int = None
self._refresh_needed = True
self._refresh_needed = RefreshNeeded.QUEUES
self.refresh_if_needed()
def lastCard(self) -> Optional[Card]:
@ -96,11 +103,15 @@ class Reviewer:
self.card = None
def refresh_if_needed(self) -> None:
if self._refresh_needed:
if self._refresh_needed is RefreshNeeded.QUEUES:
self.mw.col.reset()
self.nextCard()
self._refresh_needed = False
self.mw.fade_in_webview()
self._refresh_needed = RefreshNeeded.NO
elif self._refresh_needed is RefreshNeeded.NOTE_TEXT:
self._redraw_current_card()
self.mw.fade_in_webview()
self._refresh_needed = RefreshNeeded.NO
def op_executed(self, changes: OpChanges, focused: bool) -> bool:
if changes.note and changes.kind == OpChanges.UPDATE_NOTE_TAGS:
@ -111,14 +122,21 @@ class Reviewer:
self.card.load()
self._update_flag_icon()
elif self.mw.col.op_affects_study_queue(changes):
self._refresh_needed = True
self._refresh_needed = RefreshNeeded.QUEUES
elif changes.note or changes.notetype or changes.tag:
self._refresh_needed = True
self._refresh_needed = RefreshNeeded.NOTE_TEXT
if focused and self._refresh_needed:
if focused and self._refresh_needed is not RefreshNeeded.NO:
self.refresh_if_needed()
return self._refresh_needed
return self._refresh_needed is not RefreshNeeded.NO
def _redraw_current_card(self) -> None:
self.card.load()
if self.state == "answer":
self._showAnswer()
else:
self._showQuestion()
# Fetching a card
##########################################################################