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-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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
##########################################################################
|
||||
|
||||
|
128
qt/aqt/main.py
128
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"""
|
||||
<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()
|
||||
pass
|
||||
|
||||
# HTML helpers
|
||||
##########################################################################
|
||||
@ -1403,7 +1374,7 @@ title="%s" %s>%s</button>""" % (
|
||||
if elap > minutes * 60:
|
||||
self.maybe_auto_sync_media()
|
||||
|
||||
# Permanent libanki hooks
|
||||
# Permanent hooks
|
||||
##########################################################################
|
||||
|
||||
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_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
|
||||
|
||||
@ -1748,6 +1721,10 @@ title="%s" %s>%s</button>""" % (
|
||||
def _isAddon(self, buf: str) -> bool:
|
||||
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
|
||||
##########################################################################
|
||||
# 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:
|
||||
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.web = mw.web
|
||||
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:
|
||||
av_player.stop_and_clear_queue()
|
||||
@ -57,15 +57,19 @@ class Overview:
|
||||
self._renderBottom()
|
||||
self.mw.web.setFocus()
|
||||
gui_hooks.overview_did_refresh(self)
|
||||
self.refresh_needed = False
|
||||
|
||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
||||
if self.mw.state != "overview":
|
||||
return
|
||||
|
||||
if self.mw.col.op_affects_study_queue(op):
|
||||
# will also cover the deck description modified case
|
||||
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()
|
||||
|
||||
# Handlers
|
||||
############################################################
|
||||
|
||||
|
@ -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
|
||||
@ -14,6 +15,7 @@ from PyQt5.QtCore import Qt
|
||||
from anki import hooks
|
||||
from anki.cards import Card
|
||||
from anki.collection import Config, OperationInfo
|
||||
from anki.types import assert_exhaustive
|
||||
from anki.utils import stripHTML
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.profiles import VideoDriver
|
||||
@ -38,6 +40,14 @@ class ReviewerBottomBar:
|
||||
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:
|
||||
if question_side:
|
||||
av_player.play_tags(card.question_av_tags())
|
||||
@ -61,17 +71,17 @@ 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 = RefreshNeeded.NO
|
||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||
hooks.card_did_leech.append(self.onLeech)
|
||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||
|
||||
def show(self) -> None:
|
||||
self.mw.col.reset()
|
||||
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
|
||||
self.web.set_bridge_command(self._linkHandler, self)
|
||||
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
||||
self._reps: int = None
|
||||
self.nextCard()
|
||||
self.refresh_needed = RefreshNeeded.QUEUE
|
||||
self.refresh_if_needed()
|
||||
|
||||
def lastCard(self) -> Optional[Card]:
|
||||
if self._answeredIds:
|
||||
@ -87,26 +97,41 @@ class Reviewer:
|
||||
gui_hooks.reviewer_will_end()
|
||||
self.card = None
|
||||
|
||||
def on_operation_did_execute(self, op: OperationInfo) -> None:
|
||||
if self.mw.state != "review":
|
||||
def refresh_if_needed(self) -> None:
|
||||
if self.refresh_needed is RefreshNeeded.NO:
|
||||
return
|
||||
|
||||
if op.kind == OperationInfo.UPDATE_NOTE_TAGS:
|
||||
elif self.refresh_needed is RefreshNeeded.NOTE_MARK:
|
||||
self.card.load()
|
||||
self._update_mark_icon()
|
||||
elif op.kind == OperationInfo.SET_CARD_FLAG:
|
||||
elif self.refresh_needed is RefreshNeeded.CARD_FLAG:
|
||||
# fixme: v3 mtime check
|
||||
self.card.load()
|
||||
self._update_flag_icon()
|
||||
elif self.mw.col.op_affects_study_queue(op):
|
||||
# need queue rebuild
|
||||
elif self.refresh_needed is RefreshNeeded.QUEUE:
|
||||
self.mw.col.reset()
|
||||
self.nextCard()
|
||||
return
|
||||
elif op.changes.note or op.changes.notetype or op.changes.tag:
|
||||
# need redraw of current card
|
||||
elif self.refresh_needed is RefreshNeeded.CARD:
|
||||
self.card.load()
|
||||
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
|
||||
##########################################################################
|
||||
|
@ -729,6 +729,20 @@ def downArrow() -> str:
|
||||
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
|
||||
######################################################################
|
||||
|
||||
|
@ -24,7 +24,7 @@ from anki.cards import Card
|
||||
from anki.decks import Deck, DeckConfig
|
||||
from anki.hooks import runFilter, runHook
|
||||
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
|
||||
"""
|
||||
|
||||
@ -417,6 +417,15 @@ hooks = [
|
||||
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
|
||||
###################
|
||||
Hook(
|
||||
|
Loading…
Reference in New Issue
Block a user