experiment with replacing requireReset with updates on focus-in
- This avoids the need for a separate screen, though we may want to slightly fade out the display when information is stale. - Means the browser can delay updates just like the main window does.
This commit is contained in:
parent
1e849316be
commit
0a5be6543e
@ -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-please-use-fileimport-to-import-this = Please use File>Import to import this file.
|
||||||
qt-misc-processing = Processing...
|
qt-misc-processing = Processing...
|
||||||
qt-misc-replace-your-collection-with-an-earlier = Replace your collection with an earlier backup?
|
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-revert-to-backup = Revert to backup
|
||||||
qt-misc-reverted-to-state-prior-to = Reverted to state prior to '{ $val }'.
|
qt-misc-reverted-to-state-prior-to = Reverted to state prior to '{ $val }'.
|
||||||
qt-misc-segoe-ui = "Segoe UI"
|
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-undo = Undo
|
||||||
qt-misc-undo2 = Undo { $val }
|
qt-misc-undo2 = Undo { $val }
|
||||||
qt-misc-unexpected-response-code = Unexpected response code: { $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-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-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.
|
qt-misc-your-computers-storage-may-be-full = Your computer's storage may be full. Please delete some unneeded files, then try again.
|
||||||
|
@ -34,6 +34,7 @@ from aqt.utils import (
|
|||||||
TR,
|
TR,
|
||||||
HelpPage,
|
HelpPage,
|
||||||
askUser,
|
askUser,
|
||||||
|
current_top_level_widget,
|
||||||
disable_help_button,
|
disable_help_button,
|
||||||
getTag,
|
getTag,
|
||||||
openHelp,
|
openHelp,
|
||||||
@ -91,6 +92,7 @@ class DataModel(QAbstractTableModel):
|
|||||||
)
|
)
|
||||||
self.cards: Sequence[int] = []
|
self.cards: Sequence[int] = []
|
||||||
self.cardObjs: Dict[int, Card] = {}
|
self.cardObjs: Dict[int, Card] = {}
|
||||||
|
self.refresh_needed = False
|
||||||
|
|
||||||
def getCard(self, index: QModelIndex) -> Optional[Card]:
|
def getCard(self, index: QModelIndex) -> Optional[Card]:
|
||||||
id = self.cards[index.row()]
|
id = self.cards[index.row()]
|
||||||
@ -203,6 +205,7 @@ class DataModel(QAbstractTableModel):
|
|||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
self.beginReset()
|
self.beginReset()
|
||||||
self.endReset()
|
self.endReset()
|
||||||
|
self.refresh_needed = False
|
||||||
|
|
||||||
# caller must have called editor.saveNow() before calling this or .reset()
|
# caller must have called editor.saveNow() before calling this or .reset()
|
||||||
def beginReset(self) -> None:
|
def beginReset(self) -> None:
|
||||||
@ -281,8 +284,14 @@ class DataModel(QAbstractTableModel):
|
|||||||
else:
|
else:
|
||||||
tv.selectRow(0)
|
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:
|
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()
|
self.reset()
|
||||||
|
|
||||||
# Column data
|
# Column data
|
||||||
@ -490,7 +499,11 @@ class Browser(QMainWindow):
|
|||||||
|
|
||||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
||||||
self.setUpdatesEnabled(True)
|
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:
|
def setupMenus(self) -> None:
|
||||||
# pylint: disable=unnecessary-lambda
|
# pylint: disable=unnecessary-lambda
|
||||||
@ -1415,7 +1428,6 @@ where id in %s"""
|
|||||||
|
|
||||||
def setupHooks(self) -> None:
|
def setupHooks(self) -> None:
|
||||||
gui_hooks.undo_state_did_change.append(self.onUndoState)
|
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_fire_typing_timer.append(self.refreshCurrentCard)
|
||||||
gui_hooks.editor_did_load_note.append(self.onLoadNote)
|
gui_hooks.editor_did_load_note.append(self.onLoadNote)
|
||||||
gui_hooks.editor_did_unfocus_field.append(self.on_unfocus_field)
|
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.sidebar_should_refresh_notetypes.append(self.on_item_added)
|
||||||
gui_hooks.operation_will_execute.append(self.on_operation_will_execute)
|
gui_hooks.operation_will_execute.append(self.on_operation_will_execute)
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_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:
|
def teardownHooks(self) -> None:
|
||||||
gui_hooks.undo_state_did_change.remove(self.onUndoState)
|
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_fire_typing_timer.remove(self.refreshCurrentCard)
|
||||||
gui_hooks.editor_did_load_note.remove(self.onLoadNote)
|
gui_hooks.editor_did_load_note.remove(self.onLoadNote)
|
||||||
gui_hooks.editor_did_unfocus_field.remove(self.on_unfocus_field)
|
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.sidebar_should_refresh_notetypes.remove(self.on_item_added)
|
||||||
gui_hooks.operation_will_execute.remove(self.on_operation_will_execute)
|
gui_hooks.operation_will_execute.remove(self.on_operation_will_execute)
|
||||||
gui_hooks.operation_did_execute.remove(self.on_operation_did_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:
|
def on_unfocus_field(self, changed: bool, note: Note, field_idx: int) -> None:
|
||||||
self.refreshCurrentCard(note)
|
self.refreshCurrentCard(note)
|
||||||
|
@ -62,7 +62,7 @@ class DeckBrowser:
|
|||||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
self.scrollPos = QPoint(0, 0)
|
self.scrollPos = QPoint(0, 0)
|
||||||
self._v1_message_dismissed_at = 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:
|
def show(self) -> None:
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
@ -70,17 +70,23 @@ class DeckBrowser:
|
|||||||
self._renderPage()
|
self._renderPage()
|
||||||
# redraw top bar for theme change
|
# redraw top bar for theme change
|
||||||
self.mw.toolbar.redraw()
|
self.mw.toolbar.redraw()
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
self._renderPage()
|
self._renderPage()
|
||||||
|
self.refresh_needed = False
|
||||||
|
|
||||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
def refresh_if_needed(self) -> None:
|
||||||
if self.mw.state != "deckBrowser":
|
if self.refresh_needed:
|
||||||
return
|
|
||||||
|
|
||||||
if self.mw.col.op_affects_study_queue(op):
|
|
||||||
self.refresh()
|
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
|
# Event handlers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
130
qt/aqt/main.py
130
qt/aqt/main.py
@ -72,6 +72,7 @@ from aqt.utils import (
|
|||||||
HelpPage,
|
HelpPage,
|
||||||
askUser,
|
askUser,
|
||||||
checkInvalidFilename,
|
checkInvalidFilename,
|
||||||
|
current_top_level_widget,
|
||||||
disable_help_button,
|
disable_help_button,
|
||||||
getFile,
|
getFile,
|
||||||
getOnlyText,
|
getOnlyText,
|
||||||
@ -85,6 +86,7 @@ from aqt.utils import (
|
|||||||
showInfo,
|
showInfo,
|
||||||
showWarning,
|
showWarning,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
top_level_widget,
|
||||||
tr,
|
tr,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,28 +94,6 @@ T = TypeVar("T")
|
|||||||
|
|
||||||
install_pylib_legacy()
|
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[
|
MainWindowState = Literal[
|
||||||
"startup", "deckBrowser", "overview", "review", "resetRequired", "profileManager"
|
"startup", "deckBrowser", "overview", "review", "resetRequired", "profileManager"
|
||||||
]
|
]
|
||||||
@ -194,6 +174,7 @@ class AnkiQt(QMainWindow):
|
|||||||
self.setupHooks()
|
self.setupHooks()
|
||||||
self.setup_timers()
|
self.setup_timers()
|
||||||
self.updateTitleBar()
|
self.updateTitleBar()
|
||||||
|
self.setup_focus()
|
||||||
# screens
|
# screens
|
||||||
self.setupDeckBrowser()
|
self.setupDeckBrowser()
|
||||||
self.setupOverview()
|
self.setupOverview()
|
||||||
@ -222,6 +203,12 @@ class AnkiQt(QMainWindow):
|
|||||||
"Shortcut to create a weak reference that doesn't break code completion."
|
"Shortcut to create a weak reference that doesn't break code completion."
|
||||||
return weakref.proxy(self) # type: ignore
|
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
|
# Profiles
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
@ -771,11 +758,32 @@ class AnkiQt(QMainWindow):
|
|||||||
setattr(op.changes, field.name, True)
|
setattr(op.changes, field.name, True)
|
||||||
gui_hooks.operation_did_execute(op)
|
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:
|
def reset(self, unused_arg: bool = False) -> None:
|
||||||
"""Legacy method of telling UI to refresh after changes made to DB.
|
"""Legacy method of telling UI to refresh after changes made to DB.
|
||||||
|
|
||||||
New code should use mw.perform_op() instead."""
|
New code should use mw.perform_op() instead."""
|
||||||
|
|
||||||
if self.col:
|
if self.col:
|
||||||
# fire new `operation_did_execute` hook first. If the overview
|
# fire new `operation_did_execute` hook first. If the overview
|
||||||
# or review screen are currently open, they will rebuild the study
|
# 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()
|
self._synthesize_op_did_execute_from_reset()
|
||||||
# fire the old reset hook
|
# fire the old reset hook
|
||||||
gui_hooks.state_did_reset()
|
gui_hooks.state_did_reset()
|
||||||
|
|
||||||
self.update_undo_actions()
|
self.update_undo_actions()
|
||||||
|
|
||||||
# fixme: double-check
|
# legacy
|
||||||
# self.moveToState(self.state)
|
|
||||||
|
|
||||||
def requireReset(
|
def requireReset(
|
||||||
self,
|
self,
|
||||||
modal: bool = False,
|
modal: bool = False,
|
||||||
reason: ResetReason = ResetReason.Unknown,
|
reason: Any = None,
|
||||||
context: Any = None,
|
context: Any = None,
|
||||||
) -> 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")
|
|
||||||
|
|
||||||
def maybeReset(self) -> None:
|
|
||||||
self.autosave()
|
|
||||||
if self.state == "resetRequired":
|
|
||||||
self.state = self.returnState
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
def maybeReset(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
def delayedMaybeReset(self) -> None:
|
def delayedMaybeReset(self) -> None:
|
||||||
# if we redraw the page in a button click event it will often crash on
|
pass
|
||||||
# windows
|
|
||||||
self.progress.timer(100, self.maybeReset, False)
|
|
||||||
|
|
||||||
def _resetRequiredState(self, oldState: MainWindowState) -> None:
|
def _resetRequiredState(self, oldState: MainWindowState) -> None:
|
||||||
if oldState != "resetRequired":
|
pass
|
||||||
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"""
|
|
||||||
<center><div style="height: 100%">
|
|
||||||
<div style="position:relative; vertical-align: middle;">
|
|
||||||
{i}<br><br>
|
|
||||||
{b}</div></div></center>
|
|
||||||
<script>$('#resume').focus()</script>
|
|
||||||
""",
|
|
||||||
context=web_context,
|
|
||||||
)
|
|
||||||
self.bottomWeb.hide()
|
|
||||||
self.web.setFocus()
|
|
||||||
|
|
||||||
# HTML helpers
|
# HTML helpers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@ -1403,7 +1374,7 @@ title="%s" %s>%s</button>""" % (
|
|||||||
if elap > minutes * 60:
|
if elap > minutes * 60:
|
||||||
self.maybe_auto_sync_media()
|
self.maybe_auto_sync_media()
|
||||||
|
|
||||||
# Permanent libanki hooks
|
# Permanent hooks
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def setupHooks(self) -> None:
|
def setupHooks(self) -> None:
|
||||||
@ -1413,6 +1384,8 @@ title="%s" %s>%s</button>""" % (
|
|||||||
|
|
||||||
gui_hooks.av_player_will_play.append(self.on_av_player_will_play)
|
gui_hooks.av_player_will_play.append(self.on_av_player_will_play)
|
||||||
gui_hooks.av_player_did_end_playing.append(self.on_av_player_did_end_playing)
|
gui_hooks.av_player_did_end_playing.append(self.on_av_player_did_end_playing)
|
||||||
|
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||||
|
gui_hooks.focus_did_change.append(self.on_focus_did_change)
|
||||||
|
|
||||||
self._activeWindowOnPlay: Optional[QWidget] = None
|
self._activeWindowOnPlay: Optional[QWidget] = None
|
||||||
|
|
||||||
@ -1748,6 +1721,10 @@ title="%s" %s>%s</button>""" % (
|
|||||||
def _isAddon(self, buf: str) -> bool:
|
def _isAddon(self, buf: str) -> bool:
|
||||||
return buf.endswith(self.addonManager.ext)
|
return buf.endswith(self.addonManager.ext)
|
||||||
|
|
||||||
|
def interactiveState(self) -> bool:
|
||||||
|
"True if not in profile manager, syncing, etc."
|
||||||
|
return self.state in ("overview", "review", "deckBrowser")
|
||||||
|
|
||||||
# GC
|
# GC
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# The default Python garbage collection can trigger on any thread. This can
|
# The default Python garbage collection can trigger on any thread. This can
|
||||||
@ -1803,3 +1780,20 @@ title="%s" %s>%s</button>""" % (
|
|||||||
|
|
||||||
def serverURL(self) -> str:
|
def serverURL(self) -> str:
|
||||||
return "http://127.0.0.1:%d/" % self.mediaServer.getPort()
|
return "http://127.0.0.1:%d/" % self.mediaServer.getPort()
|
||||||
|
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
@ -43,7 +43,7 @@ class Overview:
|
|||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.web = mw.web
|
self.web = mw.web
|
||||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
self.refresh_needed = False
|
||||||
|
|
||||||
def show(self) -> None:
|
def show(self) -> None:
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
@ -57,15 +57,19 @@ class Overview:
|
|||||||
self._renderBottom()
|
self._renderBottom()
|
||||||
self.mw.web.setFocus()
|
self.mw.web.setFocus()
|
||||||
gui_hooks.overview_did_refresh(self)
|
gui_hooks.overview_did_refresh(self)
|
||||||
|
self.refresh_needed = False
|
||||||
|
|
||||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
def refresh_if_needed(self) -> None:
|
||||||
if self.mw.state != "overview":
|
if self.refresh_needed:
|
||||||
return
|
|
||||||
|
|
||||||
if self.mw.col.op_affects_study_queue(op):
|
|
||||||
# will also cover the deck description modified case
|
|
||||||
self.refresh()
|
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()
|
||||||
|
|
||||||
# Handlers
|
# Handlers
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import html
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import unicodedata as ucd
|
import unicodedata as ucd
|
||||||
|
from enum import Enum, auto
|
||||||
from typing import Any, Callable, List, Match, Optional, Sequence, Tuple, Union
|
from typing import Any, Callable, List, Match, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
@ -14,6 +15,7 @@ from PyQt5.QtCore import Qt
|
|||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.collection import Config, OperationInfo
|
from anki.collection import Config, OperationInfo
|
||||||
|
from anki.types import assert_exhaustive
|
||||||
from anki.utils import stripHTML
|
from anki.utils import stripHTML
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.profiles import VideoDriver
|
from aqt.profiles import VideoDriver
|
||||||
@ -38,6 +40,14 @@ class ReviewerBottomBar:
|
|||||||
self.reviewer = reviewer
|
self.reviewer = reviewer
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshNeeded(Enum):
|
||||||
|
NO = auto()
|
||||||
|
NOTE_MARK = auto()
|
||||||
|
CARD_FLAG = auto()
|
||||||
|
QUEUE = auto()
|
||||||
|
CARD = auto()
|
||||||
|
|
||||||
|
|
||||||
def replay_audio(card: Card, question_side: bool) -> None:
|
def replay_audio(card: Card, question_side: bool) -> None:
|
||||||
if question_side:
|
if question_side:
|
||||||
av_player.play_tags(card.question_av_tags())
|
av_player.play_tags(card.question_av_tags())
|
||||||
@ -61,17 +71,17 @@ class Reviewer:
|
|||||||
self._recordedAudio: Optional[str] = None
|
self._recordedAudio: Optional[str] = None
|
||||||
self.typeCorrect: str = None # web init happens before this is set
|
self.typeCorrect: str = None # web init happens before this is set
|
||||||
self.state: Optional[str] = None
|
self.state: Optional[str] = None
|
||||||
|
self.refresh_needed = RefreshNeeded.NO
|
||||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
hooks.card_did_leech.append(self.onLeech)
|
hooks.card_did_leech.append(self.onLeech)
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
|
||||||
|
|
||||||
def show(self) -> None:
|
def show(self) -> None:
|
||||||
self.mw.col.reset()
|
|
||||||
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
|
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
|
||||||
self.web.set_bridge_command(self._linkHandler, self)
|
self.web.set_bridge_command(self._linkHandler, self)
|
||||||
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
||||||
self._reps: int = None
|
self._reps: int = None
|
||||||
self.nextCard()
|
self.refresh_needed = RefreshNeeded.QUEUE
|
||||||
|
self.refresh_if_needed()
|
||||||
|
|
||||||
def lastCard(self) -> Optional[Card]:
|
def lastCard(self) -> Optional[Card]:
|
||||||
if self._answeredIds:
|
if self._answeredIds:
|
||||||
@ -87,26 +97,41 @@ class Reviewer:
|
|||||||
gui_hooks.reviewer_will_end()
|
gui_hooks.reviewer_will_end()
|
||||||
self.card = None
|
self.card = None
|
||||||
|
|
||||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
def refresh_if_needed(self) -> None:
|
||||||
if self.mw.state != "review":
|
if self.refresh_needed is RefreshNeeded.NO:
|
||||||
return
|
return
|
||||||
|
elif self.refresh_needed is RefreshNeeded.NOTE_MARK:
|
||||||
if op.kind == OperationInfo.UPDATE_NOTE_TAGS:
|
|
||||||
self.card.load()
|
self.card.load()
|
||||||
self._update_mark_icon()
|
self._update_mark_icon()
|
||||||
elif op.kind == OperationInfo.SET_CARD_FLAG:
|
elif self.refresh_needed is RefreshNeeded.CARD_FLAG:
|
||||||
# fixme: v3 mtime check
|
# fixme: v3 mtime check
|
||||||
self.card.load()
|
self.card.load()
|
||||||
self._update_flag_icon()
|
self._update_flag_icon()
|
||||||
elif self.mw.col.op_affects_study_queue(op):
|
elif self.refresh_needed is RefreshNeeded.QUEUE:
|
||||||
# need queue rebuild
|
|
||||||
self.mw.col.reset()
|
self.mw.col.reset()
|
||||||
self.nextCard()
|
self.nextCard()
|
||||||
return
|
elif self.refresh_needed is RefreshNeeded.CARD:
|
||||||
elif op.changes.note or op.changes.notetype or op.changes.tag:
|
|
||||||
# need redraw of current card
|
|
||||||
self.card.load()
|
self.card.load()
|
||||||
self._showQuestion()
|
self._showQuestion()
|
||||||
|
else:
|
||||||
|
assert_exhaustive(self.refresh_needed)
|
||||||
|
|
||||||
|
self.refresh_needed = RefreshNeeded.NO
|
||||||
|
|
||||||
|
def op_executed(self, op: OperationInfo, focused: bool) -> None:
|
||||||
|
if op.kind == OperationInfo.UPDATE_NOTE_TAGS:
|
||||||
|
self.refresh_needed = RefreshNeeded.NOTE_MARK
|
||||||
|
elif op.kind == OperationInfo.SET_CARD_FLAG:
|
||||||
|
self.refresh_needed = RefreshNeeded.CARD_FLAG
|
||||||
|
elif self.mw.col.op_affects_study_queue(op):
|
||||||
|
self.refresh_needed = RefreshNeeded.QUEUE
|
||||||
|
elif op.changes.note or op.changes.notetype or op.changes.tag:
|
||||||
|
self.refresh_needed = RefreshNeeded.CARD
|
||||||
|
else:
|
||||||
|
self.refresh_needed = RefreshNeeded.NO
|
||||||
|
|
||||||
|
if focused:
|
||||||
|
self.refresh_if_needed()
|
||||||
|
|
||||||
# Fetching a card
|
# Fetching a card
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -729,6 +729,20 @@ def downArrow() -> str:
|
|||||||
return "▾"
|
return "▾"
|
||||||
|
|
||||||
|
|
||||||
|
def top_level_widget(widget: QWidget) -> QWidget:
|
||||||
|
window = None
|
||||||
|
while widget := widget.parent():
|
||||||
|
window = widget
|
||||||
|
return window
|
||||||
|
|
||||||
|
|
||||||
|
def current_top_level_widget() -> Optional[QWidget]:
|
||||||
|
if widget := QApplication.focusWidget():
|
||||||
|
return top_level_widget(widget)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Tooltips
|
# Tooltips
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from anki.cards import Card
|
|||||||
from anki.decks import Deck, DeckConfig
|
from anki.decks import Deck, DeckConfig
|
||||||
from anki.hooks import runFilter, runHook
|
from anki.hooks import runFilter, runHook
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from aqt.qt import QDialog, QEvent, QMenu
|
from aqt.qt import QDialog, QEvent, QMenu, QWidget
|
||||||
from aqt.tagedit import TagEdit
|
from aqt.tagedit import TagEdit
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -417,6 +417,15 @@ hooks = [
|
|||||||
mw.reset(), `operation_will_execute` will not be called.
|
mw.reset(), `operation_will_execute` will not be called.
|
||||||
""",
|
""",
|
||||||
),
|
),
|
||||||
|
Hook(
|
||||||
|
name="focus_did_change",
|
||||||
|
args=[
|
||||||
|
"new: Optional[QWidget]",
|
||||||
|
"old: Optional[QWidget]",
|
||||||
|
],
|
||||||
|
doc="""Called each time the focus changes. Can be used to defer updates from
|
||||||
|
`operation_did_execute` until a window is brought to the front.""",
|
||||||
|
),
|
||||||
# Webview
|
# Webview
|
||||||
###################
|
###################
|
||||||
Hook(
|
Hook(
|
||||||
|
Loading…
Reference in New Issue
Block a user