diff --git a/ftl/qt/qt-misc.ftl b/ftl/qt/qt-misc.ftl index e1de29fbe..1da297bb2 100644 --- a/ftl/qt/qt-misc.ftl +++ b/ftl/qt/qt-misc.ftl @@ -36,7 +36,6 @@ qt-misc-please-select-a-deck = Please select a deck. qt-misc-please-use-fileimport-to-import-this = Please use File>Import to import this file. qt-misc-processing = Processing... qt-misc-replace-your-collection-with-an-earlier = Replace your collection with an earlier backup? -qt-misc-resume-now = Resume Now qt-misc-revert-to-backup = Revert to backup qt-misc-reverted-to-state-prior-to = Reverted to state prior to '{ $val }'. qt-misc-segoe-ui = "Segoe UI" @@ -56,7 +55,6 @@ qt-misc-unable-to-move-existing-file-to = Unable to move existing file to trash qt-misc-undo = Undo qt-misc-undo2 = Undo { $val } qt-misc-unexpected-response-code = Unexpected response code: { $val } -qt-misc-waiting-for-editing-to-finish = Waiting for editing to finish. qt-misc-would-you-like-to-download-it = Would you like to download it now? qt-misc-your-collection-file-appears-to-be = Your collection file appears to be corrupt. This can happen when the file is copied or moved while Anki is open, or when the collection is stored on a network or cloud drive. If problems persist after restarting your computer, please open an automatic backup from the profile screen. qt-misc-your-computers-storage-may-be-full = Your computer's storage may be full. Please delete some unneeded files, then try again. diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 4d21fddbe..a1e4b425d 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -34,6 +34,7 @@ from aqt.utils import ( TR, HelpPage, askUser, + current_top_level_widget, disable_help_button, getTag, openHelp, @@ -91,6 +92,7 @@ class DataModel(QAbstractTableModel): ) self.cards: Sequence[int] = [] self.cardObjs: Dict[int, Card] = {} + self.refresh_needed = False def getCard(self, index: QModelIndex) -> Optional[Card]: id = self.cards[index.row()] @@ -203,6 +205,7 @@ class DataModel(QAbstractTableModel): 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: @@ -281,8 +284,14 @@ class DataModel(QAbstractTableModel): else: tv.selectRow(0) - def maybe_redraw_after_operation(self, op: OperationInfo) -> None: + def op_executed(self, op: OperationInfo, focused: bool) -> None: if op.changes.card or op.changes.note or op.changes.deck or op.changes.notetype: + self.refresh_needed = True + if focused: + self.refresh_if_needed() + + def refresh_if_needed(self) -> None: + if self.refresh_needed: self.reset() # Column data @@ -490,7 +499,11 @@ class Browser(QMainWindow): def on_operation_did_execute(self, op: OperationInfo) -> None: self.setUpdatesEnabled(True) - self.model.maybe_redraw_after_operation(op) + self.model.op_executed(op, current_top_level_widget() == self) + + def on_focus_change(self, new: Optional[QWidget], old: Optional[QWidget]) -> None: + if current_top_level_widget() == self: + self.model.refresh_if_needed() def setupMenus(self) -> None: # pylint: disable=unnecessary-lambda @@ -1415,7 +1428,6 @@ where id in %s""" def setupHooks(self) -> None: gui_hooks.undo_state_did_change.append(self.onUndoState) - gui_hooks.state_did_reset.append(self.onReset) 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) @@ -1423,10 +1435,10 @@ where id in %s""" gui_hooks.sidebar_should_refresh_notetypes.append(self.on_item_added) gui_hooks.operation_will_execute.append(self.on_operation_will_execute) gui_hooks.operation_did_execute.append(self.on_operation_did_execute) + gui_hooks.focus_did_change.append(self.on_focus_change) def teardownHooks(self) -> None: gui_hooks.undo_state_did_change.remove(self.onUndoState) - gui_hooks.state_did_reset.remove(self.onReset) 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) @@ -1434,6 +1446,7 @@ where id in %s""" 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) diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 87aaaf6b7..a25b5e4f0 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -62,7 +62,7 @@ class DeckBrowser: self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) self._v1_message_dismissed_at = 0 - gui_hooks.operation_did_execute.append(self.on_operation_did_execute) + self.refresh_needed = False def show(self) -> None: av_player.stop_and_clear_queue() @@ -70,17 +70,23 @@ class DeckBrowser: self._renderPage() # redraw top bar for theme change self.mw.toolbar.redraw() + self.refresh() def refresh(self) -> None: self._renderPage() + self.refresh_needed = False - def on_operation_did_execute(self, op: OperationInfo) -> None: - if self.mw.state != "deckBrowser": - return - - if self.mw.col.op_affects_study_queue(op): + def refresh_if_needed(self) -> None: + if self.refresh_needed: self.refresh() + def op_executed(self, op: OperationInfo, focused: bool) -> None: + if self.mw.col.op_affects_study_queue(op): + self.refresh_needed = True + + if focused: + self.refresh_if_needed() + # Event handlers ########################################################################## diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 8c86f6de6..59f133800 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -72,6 +72,7 @@ from aqt.utils import ( HelpPage, askUser, checkInvalidFilename, + current_top_level_widget, disable_help_button, getFile, getOnlyText, @@ -85,6 +86,7 @@ from aqt.utils import ( showInfo, showWarning, tooltip, + top_level_widget, tr, ) @@ -92,28 +94,6 @@ T = TypeVar("T") install_pylib_legacy() - -class ResetReason(enum.Enum): - Unknown = "unknown" - AddCardsAddNote = "addCardsAddNote" - EditCurrentInit = "editCurrentInit" - EditorBridgeCmd = "editorBridgeCmd" - BrowserSetDeck = "browserSetDeck" - BrowserAddTags = "browserAddTags" - BrowserRemoveTags = "browserRemoveTags" - BrowserSuspend = "browserSuspend" - BrowserReposition = "browserReposition" - BrowserReschedule = "browserReschedule" - BrowserFindReplace = "browserFindReplace" - BrowserTagDupes = "browserTagDupes" - BrowserDeleteDeck = "browserDeleteDeck" - - -class ResetRequired: - def __init__(self, mw: AnkiQt) -> None: - self.mw = mw - - MainWindowState = Literal[ "startup", "deckBrowser", "overview", "review", "resetRequired", "profileManager" ] @@ -194,6 +174,7 @@ class AnkiQt(QMainWindow): self.setupHooks() self.setup_timers() self.updateTitleBar() + self.setup_focus() # screens self.setupDeckBrowser() self.setupOverview() @@ -222,6 +203,12 @@ class AnkiQt(QMainWindow): "Shortcut to create a weak reference that doesn't break code completion." return weakref.proxy(self) # type: ignore + def setup_focus(self) -> None: + qconnect(self.app.focusChanged, self.on_focus_changed) + + def on_focus_changed(self, old: QWidget, new: QWidget) -> None: + gui_hooks.focus_did_change(new, old) + # Profiles ########################################################################## @@ -771,11 +758,32 @@ class AnkiQt(QMainWindow): setattr(op.changes, field.name, True) gui_hooks.operation_did_execute(op) + def on_operation_did_execute(self, op: OperationInfo) -> None: + "Notify current screen of changes." + focused = current_top_level_widget() == self + if self.state == "review": + self.reviewer.op_executed(op, focused) + elif self.state == "overview": + self.overview.op_executed(op, focused) + elif self.state == "deckBrowser": + self.deckBrowser.op_executed(op, focused) + + def on_focus_did_change( + self, new_focus: Optional[QWidget], _old: Optional[QWidget] + ) -> None: + "If main window has received focus, ensure current UI state is updated." + if new_focus and top_level_widget(new_focus) == self: + if self.state == "review": + self.reviewer.refresh_if_needed() + elif self.state == "overview": + self.overview.refresh_if_needed() + elif self.state == "deckBrowser": + self.deckBrowser.refresh_if_needed() + def reset(self, unused_arg: bool = False) -> None: """Legacy method of telling UI to refresh after changes made to DB. New code should use mw.perform_op() instead.""" - if self.col: # fire new `operation_did_execute` hook first. If the overview # or review screen are currently open, they will rebuild the study @@ -783,63 +791,26 @@ class AnkiQt(QMainWindow): self._synthesize_op_did_execute_from_reset() # fire the old reset hook gui_hooks.state_did_reset() - self.update_undo_actions() - # fixme: double-check - # self.moveToState(self.state) + # legacy def requireReset( self, modal: bool = False, - reason: ResetReason = ResetReason.Unknown, + reason: Any = None, context: Any = None, ) -> None: - "Signal queue needs to be rebuilt when edits are finished or by user." - self.autosave() - self.resetModal = modal - if gui_hooks.main_window_should_require_reset( - self.interactiveState(), reason, context - ): - self.moveToState("resetRequired") - - def interactiveState(self) -> bool: - "True if not in profile manager, syncing, etc." - return self.state in ("overview", "review", "deckBrowser") + self.reset() def maybeReset(self) -> None: - self.autosave() - if self.state == "resetRequired": - self.state = self.returnState - self.reset() + pass def delayedMaybeReset(self) -> None: - # if we redraw the page in a button click event it will often crash on - # windows - self.progress.timer(100, self.maybeReset, False) + pass def _resetRequiredState(self, oldState: MainWindowState) -> None: - if oldState != "resetRequired": - self.returnState = oldState - if self.resetModal: - # we don't have to change the webview, as we have a covering window - return - web_context = ResetRequired(self) - self.web.set_bridge_command(lambda url: self.delayedMaybeReset(), web_context) - i = tr(TR.QT_MISC_WAITING_FOR_EDITING_TO_FINISH) - b = self.button("refresh", tr(TR.QT_MISC_RESUME_NOW), id="resume") - self.web.stdHtml( - f""" -