From 017005a4f8765c0db5e8219a62347f0e811289c9 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 16 Mar 2021 18:30:54 +1000 Subject: [PATCH] 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 --- qt/aqt/browser.py | 51 +++++++++++++++------------------------------- qt/aqt/editor.py | 1 + qt/aqt/reviewer.py | 34 +++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index c5477944e..09e409e2a 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -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() diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 5a6da7b6a..4b8c0f9ca 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -90,6 +90,7 @@ _html = """ """ + 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 diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 8b5aeb0c8..f8154ab1d 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -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 ##########################################################################