mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Enable strict_optional for aqt/tagedit, utils, sync (#3578)
* Enable strict_optional for tagedit * Fix mypy errors * Enable strict_optional for utils * Fix mypy errors * Enable strict_optional for sync * Fix mypy errors --------- Co-authored-by: Abdo <abdo@abdnh.net>
This commit is contained in:
parent
29f714d973
commit
9d09c32ece
6 changed files with 111 additions and 49 deletions
|
@ -104,6 +104,12 @@ strict_optional = True
|
|||
strict_optional = True
|
||||
[mypy-aqt.progress]
|
||||
strict_optional = True
|
||||
[mypy-aqt.tagedit]
|
||||
strict_optional = True
|
||||
[mypy-aqt.utils]
|
||||
strict_optional = True
|
||||
[mypy-aqt.sync]
|
||||
strict_optional = True
|
||||
[mypy-anki.scheduler.base]
|
||||
strict_optional = True
|
||||
[mypy-anki._backend.rsbridge]
|
||||
|
|
|
@ -1128,7 +1128,7 @@ class Collection(DeprecatedNamesMixin):
|
|||
self._backend.abort_sync()
|
||||
|
||||
def full_upload_or_download(
|
||||
self, *, auth: SyncAuth, server_usn: int | None, upload: bool
|
||||
self, *, auth: SyncAuth | None, server_usn: int | None, upload: bool
|
||||
) -> None:
|
||||
self._backend.full_upload_or_download(
|
||||
sync_pb2.FullUploadOrDownloadRequest(
|
||||
|
|
|
@ -94,7 +94,7 @@ class NewDeckStats(QDialog):
|
|||
lambda _: self.refresh()
|
||||
).run_in_background()
|
||||
|
||||
def _imagePath(self) -> str:
|
||||
def _imagePath(self) -> str | None:
|
||||
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
|
||||
name = f"anki-{tr.statistics_stats()}{name}"
|
||||
file = getSaveFile(
|
||||
|
@ -196,7 +196,7 @@ class DeckStats(QDialog):
|
|||
self.reject()
|
||||
callback()
|
||||
|
||||
def _imagePath(self) -> str:
|
||||
def _imagePath(self) -> str | None:
|
||||
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
|
||||
name = f"anki-{tr.statistics_stats()}{name}"
|
||||
file = getSaveFile(
|
||||
|
|
|
@ -168,7 +168,7 @@ def full_sync(
|
|||
|
||||
|
||||
def confirm_full_download(
|
||||
mw: aqt.main.AnkiQt, server_usn: int, on_done: Callable[[], None]
|
||||
mw: aqt.main.AnkiQt, server_usn: int | None, on_done: Callable[[], None]
|
||||
) -> None:
|
||||
# confirmation step required, as some users customize their notetypes
|
||||
# in an empty collection, then want to upload them
|
||||
|
@ -184,7 +184,7 @@ def confirm_full_download(
|
|||
|
||||
|
||||
def confirm_full_upload(
|
||||
mw: aqt.main.AnkiQt, server_usn: int, on_done: Callable[[], None]
|
||||
mw: aqt.main.AnkiQt, server_usn: int | None, on_done: Callable[[], None]
|
||||
) -> None:
|
||||
# confirmation step required, as some users have reported an upload
|
||||
# happening despite having their AnkiWeb collection not being empty
|
||||
|
@ -220,7 +220,7 @@ def on_full_sync_timer(mw: aqt.main.AnkiQt, label: str) -> None:
|
|||
|
||||
|
||||
def full_download(
|
||||
mw: aqt.main.AnkiQt, server_usn: int, on_done: Callable[[], None]
|
||||
mw: aqt.main.AnkiQt, server_usn: int | None, on_done: Callable[[], None]
|
||||
) -> None:
|
||||
label = tr.sync_downloading_from_ankiweb()
|
||||
|
||||
|
@ -372,7 +372,9 @@ def get_id_and_pass_from_user(
|
|||
l2.setBuddy(passwd)
|
||||
vbox.addLayout(g)
|
||||
bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore
|
||||
bb.button(QDialogButtonBox.StandardButton.Ok).setAutoDefault(True)
|
||||
ok_button = bb.button(QDialogButtonBox.StandardButton.Ok)
|
||||
assert ok_button is not None
|
||||
ok_button.setAutoDefault(True)
|
||||
qconnect(bb.accepted, diag.accept)
|
||||
qconnect(bb.rejected, diag.reject)
|
||||
vbox.addWidget(bb)
|
||||
|
|
|
@ -42,13 +42,17 @@ class TagEdit(QLineEdit):
|
|||
l = (d.name for d in self.col.decks.all_names_and_ids())
|
||||
self.model.setStringList(l)
|
||||
|
||||
def focusInEvent(self, evt: QFocusEvent) -> None:
|
||||
def focusInEvent(self, evt: QFocusEvent | None) -> None:
|
||||
QLineEdit.focusInEvent(self, evt)
|
||||
|
||||
def keyPressEvent(self, evt: QKeyEvent) -> None:
|
||||
def keyPressEvent(self, evt: QKeyEvent | None) -> None:
|
||||
assert evt is not None
|
||||
popup = self._completer.popup()
|
||||
assert popup is not None
|
||||
|
||||
if evt.key() in (Qt.Key.Key_Up, Qt.Key.Key_Down):
|
||||
# show completer on arrow key up/down
|
||||
if not self._completer.popup().isVisible():
|
||||
if not popup.isVisible():
|
||||
self.showCompleter()
|
||||
return
|
||||
if (
|
||||
|
@ -56,24 +60,21 @@ class TagEdit(QLineEdit):
|
|||
and evt.modifiers() & Qt.KeyboardModifier.ControlModifier
|
||||
):
|
||||
# select next completion
|
||||
if not self._completer.popup().isVisible():
|
||||
if not popup.isVisible():
|
||||
self.showCompleter()
|
||||
index = self._completer.currentIndex()
|
||||
self._completer.popup().setCurrentIndex(index)
|
||||
popup.setCurrentIndex(index)
|
||||
cur_row = index.row()
|
||||
if not self._completer.setCurrentRow(cur_row + 1):
|
||||
self._completer.setCurrentRow(0)
|
||||
return
|
||||
if (
|
||||
evt.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return)
|
||||
and self._completer.popup().isVisible()
|
||||
):
|
||||
if evt.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return) and popup.isVisible():
|
||||
# apply first completion if no suggestion selected
|
||||
selected_row = self._completer.popup().currentIndex().row()
|
||||
selected_row = popup.currentIndex().row()
|
||||
if selected_row == -1:
|
||||
self._completer.setCurrentRow(0)
|
||||
index = self._completer.currentIndex()
|
||||
self._completer.popup().setCurrentIndex(index)
|
||||
popup.setCurrentIndex(index)
|
||||
self.hideCompleter()
|
||||
QWidget.keyPressEvent(self, evt)
|
||||
return
|
||||
|
@ -97,15 +98,19 @@ class TagEdit(QLineEdit):
|
|||
self._completer.setCompletionPrefix(self.text())
|
||||
self._completer.complete()
|
||||
|
||||
def focusOutEvent(self, evt: QFocusEvent) -> None:
|
||||
def focusOutEvent(self, evt: QFocusEvent | None) -> None:
|
||||
QLineEdit.focusOutEvent(self, evt)
|
||||
self.lostFocus.emit() # type: ignore
|
||||
self._completer.popup().hide()
|
||||
popup = self._completer.popup()
|
||||
assert popup is not None
|
||||
popup.hide()
|
||||
|
||||
def hideCompleter(self) -> None:
|
||||
if sip.isdeleted(self._completer): # type: ignore
|
||||
return
|
||||
self._completer.popup().hide()
|
||||
popup = self._completer.popup()
|
||||
assert popup is not None
|
||||
popup.hide()
|
||||
|
||||
|
||||
class TagCompleter(QCompleter):
|
||||
|
@ -120,7 +125,9 @@ class TagCompleter(QCompleter):
|
|||
self.edit = edit
|
||||
self.cursor: int | None = None
|
||||
|
||||
def splitPath(self, tags: str) -> list[str]:
|
||||
def splitPath(self, tags: str | None) -> list[str]:
|
||||
assert tags is not None
|
||||
assert self.edit.col is not None
|
||||
stripped_tags = tags.strip()
|
||||
stripped_tags = re.sub(" +", " ", stripped_tags)
|
||||
self.tags = self.edit.col.tags.split(stripped_tags)
|
||||
|
|
101
qt/aqt/utils.py
101
qt/aqt/utils.py
|
@ -118,10 +118,13 @@ HelpPageArgument = Union["HelpPage.V", str]
|
|||
|
||||
|
||||
def openHelp(section: HelpPageArgument) -> None:
|
||||
assert tr.backend is not None
|
||||
backend = tr.backend()
|
||||
assert backend is not None
|
||||
if isinstance(section, str):
|
||||
link = tr.backend().help_page_link(page=HelpPage.INDEX) + section
|
||||
link = backend.help_page_link(page=HelpPage.INDEX) + section
|
||||
else:
|
||||
link = tr.backend().help_page_link(page=section)
|
||||
link = backend.help_page_link(page=section)
|
||||
openLink(link)
|
||||
|
||||
|
||||
|
@ -170,17 +173,20 @@ class MessageBox(QMessageBox):
|
|||
b = self.addButton(button)
|
||||
# a translator has complained the default Qt translation is inappropriate, so we override it
|
||||
if button == QMessageBox.StandardButton.Discard:
|
||||
assert b is not None
|
||||
b.setText(tr.actions_discard())
|
||||
elif isinstance(button, tuple):
|
||||
b = self.addButton(button[0], button[1])
|
||||
else:
|
||||
continue
|
||||
if callback is not None:
|
||||
assert b is not None
|
||||
qconnect(b.clicked, partial(callback, i))
|
||||
if i == default_button:
|
||||
self.setDefaultButton(b)
|
||||
if help is not None:
|
||||
b = self.addButton(QMessageBox.StandardButton.Help)
|
||||
assert b is not None
|
||||
qconnect(b.clicked, lambda: openHelp(help))
|
||||
self.open()
|
||||
|
||||
|
@ -316,9 +322,11 @@ def showInfo(
|
|||
mb.setDefaultButton(default)
|
||||
else:
|
||||
b = mb.addButton(QMessageBox.StandardButton.Ok)
|
||||
assert b is not None
|
||||
b.setDefault(True)
|
||||
if help is not None:
|
||||
b = mb.addButton(QMessageBox.StandardButton.Help)
|
||||
assert b is not None
|
||||
qconnect(b.clicked, lambda: openHelp(help))
|
||||
b.setAutoDefault(False)
|
||||
return mb.exec()
|
||||
|
@ -363,7 +371,9 @@ def showText(
|
|||
if copyBtn:
|
||||
|
||||
def onCopy() -> None:
|
||||
QApplication.clipboard().setText(text.toPlainText())
|
||||
clipboard = QApplication.clipboard()
|
||||
assert clipboard is not None
|
||||
clipboard.setText(text.toPlainText())
|
||||
|
||||
btn = QPushButton(tr.qt_misc_copy_to_clipboard())
|
||||
qconnect(btn.clicked, onCopy)
|
||||
|
@ -415,6 +425,7 @@ def askUser(
|
|||
default = QMessageBox.StandardButton.Yes
|
||||
r = msgfunc(parent, title, text, sb, default)
|
||||
if r == QMessageBox.StandardButton.Help:
|
||||
assert help is not None
|
||||
openHelp(help)
|
||||
else:
|
||||
break
|
||||
|
@ -431,7 +442,7 @@ class ButtonedDialog(QMessageBox):
|
|||
title: str = "Anki",
|
||||
):
|
||||
QMessageBox.__init__(self, parent)
|
||||
self._buttons: list[QPushButton] = []
|
||||
self._buttons: list[QPushButton | None] = []
|
||||
self.setWindowTitle(title)
|
||||
self.help = help
|
||||
self.setIcon(QMessageBox.Icon.Warning)
|
||||
|
@ -444,11 +455,13 @@ class ButtonedDialog(QMessageBox):
|
|||
|
||||
def run(self) -> str:
|
||||
self.exec()
|
||||
but = self.clickedButton().text()
|
||||
if but == "Help":
|
||||
clicked_button = self.clickedButton()
|
||||
assert clicked_button is not None
|
||||
txt = clicked_button.text()
|
||||
if txt == "Help":
|
||||
# FIXME stop dialog closing?
|
||||
assert self.help is not None
|
||||
openHelp(self.help)
|
||||
txt = self.clickedButton().text()
|
||||
# work around KDE 'helpfully' adding accelerators to button text of Qt apps
|
||||
return txt.replace("&", "")
|
||||
|
||||
|
@ -504,13 +517,18 @@ class GetTextDialog(QDialog):
|
|||
b = QDialogButtonBox(buts) # type: ignore
|
||||
v.addWidget(b)
|
||||
self.setLayout(v)
|
||||
qconnect(b.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept)
|
||||
qconnect(b.button(QDialogButtonBox.StandardButton.Cancel).clicked, self.reject)
|
||||
ok_button = b.button(QDialogButtonBox.StandardButton.Ok)
|
||||
assert ok_button is not None
|
||||
qconnect(ok_button.clicked, self.accept)
|
||||
|
||||
cancel_button = b.button(QDialogButtonBox.StandardButton.Cancel)
|
||||
assert cancel_button is not None
|
||||
qconnect(cancel_button.clicked, self.reject)
|
||||
|
||||
if help:
|
||||
qconnect(
|
||||
b.button(QDialogButtonBox.StandardButton.Help).clicked,
|
||||
self.helpRequested,
|
||||
)
|
||||
help_button = b.button(QDialogButtonBox.StandardButton.Help)
|
||||
assert help_button is not None
|
||||
qconnect(help_button.clicked, self.helpRequested)
|
||||
self.l.setFocus()
|
||||
|
||||
def accept(self) -> None:
|
||||
|
@ -520,7 +538,8 @@ class GetTextDialog(QDialog):
|
|||
return QDialog.reject(self)
|
||||
|
||||
def helpRequested(self) -> None:
|
||||
openHelp(self.help)
|
||||
if self.help is not None:
|
||||
openHelp(self.help)
|
||||
|
||||
|
||||
def getText(
|
||||
|
@ -624,6 +643,7 @@ def getFile(
|
|||
if dir and key:
|
||||
raise Exception("expected dir or key")
|
||||
if not dir:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
dirkey = f"{key}Directory"
|
||||
dir = aqt.mw.pm.profile.get(dirkey, "")
|
||||
else:
|
||||
|
@ -635,6 +655,7 @@ def getFile(
|
|||
else QFileDialog.FileMode.ExistingFile
|
||||
)
|
||||
d.setFileMode(mode)
|
||||
assert dir is not None
|
||||
if os.path.exists(dir):
|
||||
d.setDirectory(dir)
|
||||
d.setWindowTitle(title)
|
||||
|
@ -644,6 +665,7 @@ def getFile(
|
|||
def accept() -> None:
|
||||
files = list(d.selectedFiles())
|
||||
if dirkey:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
dir = os.path.dirname(files[0])
|
||||
aqt.mw.pm.profile[dirkey] = dir
|
||||
result = files if multi else files[0]
|
||||
|
@ -683,10 +705,11 @@ def getSaveFile(
|
|||
dir_description: str,
|
||||
key: str,
|
||||
ext: str,
|
||||
fname: str | None = None,
|
||||
) -> str:
|
||||
fname: str = "",
|
||||
) -> str | None:
|
||||
"""Ask the user for a file to save. Use DIR_DESCRIPTION as config
|
||||
variable. The file dialog will default to open with FNAME."""
|
||||
assert aqt.mw.pm.profile is not None
|
||||
config_key = f"{dir_description}Directory"
|
||||
|
||||
defaultPath = QStandardPaths.writableLocation(
|
||||
|
@ -709,9 +732,10 @@ def getSaveFile(
|
|||
dir = os.path.dirname(file)
|
||||
aqt.mw.pm.profile[config_key] = dir
|
||||
# check if it exists
|
||||
if os.path.exists(file):
|
||||
if not askUser(tr.qt_misc_this_file_exists_are_you_sure(), parent):
|
||||
return None
|
||||
if os.path.exists(file) and not askUser(
|
||||
tr.qt_misc_this_file_exists_are_you_sure(), parent
|
||||
):
|
||||
return None
|
||||
return file
|
||||
|
||||
|
||||
|
@ -735,6 +759,7 @@ def _qt_state_key(kind: _QtStateKeyKind, key: str) -> str:
|
|||
def saveGeom(widget: QWidget, key: str) -> None:
|
||||
# restoring a fullscreen window breaks the tab functionality of 5.15
|
||||
if not widget.isFullScreen() or qtmajor == 6:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.GEOMETRY, key)
|
||||
aqt.mw.pm.profile[key] = widget.saveGeometry()
|
||||
|
||||
|
@ -745,6 +770,7 @@ def restoreGeom(
|
|||
adjustSize: bool = False,
|
||||
default_size: tuple[int, int] | None = None,
|
||||
) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.GEOMETRY, key)
|
||||
if existing_geom := aqt.mw.pm.profile.get(key):
|
||||
widget.restoreGeometry(existing_geom)
|
||||
|
@ -756,7 +782,9 @@ def restoreGeom(
|
|||
|
||||
|
||||
def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
|
||||
handle = widget.window().windowHandle()
|
||||
window = widget.window()
|
||||
assert window is not None
|
||||
handle = window.windowHandle()
|
||||
if not handle:
|
||||
# window has not yet been shown, retry later
|
||||
aqt.mw.progress.timer(
|
||||
|
@ -765,7 +793,9 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
|
|||
return
|
||||
|
||||
# ensure widget is smaller than screen bounds
|
||||
geom = handle.screen().availableGeometry()
|
||||
screen = handle.screen()
|
||||
assert screen is not None
|
||||
geom = screen.availableGeometry()
|
||||
wsize = widget.size()
|
||||
cappedWidth = min(geom.width(), wsize.width())
|
||||
cappedHeight = min(geom.height(), wsize.height())
|
||||
|
@ -784,44 +814,52 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
|
|||
|
||||
|
||||
def saveState(widget: QFileDialog | QMainWindow, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.STATE, key)
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
|
||||
def restoreState(widget: QFileDialog | QMainWindow, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.STATE, key)
|
||||
if data := aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(data)
|
||||
|
||||
|
||||
def saveSplitter(widget: QSplitter, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.SPLITTER, key)
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
|
||||
def restoreSplitter(widget: QSplitter, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.SPLITTER, key)
|
||||
if data := aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(data)
|
||||
|
||||
|
||||
def saveHeader(widget: QHeaderView, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.HEADER, key)
|
||||
aqt.mw.pm.profile[key] = widget.saveState()
|
||||
|
||||
|
||||
def restoreHeader(widget: QHeaderView, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key = _qt_state_key(_QtStateKeyKind.HEADER, key)
|
||||
if state := aqt.mw.pm.profile.get(key):
|
||||
widget.restoreState(state)
|
||||
|
||||
|
||||
def save_is_checked(widget: QCheckBox, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key += "IsChecked"
|
||||
aqt.mw.pm.profile[key] = widget.isChecked()
|
||||
|
||||
|
||||
def restore_is_checked(widget: QCheckBox, key: str) -> None:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
key += "IsChecked"
|
||||
if aqt.mw.pm.profile.get(key) is not None:
|
||||
widget.setChecked(aqt.mw.pm.profile[key])
|
||||
|
@ -847,8 +885,11 @@ def restore_combo_index_for_session(
|
|||
|
||||
|
||||
def save_combo_history(comboBox: QComboBox, history: list[str], name: str) -> str:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
name += "BoxHistory"
|
||||
text_input = comboBox.lineEdit().text()
|
||||
line_edit = comboBox.lineEdit()
|
||||
assert line_edit is not None
|
||||
text_input = line_edit.text()
|
||||
if text_input in history:
|
||||
history.remove(text_input)
|
||||
history.insert(0, text_input)
|
||||
|
@ -861,14 +902,17 @@ def save_combo_history(comboBox: QComboBox, history: list[str], name: str) -> st
|
|||
|
||||
|
||||
def restore_combo_history(comboBox: QComboBox, name: str) -> list[str]:
|
||||
assert aqt.mw.pm.profile is not None
|
||||
name += "BoxHistory"
|
||||
history = aqt.mw.pm.profile.get(name, [])
|
||||
comboBox.addItems([""] + history)
|
||||
if history:
|
||||
session_input = aqt.mw.pm.session.get(name)
|
||||
if session_input and session_input == history[0]:
|
||||
comboBox.lineEdit().setText(session_input)
|
||||
comboBox.lineEdit().selectAll()
|
||||
line_edit = comboBox.lineEdit()
|
||||
assert line_edit is not None
|
||||
line_edit.setText(session_input)
|
||||
line_edit.selectAll()
|
||||
return history
|
||||
|
||||
|
||||
|
@ -980,7 +1024,7 @@ def send_to_trash(path: Path) -> None:
|
|||
except Exception as exc:
|
||||
# Linux users may not have a trash folder set up
|
||||
print("trash failure:", path, exc)
|
||||
if path.is_dir:
|
||||
if path.is_dir():
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
path.unlink()
|
||||
|
@ -1005,7 +1049,8 @@ def tooltip(
|
|||
class CustomLabel(QLabel):
|
||||
silentlyClose = True
|
||||
|
||||
def mousePressEvent(self, evt: QMouseEvent) -> None:
|
||||
def mousePressEvent(self, evt: QMouseEvent | None) -> None:
|
||||
assert evt is not None
|
||||
evt.accept()
|
||||
self.hide()
|
||||
|
||||
|
@ -1074,7 +1119,7 @@ class MenuList:
|
|||
print(
|
||||
"MenuList will be removed; please copy it into your add-on's code if you need it."
|
||||
)
|
||||
self.children: list[MenuListChild] = []
|
||||
self.children: list[MenuListChild | None] = []
|
||||
|
||||
def addItem(self, title: str, func: Callable) -> MenuItem:
|
||||
item = MenuItem(title, func)
|
||||
|
@ -1114,6 +1159,7 @@ class SubMenu(MenuList):
|
|||
|
||||
def renderTo(self, menu: QMenu) -> None:
|
||||
submenu = menu.addMenu(self.title)
|
||||
assert submenu is not None
|
||||
super().renderTo(submenu)
|
||||
|
||||
|
||||
|
@ -1124,6 +1170,7 @@ class MenuItem:
|
|||
|
||||
def renderTo(self, qmenu: QMenu) -> None:
|
||||
a = qmenu.addAction(self.title)
|
||||
assert a is not None
|
||||
qconnect(a.triggered, self.func)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue