add types to utils.py

The function signatures for things like getFile() are awful, but
sadly are used by a bunch of add-ons.
This commit is contained in:
Damien Elmes 2021-02-01 20:23:48 +10:00
parent 08b76e4489
commit 98f4b3db81
8 changed files with 210 additions and 121 deletions

View File

@ -867,9 +867,10 @@ class AddonsDialog(QDialog):
def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]: def onInstallFiles(self, paths: Optional[List[str]] = None) -> Optional[bool]:
if not paths: if not paths:
key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + " (*{})".format(self.mgr.ext) key = tr(TR.ADDONS_PACKAGED_ANKI_ADDON) + " (*{})".format(self.mgr.ext)
paths = getFile( paths_ = getFile(
self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True self, tr(TR.ADDONS_INSTALL_ADDONS), None, key, key="addons", multi=True
) )
paths = paths_ # type: ignore
if not paths: if not paths:
return False return False

View File

@ -607,14 +607,14 @@ class CardLayout(QDialog):
n = len(self.templates) n = len(self.templates)
template = self.current_template() template = self.current_template()
current_pos = self.templates.index(template) + 1 current_pos = self.templates.index(template) + 1
pos = getOnlyText( pos_txt = getOnlyText(
tr(TR.CARD_TEMPLATES_ENTER_NEW_CARD_POSITION_1, val=n), tr(TR.CARD_TEMPLATES_ENTER_NEW_CARD_POSITION_1, val=n),
default=str(current_pos), default=str(current_pos),
) )
if not pos: if not pos_txt:
return return
try: try:
pos = int(pos) pos = int(pos_txt)
except ValueError: except ValueError:
return return
if pos < 1 or pos > n: if pos < 1 or pos > n:

View File

@ -256,7 +256,8 @@ class DeckBrowser:
self.mw.col.decks.rename(deck, newName) self.mw.col.decks.rename(deck, newName)
gui_hooks.sidebar_should_refresh_decks() gui_hooks.sidebar_should_refresh_decks()
except DeckRenameError as e: except DeckRenameError as e:
return showWarning(e.description) showWarning(e.description)
return
self.show() self.show()
def _options(self, did): def _options(self, did):

View File

@ -12,7 +12,7 @@ import urllib.parse
import urllib.request import urllib.request
import warnings import warnings
from random import randrange from random import randrange
from typing import Any, Callable, Dict, List, Match, Optional, Tuple from typing import Any, Callable, Dict, List, Match, Optional, Tuple, cast
import bs4 import bs4
import requests import requests
@ -750,7 +750,13 @@ class Editor:
def accept(file: str) -> None: def accept(file: str) -> None:
self.addMedia(file, canDelete=True) self.addMedia(file, canDelete=True)
file = getFile(self.widget, tr(TR.EDITING_ADD_MEDIA), accept, key, key="media") file = getFile(
self.widget,
tr(TR.EDITING_ADD_MEDIA),
cast(Callable[[Any], None], accept),
key,
key="media",
)
self.parentWindow.activateWindow() self.parentWindow.activateWindow()
def addMedia(self, path: str, canDelete: bool = False) -> None: def addMedia(self, path: str, canDelete: bool = False) -> None:

View File

@ -1530,14 +1530,15 @@ title="%s" %s>%s</button>""" % (
def setupAppMsg(self) -> None: def setupAppMsg(self) -> None:
qconnect(self.app.appMsg, self.onAppMsg) qconnect(self.app.appMsg, self.onAppMsg)
def onAppMsg(self, buf: str) -> Optional[QTimer]: def onAppMsg(self, buf: str) -> None:
is_addon = self._isAddon(buf) is_addon = self._isAddon(buf)
if self.state == "startup": if self.state == "startup":
# try again in a second # try again in a second
return self.progress.timer( self.progress.timer(
1000, lambda: self.onAppMsg(buf), False, requiresCollection=False 1000, lambda: self.onAppMsg(buf), False, requiresCollection=False
) )
return
elif self.state == "profileManager": elif self.state == "profileManager":
# can't raise window while in profile manager # can't raise window while in profile manager
if buf == "raise": if buf == "raise":
@ -1547,7 +1548,8 @@ title="%s" %s>%s</button>""" % (
msg = tr(TR.QT_MISC_ADDON_WILL_BE_INSTALLED_WHEN_A) msg = tr(TR.QT_MISC_ADDON_WILL_BE_INSTALLED_WHEN_A)
else: else:
msg = tr(TR.QT_MISC_DECK_WILL_BE_IMPORTED_WHEN_A) msg = tr(TR.QT_MISC_DECK_WILL_BE_IMPORTED_WHEN_A)
return tooltip(msg) tooltip(msg)
return
if not self.interactiveState() or self.progress.busy(): if not self.interactiveState() or self.progress.busy():
# we can't raise the main window while in profile dialog, syncing, etc # we can't raise the main window while in profile dialog, syncing, etc
if buf != "raise": if buf != "raise":

View File

@ -696,7 +696,8 @@ class SidebarTreeView(QTreeView):
try: try:
self.mw.col.decks.rename(deck, new_name) self.mw.col.decks.rename(deck, new_name)
except DeckRenameError as e: except DeckRenameError as e:
return showWarning(e.description) showWarning(e.description)
return
self.refresh() self.refresh()
self.mw.deckBrowser.refresh() self.mw.deckBrowser.refresh()

View File

@ -8,12 +8,35 @@ import re
import subprocess import subprocess
import sys import sys
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Union, cast from typing import (
TYPE_CHECKING,
Any,
Callable,
List,
Literal,
Optional,
Sequence,
Tuple,
Union,
cast,
)
from markdown import markdown from markdown import markdown
from PyQt5.QtWidgets import (
QAction,
QDialog,
QDialogButtonBox,
QFileDialog,
QHeaderView,
QMenu,
QPushButton,
QSplitter,
QWidget,
)
import anki import anki
import aqt import aqt
from anki import Collection
from anki.errors import InvalidInput from anki.errors import InvalidInput
from anki.lang import TR # pylint: disable=unused-import from anki.lang import TR # pylint: disable=unused-import
from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild
@ -77,7 +100,7 @@ argument. However, add-on may use string, and we want to accept this.
""" """
def openHelp(section: HelpPageArgument): def openHelp(section: HelpPageArgument) -> None:
link = aqt.appHelpSite link = aqt.appHelpSite
if section: if section:
if isinstance(section, HelpPage): if isinstance(section, HelpPage):
@ -87,27 +110,35 @@ def openHelp(section: HelpPageArgument):
openLink(link) openLink(link)
def openLink(link): def openLink(link: str) -> None:
tooltip(tr(TR.QT_MISC_LOADING), period=1000) tooltip(tr(TR.QT_MISC_LOADING), period=1000)
with noBundledLibs(): with noBundledLibs():
QDesktopServices.openUrl(QUrl(link)) QDesktopServices.openUrl(QUrl(link))
def showWarning( def showWarning(
text, parent=None, help="", title="Anki", textFormat: Optional[TextFormat] = None text: str,
): parent: Optional[QDialog] = None,
help: HelpPageArgument = "",
title: str = "Anki",
textFormat: Optional[TextFormat] = None,
) -> int:
"Show a small warning with an OK button." "Show a small warning with an OK button."
return showInfo(text, parent, help, "warning", title=title, textFormat=textFormat) return showInfo(text, parent, help, "warning", title=title, textFormat=textFormat)
def showCritical( def showCritical(
text, parent=None, help="", title="Anki", textFormat: Optional[TextFormat] = None text: str,
): parent: Optional[QDialog] = None,
help: str = "",
title: str = "Anki",
textFormat: Optional[TextFormat] = None,
) -> int:
"Show a small critical error with an OK button." "Show a small critical error with an OK button."
return showInfo(text, parent, help, "critical", title=title, textFormat=textFormat) return showInfo(text, parent, help, "critical", title=title, textFormat=textFormat)
def show_invalid_search_error(err: Exception): def show_invalid_search_error(err: Exception) -> None:
"Render search errors in markdown, then display a warning." "Render search errors in markdown, then display a warning."
text = str(err) text = str(err)
if isinstance(err, InvalidInput): if isinstance(err, InvalidInput):
@ -116,24 +147,27 @@ def show_invalid_search_error(err: Exception):
def showInfo( def showInfo(
text, text: str,
parent=False, parent: Union[Literal[False], QDialog] = False,
help="", help: HelpPageArgument = "",
type="info", type: str = "info",
title="Anki", title: str = "Anki",
textFormat: Optional[TextFormat] = None, textFormat: Optional[TextFormat] = None,
customBtns=None, customBtns: Optional[List[QMessageBox.StandardButton]] = None,
) -> int: ) -> int:
"Show a small info window with an OK button." "Show a small info window with an OK button."
parent_widget: QWidget
if parent is False: if parent is False:
parent = aqt.mw.app.activeWindow() or aqt.mw parent_widget = aqt.mw.app.activeWindow() or aqt.mw
else:
parent_widget = parent
if type == "warning": if type == "warning":
icon = QMessageBox.Warning icon = QMessageBox.Warning
elif type == "critical": elif type == "critical":
icon = QMessageBox.Critical icon = QMessageBox.Critical
else: else:
icon = QMessageBox.Information icon = QMessageBox.Information
mb = QMessageBox(parent) mb = QMessageBox(parent_widget) #
if textFormat == "plain": if textFormat == "plain":
mb.setTextFormat(Qt.PlainText) mb.setTextFormat(Qt.PlainText)
elif textFormat == "rich": elif textFormat == "rich":
@ -161,16 +195,16 @@ def showInfo(
def showText( def showText(
txt, txt: str,
parent=None, parent: Optional[QWidget] = None,
type="text", type: str = "text",
run=True, run: bool = True,
geomKey=None, geomKey: Optional[str] = None,
minWidth=500, minWidth: int = 500,
minHeight=400, minHeight: int = 400,
title="Anki", title: str = "Anki",
copyBtn=False, copyBtn: bool = False,
): ) -> Optional[Tuple[QDialog, QDialogButtonBox]]:
if not parent: if not parent:
parent = aqt.mw.app.activeWindow() or aqt.mw parent = aqt.mw.app.activeWindow() or aqt.mw
diag = QDialog(parent) diag = QDialog(parent)
@ -189,21 +223,21 @@ def showText(
layout.addWidget(box) layout.addWidget(box)
if copyBtn: if copyBtn:
def onCopy(): def onCopy() -> None:
QApplication.clipboard().setText(text.toPlainText()) QApplication.clipboard().setText(text.toPlainText())
btn = QPushButton(tr(TR.QT_MISC_COPY_TO_CLIPBOARD)) btn = QPushButton(tr(TR.QT_MISC_COPY_TO_CLIPBOARD))
qconnect(btn.clicked, onCopy) qconnect(btn.clicked, onCopy)
box.addButton(btn, QDialogButtonBox.ActionRole) box.addButton(btn, QDialogButtonBox.ActionRole)
def onReject(): def onReject() -> None:
if geomKey: if geomKey:
saveGeom(diag, geomKey) saveGeom(diag, geomKey)
QDialog.reject(diag) QDialog.reject(diag)
qconnect(box.rejected, onReject) qconnect(box.rejected, onReject)
def onFinish(): def onFinish() -> None:
if geomKey: if geomKey:
saveGeom(diag, geomKey) saveGeom(diag, geomKey)
@ -214,18 +248,19 @@ def showText(
restoreGeom(diag, geomKey) restoreGeom(diag, geomKey)
if run: if run:
diag.exec_() diag.exec_()
return None
else: else:
return diag, box return diag, box
def askUser( def askUser(
text, text: str,
parent=None, parent: QDialog = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
defaultno=False, defaultno: bool = False,
msgfunc=None, msgfunc: Optional[Callable] = None,
title="Anki", title: str = "Anki",
): ) -> bool:
"Show a yes/no question. Return true if yes." "Show a yes/no question. Return true if yes."
if not parent: if not parent:
parent = aqt.mw.app.activeWindow() parent = aqt.mw.app.activeWindow()
@ -239,7 +274,7 @@ def askUser(
default = QMessageBox.No default = QMessageBox.No
else: else:
default = QMessageBox.Yes default = QMessageBox.Yes
r = msgfunc(parent, title, text, sb, default) r = msgfunc(parent, title, text, cast(QMessageBox.StandardButtons, sb), default)
if r == QMessageBox.Help: if r == QMessageBox.Help:
openHelp(help) openHelp(help)
@ -250,10 +285,15 @@ def askUser(
class ButtonedDialog(QMessageBox): class ButtonedDialog(QMessageBox):
def __init__( def __init__(
self, text, buttons, parent=None, help: HelpPageArgument = None, title="Anki" self,
text: str,
buttons: List[str],
parent: Optional[QDialog] = None,
help: HelpPageArgument = None,
title: str = "Anki",
): ):
QMessageBox.__init__(self, parent) QMessageBox.__init__(self, parent)
self._buttons = [] self._buttons: List[QPushButton] = []
self.setWindowTitle(title) self.setWindowTitle(title)
self.help = help self.help = help
self.setIcon(QMessageBox.Warning) self.setIcon(QMessageBox.Warning)
@ -264,7 +304,7 @@ class ButtonedDialog(QMessageBox):
self.addButton(tr(TR.ACTIONS_HELP), QMessageBox.HelpRole) self.addButton(tr(TR.ACTIONS_HELP), QMessageBox.HelpRole)
buttons.append(tr(TR.ACTIONS_HELP)) buttons.append(tr(TR.ACTIONS_HELP))
def run(self): def run(self) -> str:
self.exec_() self.exec_()
but = self.clickedButton().text() but = self.clickedButton().text()
if but == "Help": if but == "Help":
@ -274,13 +314,17 @@ class ButtonedDialog(QMessageBox):
# work around KDE 'helpfully' adding accelerators to button text of Qt apps # work around KDE 'helpfully' adding accelerators to button text of Qt apps
return txt.replace("&", "") return txt.replace("&", "")
def setDefault(self, idx): def setDefault(self, idx: int) -> None:
self.setDefaultButton(self._buttons[idx]) self.setDefaultButton(self._buttons[idx])
def askUserDialog( def askUserDialog(
text, buttons, parent=None, help: HelpPageArgument = None, title="Anki" text: str,
): buttons: List[str],
parent: Optional[QDialog] = None,
help: HelpPageArgument = None,
title: str = "Anki",
) -> ButtonedDialog:
if not parent: if not parent:
parent = aqt.mw parent = aqt.mw
diag = ButtonedDialog(text, buttons, parent, help, title=title) diag = ButtonedDialog(text, buttons, parent, help, title=title)
@ -290,14 +334,14 @@ def askUserDialog(
class GetTextDialog(QDialog): class GetTextDialog(QDialog):
def __init__( def __init__(
self, self,
parent, parent: Optional[QDialog],
question, question: str,
help: HelpPageArgument = None, help: HelpPageArgument = None,
edit=None, edit: Optional[QLineEdit] = None,
default="", default: str = "",
title="Anki", title: str = "Anki",
minWidth=400, minWidth: int = 400,
): ) -> None:
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.setWindowTitle(title) self.setWindowTitle(title)
disable_help_button(self) disable_help_button(self)
@ -325,26 +369,26 @@ class GetTextDialog(QDialog):
if help: if help:
qconnect(b.button(QDialogButtonBox.Help).clicked, self.helpRequested) qconnect(b.button(QDialogButtonBox.Help).clicked, self.helpRequested)
def accept(self): def accept(self) -> None:
return QDialog.accept(self) return QDialog.accept(self)
def reject(self): def reject(self) -> None:
return QDialog.reject(self) return QDialog.reject(self)
def helpRequested(self): def helpRequested(self) -> None:
openHelp(self.help) openHelp(self.help)
def getText( def getText(
prompt, prompt: str,
parent=None, parent: Optional[QDialog] = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
edit=None, edit: Optional[QLineEdit] = None,
default="", default: str = "",
title="Anki", title: str = "Anki",
geomKey=None, geomKey: Optional[str] = None,
**kwargs, **kwargs: Any,
): ) -> Tuple[str, int]:
if not parent: if not parent:
parent = aqt.mw.app.activeWindow() or aqt.mw parent = aqt.mw.app.activeWindow() or aqt.mw
d = GetTextDialog( d = GetTextDialog(
@ -359,7 +403,7 @@ def getText(
return (str(d.l.text()), ret) return (str(d.l.text()), ret)
def getOnlyText(*args, **kwargs): def getOnlyText(*args: Any, **kwargs: Any) -> str:
(s, r) = getText(*args, **kwargs) (s, r) = getText(*args, **kwargs)
if r: if r:
return s return s
@ -368,7 +412,10 @@ def getOnlyText(*args, **kwargs):
# fixme: these utilities could be combined into a single base class # fixme: these utilities could be combined into a single base class
def chooseList(prompt, choices, startrow=0, parent=None): # unused by Anki, but used by add-ons
def chooseList(
prompt: str, choices: List[str], startrow: int = 0, parent: Any = None
) -> int:
if not parent: if not parent:
parent = aqt.mw.app.activeWindow() parent = aqt.mw.app.activeWindow()
d = QDialog(parent) d = QDialog(parent)
@ -389,7 +436,9 @@ def chooseList(prompt, choices, startrow=0, parent=None):
return c.currentRow() return c.currentRow()
def getTag(parent, deck, question, tags="user", **kwargs): def getTag(
parent: QDialog, deck: Collection, question: str, **kwargs: Any
) -> Tuple[str, int]:
from aqt.tagedit import TagEdit from aqt.tagedit import TagEdit
te = TagEdit(parent) te = TagEdit(parent)
@ -409,7 +458,15 @@ def disable_help_button(widget: QWidget) -> None:
###################################################################### ######################################################################
def getFile(parent, title, cb, filter="*.*", dir=None, key=None, multi=False): def getFile(
parent: QDialog,
title: str,
cb: Optional[Callable[[Union[str, Sequence[str]]], None]],
filter: str = "*.*",
dir: Optional[str] = None,
key: Optional[str] = None,
multi: bool = False, # controls whether a single or multiple files is returned
) -> Optional[Union[Sequence[str], str]]:
"Ask the user for a file." "Ask the user for a file."
assert not dir or not key assert not dir or not key
if not dir: if not dir:
@ -426,7 +483,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None, multi=False):
d.setNameFilter(filter) d.setNameFilter(filter)
ret = [] ret = []
def accept(): def accept() -> None:
files = list(d.selectedFiles()) files = list(d.selectedFiles())
if dirkey: if dirkey:
dir = os.path.dirname(files[0]) dir = os.path.dirname(files[0])
@ -442,10 +499,17 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None, multi=False):
d.exec_() d.exec_()
if key: if key:
saveState(d, key) saveState(d, key)
return ret and ret[0] return ret[0] if ret else None
def getSaveFile(parent, title, dir_description, key, ext, fname=None): def getSaveFile(
parent: QDialog,
title: str,
dir_description: str,
key: str,
ext: str,
fname: Optional[str] = None,
) -> str:
"""Ask the user for a file to save. Use DIR_DESCRIPTION as config """Ask the user for a file to save. Use DIR_DESCRIPTION as config
variable. The file dialog will default to open with FNAME.""" variable. The file dialog will default to open with FNAME."""
config_key = dir_description + "Directory" config_key = dir_description + "Directory"
@ -474,7 +538,7 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None):
return file return file
def saveGeom(widget, key: str): def saveGeom(widget: QDialog, key: str) -> None:
key += "Geom" key += "Geom"
if isMac and widget.windowState() & Qt.WindowFullScreen: if isMac and widget.windowState() & Qt.WindowFullScreen:
geom = None geom = None
@ -483,7 +547,9 @@ def saveGeom(widget, key: str):
aqt.mw.pm.profile[key] = geom aqt.mw.pm.profile[key] = geom
def restoreGeom(widget, key: str, offset=None, adjustSize=False): def restoreGeom(
widget: QWidget, key: str, offset: Optional[int] = None, adjustSize: bool = False
) -> None:
key += "Geom" key += "Geom"
if aqt.mw.pm.profile.get(key): if aqt.mw.pm.profile.get(key):
widget.restoreGeometry(aqt.mw.pm.profile[key]) widget.restoreGeometry(aqt.mw.pm.profile[key])
@ -498,7 +564,7 @@ def restoreGeom(widget, key: str, offset=None, adjustSize=False):
widget.adjustSize() widget.adjustSize()
def ensureWidgetInScreenBoundaries(widget): def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
handle = widget.window().windowHandle() handle = widget.window().windowHandle()
if not handle: if not handle:
# window has not yet been shown, retry later # window has not yet been shown, retry later
@ -524,58 +590,60 @@ def ensureWidgetInScreenBoundaries(widget):
widget.move(x, y) widget.move(x, y)
def saveState(widget, key: str): def saveState(widget: QFileDialog, key: str) -> None:
key += "State" key += "State"
aqt.mw.pm.profile[key] = widget.saveState() aqt.mw.pm.profile[key] = widget.saveState()
def restoreState(widget, key: str): def restoreState(widget: Union[aqt.AnkiQt, QFileDialog], key: str) -> None:
key += "State" key += "State"
if aqt.mw.pm.profile.get(key): if aqt.mw.pm.profile.get(key):
widget.restoreState(aqt.mw.pm.profile[key]) widget.restoreState(aqt.mw.pm.profile[key])
def saveSplitter(widget, key): def saveSplitter(widget: QSplitter, key: str) -> None:
key += "Splitter" key += "Splitter"
aqt.mw.pm.profile[key] = widget.saveState() aqt.mw.pm.profile[key] = widget.saveState()
def restoreSplitter(widget, key): def restoreSplitter(widget: QSplitter, key: str) -> None:
key += "Splitter" key += "Splitter"
if aqt.mw.pm.profile.get(key): if aqt.mw.pm.profile.get(key):
widget.restoreState(aqt.mw.pm.profile[key]) widget.restoreState(aqt.mw.pm.profile[key])
def saveHeader(widget, key): def saveHeader(widget: QHeaderView, key: str) -> None:
key += "Header" key += "Header"
aqt.mw.pm.profile[key] = widget.saveState() aqt.mw.pm.profile[key] = widget.saveState()
def restoreHeader(widget, key): def restoreHeader(widget: QHeaderView, key: str) -> None:
key += "Header" key += "Header"
if aqt.mw.pm.profile.get(key): if aqt.mw.pm.profile.get(key):
widget.restoreState(aqt.mw.pm.profile[key]) widget.restoreState(aqt.mw.pm.profile[key])
def save_is_checked(widget, key: str): def save_is_checked(widget: QWidget, key: str) -> None:
key += "IsChecked" key += "IsChecked"
aqt.mw.pm.profile[key] = widget.isChecked() aqt.mw.pm.profile[key] = widget.isChecked()
def restore_is_checked(widget, key: str): def restore_is_checked(widget: QWidget, key: str) -> None:
key += "IsChecked" key += "IsChecked"
if aqt.mw.pm.profile.get(key) is not None: if aqt.mw.pm.profile.get(key) is not None:
widget.setChecked(aqt.mw.pm.profile[key]) widget.setChecked(aqt.mw.pm.profile[key])
def save_combo_index_for_session(widget: QComboBox, key: str): def save_combo_index_for_session(widget: QComboBox, key: str) -> None:
textKey = key + "ComboActiveText" textKey = key + "ComboActiveText"
indexKey = key + "ComboActiveIndex" indexKey = key + "ComboActiveIndex"
aqt.mw.pm.session[textKey] = widget.currentText() aqt.mw.pm.session[textKey] = widget.currentText()
aqt.mw.pm.session[indexKey] = widget.currentIndex() aqt.mw.pm.session[indexKey] = widget.currentIndex()
def restore_combo_index_for_session(widget: QComboBox, history: List[str], key: str): def restore_combo_index_for_session(
widget: QComboBox, history: List[str], key: str
) -> None:
textKey = key + "ComboActiveText" textKey = key + "ComboActiveText"
indexKey = key + "ComboActiveIndex" indexKey = key + "ComboActiveIndex"
text = aqt.mw.pm.session.get(textKey) text = aqt.mw.pm.session.get(textKey)
@ -585,7 +653,7 @@ def restore_combo_index_for_session(widget: QComboBox, history: List[str], key:
widget.setCurrentIndex(index) widget.setCurrentIndex(index)
def save_combo_history(comboBox: QComboBox, history: List[str], name: str): def save_combo_history(comboBox: QComboBox, history: List[str], name: str) -> str:
name += "BoxHistory" name += "BoxHistory"
text_input = comboBox.lineEdit().text() text_input = comboBox.lineEdit().text()
if text_input in history: if text_input in history:
@ -599,7 +667,7 @@ def save_combo_history(comboBox: QComboBox, history: List[str], name: str):
return text_input return text_input
def restore_combo_history(comboBox: QComboBox, name: str): def restore_combo_history(comboBox: QComboBox, name: str) -> List[str]:
name += "BoxHistory" name += "BoxHistory"
history = aqt.mw.pm.profile.get(name, []) history = aqt.mw.pm.profile.get(name, [])
comboBox.addItems([""] + history) comboBox.addItems([""] + history)
@ -611,13 +679,13 @@ def restore_combo_history(comboBox: QComboBox, name: str):
return history return history
def mungeQA(col, txt): def mungeQA(col: Collection, txt: str) -> str:
print("mungeQA() deprecated; use mw.prepare_card_text_for_display()") print("mungeQA() deprecated; use mw.prepare_card_text_for_display()")
txt = col.media.escape_media_filenames(txt) txt = col.media.escape_media_filenames(txt)
return txt return txt
def openFolder(path): def openFolder(path: str) -> None:
if isWin: if isWin:
subprocess.Popen(["explorer", "file://" + path]) subprocess.Popen(["explorer", "file://" + path])
else: else:
@ -625,27 +693,27 @@ def openFolder(path):
QDesktopServices.openUrl(QUrl("file://" + path)) QDesktopServices.openUrl(QUrl("file://" + path))
def shortcut(key): def shortcut(key: str) -> str:
if isMac: if isMac:
return re.sub("(?i)ctrl", "Command", key) return re.sub("(?i)ctrl", "Command", key)
return key return key
def maybeHideClose(bbox): def maybeHideClose(bbox: QDialogButtonBox) -> None:
if isMac: if isMac:
b = bbox.button(QDialogButtonBox.Close) b = bbox.button(QDialogButtonBox.Close)
if b: if b:
bbox.removeButton(b) bbox.removeButton(b)
def addCloseShortcut(widg): def addCloseShortcut(widg: QDialog) -> None:
if not isMac: if not isMac:
return return
widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg) widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
qconnect(widg._closeShortcut.activated, widg.reject) qconnect(widg._closeShortcut.activated, widg.reject)
def downArrow(): def downArrow() -> str:
if isWin: if isWin:
return "" return ""
# windows 10 is lacking the smaller arrow on English installs # windows 10 is lacking the smaller arrow on English installs
@ -659,13 +727,19 @@ _tooltipTimer: Optional[QTimer] = None
_tooltipLabel: Optional[QLabel] = None _tooltipLabel: Optional[QLabel] = None
def tooltip(msg, period=3000, parent=None, x_offset=0, y_offset=100): def tooltip(
msg: str,
period: int = 3000,
parent: Optional[aqt.AnkiQt] = None,
x_offset: int = 0,
y_offset: int = 100,
) -> None:
global _tooltipTimer, _tooltipLabel global _tooltipTimer, _tooltipLabel
class CustomLabel(QLabel): class CustomLabel(QLabel):
silentlyClose = True silentlyClose = True
def mousePressEvent(self, evt): def mousePressEvent(self, evt: QMouseEvent) -> None:
evt.accept() evt.accept()
self.hide() self.hide()
@ -697,7 +771,7 @@ def tooltip(msg, period=3000, parent=None, x_offset=0, y_offset=100):
_tooltipLabel = lab _tooltipLabel = lab
def closeTooltip(): def closeTooltip() -> None:
global _tooltipLabel, _tooltipTimer global _tooltipLabel, _tooltipTimer
if _tooltipLabel: if _tooltipLabel:
try: try:
@ -712,7 +786,7 @@ def closeTooltip():
# true if invalid; print warning # true if invalid; print warning
def checkInvalidFilename(str, dirsep=True): def checkInvalidFilename(str: str, dirsep: bool = True) -> bool:
bad = invalidFilename(str, dirsep) bad = invalidFilename(str, dirsep)
if bad: if bad:
showWarning(tr(TR.QT_MISC_THE_FOLLOWING_CHARACTER_CAN_NOT_BE, val=bad)) showWarning(tr(TR.QT_MISC_THE_FOLLOWING_CHARACTER_CAN_NOT_BE, val=bad))
@ -723,28 +797,30 @@ def checkInvalidFilename(str, dirsep=True):
# Menus # Menus
###################################################################### ######################################################################
MenuListChild = Union["SubMenu", QAction, "MenuItem", "MenuList"]
class MenuList: class MenuList:
def __init__(self): def __init__(self) -> None:
self.children = [] self.children: List[MenuListChild] = []
def addItem(self, title, func): def addItem(self, title: str, func: Callable) -> MenuItem:
item = MenuItem(title, func) item = MenuItem(title, func)
self.children.append(item) self.children.append(item)
return item return item
def addSeparator(self): def addSeparator(self) -> None:
self.children.append(None) self.children.append(None)
def addMenu(self, title): def addMenu(self, title: str) -> SubMenu:
submenu = SubMenu(title) submenu = SubMenu(title)
self.children.append(submenu) self.children.append(submenu)
return submenu return submenu
def addChild(self, child): def addChild(self, child: Union[SubMenu, QAction, MenuList]) -> None:
self.children.append(child) self.children.append(child)
def renderTo(self, qmenu): def renderTo(self, qmenu: QMenu) -> None:
for child in self.children: for child in self.children:
if child is None: if child is None:
qmenu.addSeparator() qmenu.addSeparator()
@ -753,33 +829,33 @@ class MenuList:
else: else:
child.renderTo(qmenu) child.renderTo(qmenu)
def popupOver(self, widget): def popupOver(self, widget: QPushButton) -> None:
qmenu = QMenu() qmenu = QMenu()
self.renderTo(qmenu) self.renderTo(qmenu)
qmenu.exec_(widget.mapToGlobal(QPoint(0, 0))) qmenu.exec_(widget.mapToGlobal(QPoint(0, 0)))
class SubMenu(MenuList): class SubMenu(MenuList):
def __init__(self, title): def __init__(self, title: str) -> None:
super().__init__() super().__init__()
self.title = title self.title = title
def renderTo(self, menu): def renderTo(self, menu: QMenu) -> None:
submenu = menu.addMenu(self.title) submenu = menu.addMenu(self.title)
super().renderTo(submenu) super().renderTo(submenu)
class MenuItem: class MenuItem:
def __init__(self, title, func): def __init__(self, title: str, func: Callable) -> None:
self.title = title self.title = title
self.func = func self.func = func
def renderTo(self, qmenu): def renderTo(self, qmenu: QMenu) -> None:
a = qmenu.addAction(self.title) a = qmenu.addAction(self.title)
qconnect(a.triggered, self.func) qconnect(a.triggered, self.func)
def qtMenuShortcutWorkaround(qmenu): def qtMenuShortcutWorkaround(qmenu: QMenu) -> None:
if qtminor < 10: if qtminor < 10:
return return
for act in qmenu.actions(): for act in qmenu.actions():
@ -789,7 +865,7 @@ def qtMenuShortcutWorkaround(qmenu):
###################################################################### ######################################################################
def supportText(): def supportText() -> str:
import platform import platform
import time import time
@ -802,9 +878,9 @@ def supportText():
else: else:
platname = "Linux" platname = "Linux"
def schedVer(): def schedVer() -> str:
try: try:
return mw.col.schedVer() return str(mw.col.schedVer())
except: except:
return "?" return "?"
@ -832,7 +908,7 @@ Add-ons, last update check: {}
###################################################################### ######################################################################
# adapted from version detection in qutebrowser # adapted from version detection in qutebrowser
def opengl_vendor(): def opengl_vendor() -> Optional[str]:
old_context = QOpenGLContext.currentContext() old_context = QOpenGLContext.currentContext()
old_surface = None if old_context is None else old_context.surface() old_surface = None if old_context is None else old_context.surface()
@ -871,7 +947,7 @@ def opengl_vendor():
old_context.makeCurrent(old_surface) old_context.makeCurrent(old_surface)
def gfxDriverIsBroken(): def gfxDriverIsBroken() -> bool:
driver = opengl_vendor() driver = opengl_vendor()
return driver == "nouveau" return driver == "nouveau"

View File

@ -14,6 +14,8 @@ disallow_untyped_defs=true
disallow_untyped_defs=true disallow_untyped_defs=true
[mypy-aqt.editor] [mypy-aqt.editor]
disallow_untyped_defs=true disallow_untyped_defs=true
[mypy-aqt.utils]
disallow_untyped_defs=true
[mypy-aqt.mpv] [mypy-aqt.mpv]