Skip to content

Commit 10d6682

Browse files
committed
Added type hints in __init__.py and MathCATPreferences.py
1 parent 26b29b9 commit 10d6682

File tree

2 files changed

+78
-68
lines changed

2 files changed

+78
-68
lines changed

addon/globalPlugins/MathCAT/MathCATPreferences.py

Lines changed: 75 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import gettext
1111
import addonHandler
1212
from logHandler import log # logging
13-
from typing import List, Dict, Union, Callable
13+
from collections.abc import Callable
1414
from .MathCAT import convertSSMLTextForNVDA
1515
from speech import speak
1616
from zipfile import ZipFile
@@ -20,11 +20,11 @@
2020

2121
# two constants to scale "PauseFactor"
2222
# these work out so that a slider that goes [0,14] has value ~100 at 7 and ~1000 at 14
23-
PAUSE_FACTOR_SCALE = 9.5
24-
PAUSE_FACTOR_LOG_BASE = 1.4
23+
PAUSE_FACTOR_SCALE: float = 9.5
24+
PAUSE_FACTOR_LOG_BASE: float = 1.4
2525

2626
# initialize the user preferences tuples
27-
userPreferences: Dict[str, Dict[str, Union[int, str, bool]]] = {}
27+
userPreferences: dict[str, dict[str, int | str | bool]] = {}
2828
# Speech_Language is derived from the folder structures
2929
Speech_DecimalSeparator = ("Auto", ".", ",", "Custom")
3030
Speech_Impairment = ("LearningDisability", "Blindness", "LowVision")
@@ -47,7 +47,7 @@ def __init__(self, parent):
4747
MathCATgui.MathCATPreferencesDialog.__init__(self, parent)
4848

4949
# load the logo into the dialog
50-
fullPathToLogo = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png")
50+
fullPathToLogo: str = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png")
5151
if os.path.exists(fullPathToLogo):
5252
self._bitmapLogo.SetBitmap(wx.Bitmap(fullPathToLogo))
5353

@@ -78,17 +78,17 @@ def __init__(self, parent):
7878
UserInterface.setUIValues(self)
7979

8080
@staticmethod
81-
def pathToLanguagesFolder():
81+
def pathToLanguagesFolder() -> str:
8282
# the user preferences file is stored at: MathCAT\Rules\Languages
8383
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Languages")
8484

8585
@staticmethod
86-
def pathToBrailleFolder():
86+
def pathToBrailleFolder() -> str:
8787
# the user preferences file is stored at: MathCAT\Rules\Languages
8888
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Braille")
8989

9090
@staticmethod
91-
def languagesDict() -> Dict[str, str]:
91+
def languagesDict() -> dict[str, str]:
9292
languages = {
9393
"aa": "Afar",
9494
"ab": "Аҧсуа",
@@ -268,10 +268,10 @@ def languagesDict() -> Dict[str, str]:
268268
def getRulesFiles(
269269
self,
270270
pathToDir: str,
271-
processSubDirs: Callable[[str, str], List[str]] | None,
272-
) -> List[str]:
273-
language = os.path.basename(pathToDir)
274-
ruleFiles = [os.path.basename(file) for file in glob.glob(os.path.join(pathToDir, "*_Rules.yaml"))]
271+
processSubDirs: Callable[[str, str], list[str]] | None,
272+
) -> list[str]:
273+
language: str = os.path.basename(pathToDir)
274+
ruleFiles: list[str] = [os.path.basename(file) for file in glob.glob(os.path.join(pathToDir, "*_Rules.yaml"))]
275275
for dir in os.listdir(pathToDir):
276276
if os.path.isdir(os.path.join(pathToDir, dir)):
277277
if processSubDirs:
@@ -280,7 +280,7 @@ def getRulesFiles(
280280
if len(ruleFiles) == 0:
281281
# look in the .zip file for the style files, including regional subdirs -- it might not have been unzipped
282282
try:
283-
zip_file = ZipFile(f"{pathToDir}\\{language}.zip", "r")
283+
zip_file: ZipFile = ZipFile(f"{pathToDir}\\{language}.zip", "r")
284284
for file in zip_file.namelist():
285285
if file.endswith("_Rules.yaml"):
286286
ruleFiles.append(file)
@@ -291,15 +291,15 @@ def getRulesFiles(
291291

292292
return ruleFiles
293293

294-
def getLanguages(self):
295-
def addRegionalLanguages(subDir: str, language: str) -> List[str]:
294+
def getLanguages(self) -> None:
295+
def addRegionalLanguages(subDir: str, language: str) -> list[str]:
296296
# the language variants are in folders named using ISO 3166-1 alpha-2
297297
# codes https://en.wikipedia.org/wiki/ISO_3166-2
298298
# check if there are language variants in the language folder
299299
if subDir != "SharedRules":
300-
languagesDict = UserInterface.languagesDict()
300+
languagesDict: dict[str, str] = UserInterface.languagesDict()
301301
# add to the listbox the text for this language variant together with the code
302-
regionalCode = language + "-" + subDir.upper()
302+
regionalCode: str = language + "-" + subDir.upper()
303303
if languagesDict.get(regionalCode, "missing") != "missing":
304304
self._choiceLanguage.Append(f"{languagesDict[regionalCode]} ({language}-{subDir})")
305305
elif languagesDict.get(language, "missing") != "missing":
@@ -310,7 +310,7 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]:
310310
return []
311311

312312
# initialise the language list
313-
languagesDict = UserInterface.languagesDict()
313+
languagesDict: dict[str, str] = UserInterface.languagesDict()
314314
# clear the language names in the dialog
315315
self._choiceLanguage.Clear()
316316
# Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog
@@ -319,9 +319,9 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]:
319319
# populate the available language names in the dialog
320320
# the implemented languages are in folders named using the relevant ISO 639-1
321321
# code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
322-
languageDir = UserInterface.pathToLanguagesFolder()
322+
languageDir: str = UserInterface.pathToLanguagesFolder()
323323
for language in os.listdir(languageDir):
324-
pathToLanguageDir = os.path.join(UserInterface.pathToLanguagesFolder(), language)
324+
pathToLanguageDir: str = os.path.join(UserInterface.pathToLanguagesFolder(), language)
325325
if os.path.isdir(pathToLanguageDir):
326326
# only add this language if there is a xxx_Rules.yaml file
327327
if len(self.getRulesFiles(pathToLanguageDir, addRegionalLanguages)) > 0:
@@ -331,10 +331,10 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]:
331331
else:
332332
self._choiceLanguage.Append(language + " (" + language + ")")
333333

334-
def getLanguageCode(self):
335-
lang_selection = self._choiceLanguage.GetStringSelection()
336-
lang_code = lang_selection[lang_selection.find("(") + 1 : lang_selection.find(")")]
337-
return lang_code
334+
def getLanguageCode(self) -> str:
335+
langSelection: str = self._choiceLanguage.GetStringSelection()
336+
langCode: str = langSelection[langSelection.find("(") + 1 : langSelection.find(")")]
337+
return langCode
338338

339339
def getSpeechStyles(self, thisSpeechStyle: str):
340340
"""Get all the speech styles for the current language.
@@ -346,19 +346,19 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]:
346346
The 'lang', if it has a region dialect, is of the form 'en\uk'
347347
The returned list is sorted alphabetically"""
348348
# start with the regional dialect, then add on any (unique) styles in the main dir
349-
mainLang = lang.split("\\")[0] # does the right thing even if there is no regional directory
350-
allStyleFiles = []
349+
mainLang: str = lang.split("\\")[0] # does the right thing even if there is no regional directory
350+
allStyleFiles: list[str] = []
351351
if lang.find("\\") >= 0:
352-
allStyleFiles = [os.path.basename(name) for name in glob.glob(dir + lang + "\\*_Rules.yaml")]
352+
allStyleFiles: list[str] = [os.path.basename(name) for name in glob.glob(dir + lang + "\\*_Rules.yaml")]
353353
allStyleFiles.extend(
354354
[os.path.basename(name) for name in glob.glob(dir + mainLang + "\\*_Rules.yaml")],
355355
)
356356
allStyleFiles = list(set(allStyleFiles)) # make them unique
357357
if len(allStyleFiles) == 0:
358358
# look in the .zip file for the style files -- this will have regional variants, but also have that dir
359359
try:
360-
zipFile = dir + mainLang + "\\" + mainLang + ".zip"
361-
zipFile = ZipFile(zipFile, "r") # file might not exist
360+
zipFilePath: str = dir + mainLang + "\\" + mainLang + ".zip"
361+
zipFile: ZipFile = ZipFile(zipFilePath, "r") # file might not exist
362362
allStyleFiles = [
363363
name.split("/")[-1] for name in zipFile.namelist() if name.endswith("_Rules.yaml")
364364
]
@@ -370,7 +370,7 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]:
370370
# clear the SpeechStyle choices
371371
self._choiceSpeechStyle.Clear()
372372
# get the currently selected language code
373-
languageCode = UserInterface.getLanguageCode(self)
373+
languageCode: str = UserInterface.getLanguageCode(self)
374374

375375
if languageCode == "Auto":
376376
# list the speech styles for the current voice rather than have none listed
@@ -402,28 +402,28 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]:
402402
# that didn't work, choose the first in the list
403403
self._choiceSpeechStyle.SetSelection(0)
404404

405-
def getBrailleCodes(self):
405+
def getBrailleCodes(self) -> None:
406406
# initialise the braille code list
407407
self._choiceBrailleMathCode.Clear()
408408
# populate the available braille codes in the dialog
409409
# the dir names are used, not the rule file names because the dir names have to be unique
410-
pathToBrailleFolder = UserInterface.pathToBrailleFolder()
410+
pathToBrailleFolder: str = UserInterface.pathToBrailleFolder()
411411
for brailleCode in os.listdir(pathToBrailleFolder):
412-
pathToBrailleCode = os.path.join(pathToBrailleFolder, brailleCode)
412+
pathToBrailleCode: str = os.path.join(pathToBrailleFolder, brailleCode)
413413
if os.path.isdir(pathToBrailleCode):
414414
if len(self.getRulesFiles(pathToBrailleCode, None)) > 0:
415415
self._choiceBrailleMathCode.Append(brailleCode)
416416

417-
def setUIValues(self):
417+
def setUIValues(self) -> None:
418418
# set the UI elements to the ones read from the preference file(s)
419419
try:
420420
self._choiceImpairment.SetSelection(
421421
Speech_Impairment.index(userPreferences["Speech"]["Impairment"]),
422422
)
423423
try:
424-
langPref = userPreferences["Speech"]["Language"]
424+
langPref: str = userPreferences["Speech"]["Language"]
425425
self._choiceLanguage.SetSelection(0)
426-
i = 1 # no need to test i == 0
426+
i: int = 1 # no need to test i == 0
427427
while i < self._choiceLanguage.GetCount():
428428
if f"({langPref})" in self._choiceLanguage.GetString(i):
429429
self._choiceLanguage.SetSelection(i)
@@ -489,7 +489,7 @@ def setUIValues(self):
489489
Braille_BrailleNavHighlight.index(userPreferences["Braille"]["BrailleNavHighlight"]),
490490
)
491491
try:
492-
braillePref = userPreferences["Braille"]["BrailleCode"]
492+
braillePref: str = userPreferences["Braille"]["BrailleCode"]
493493
i = 0
494494
while braillePref != self._choiceBrailleMathCode.GetString(i):
495495
i = i + 1
@@ -507,7 +507,7 @@ def setUIValues(self):
507507
except KeyError as err:
508508
print("Key not found", err)
509509

510-
def getUIValues(self):
510+
def getUIValues(self) -> None:
511511
global userPreferences
512512
# read the values from the UI and update the user preferences dictionary
513513
userPreferences["Speech"]["Impairment"] = Speech_Impairment[self._choiceImpairment.GetSelection()]
@@ -518,8 +518,8 @@ def getUIValues(self):
518518
userPreferences["Speech"]["SpeechStyle"] = self._choiceSpeechStyle.GetStringSelection()
519519
userPreferences["Speech"]["Verbosity"] = Speech_Verbosity[self._choiceSpeechAmount.GetSelection()]
520520
userPreferences["Speech"]["MathRate"] = self._sliderRelativeSpeed.GetValue()
521-
pfSlider = self._sliderPauseFactor.GetValue()
522-
pauseFactor = (
521+
pfSlider: int = self._sliderPauseFactor.GetValue()
522+
pauseFactor: int = (
523523
0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider))
524524
) # avoid log(0)
525525
userPreferences["Speech"]["PauseFactor"] = pauseFactor
@@ -551,21 +551,21 @@ def getUIValues(self):
551551
userPreferences["NVDAAddOn"]["LastCategory"] = self._listBoxPreferencesTopic.GetSelection()
552552

553553
@staticmethod
554-
def pathToDefaultPreferences():
554+
def pathToDefaultPreferences() -> str:
555555
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "prefs.yaml")
556556

557557
@staticmethod
558-
def pathToUserPreferencesFolder():
558+
def pathToUserPreferencesFolder() -> str:
559559
# the user preferences file is stored at: C:\Users\<user-name>AppData\Roaming\MathCAT\prefs.yaml
560560
return os.path.join(os.path.expandvars("%APPDATA%"), "MathCAT")
561561

562562
@staticmethod
563-
def pathToUserPreferences():
563+
def pathToUserPreferences() -> str:
564564
# the user preferences file is stored at: C:\Users\<user-name>AppData\Roaming\MathCAT\prefs.yaml
565565
return os.path.join(UserInterface.pathToUserPreferencesFolder(), "prefs.yaml")
566566

567567
@staticmethod
568-
def loadDefaultPreferences():
568+
def loadDefaultPreferences() -> None:
569569
global userPreferences
570570
# load default preferences into the user preferences data structure (overwrites existing)
571571
if os.path.exists(UserInterface.pathToDefaultPreferences()):
@@ -576,7 +576,7 @@ def loadDefaultPreferences():
576576
userPreferences = yaml.load(f, Loader=yaml.FullLoader)
577577

578578
@staticmethod
579-
def loadUserPreferences():
579+
def loadUserPreferences() -> None:
580580
global userPreferences
581581
# merge user file values into the user preferences data structure
582582
if os.path.exists(UserInterface.pathToUserPreferences()):
@@ -585,7 +585,12 @@ def loadUserPreferences():
585585
userPreferences.update(yaml.load(f, Loader=yaml.FullLoader))
586586

587587
@staticmethod
588-
def validate(key1: str, key2: str, validValues: List[Union[str, bool]], defaultValue: Union[str, bool]):
588+
def validate(
589+
key1: str,
590+
key2: str,
591+
validValues: list[str | bool],
592+
defaultValue: str | bool,
593+
) -> None:
589594
global userPreferences
590595
try:
591596
if validValues == []:
@@ -606,7 +611,12 @@ def validate(key1: str, key2: str, validValues: List[Union[str, bool]], defaultV
606611
userPreferences[key1][key2] = defaultValue
607612

608613
@staticmethod
609-
def validateInt(key1: str, key2: str, validValues: List[int], defaultValue: int):
614+
def validateInt(
615+
key1: str,
616+
key2: str,
617+
validValues: list[int],
618+
defaultValue: int
619+
) -> None:
610620
global userPreferences
611621
try:
612622
# any value between lower and upper bounds is valid
@@ -678,7 +688,7 @@ def validateUserPreferences():
678688
UserInterface.validate("Braille", "BrailleCode", [], "Nemeth")
679689

680690
@staticmethod
681-
def writeUserPreferences():
691+
def writeUserPreferences() -> None:
682692
# Language is special because it is set elsewhere by SetPreference which overrides the user_prefs -- so set it here
683693
from . import libmathcat_py as libmathcat
684694

@@ -695,23 +705,23 @@ def writeUserPreferences():
695705
# write values to the user preferences file, NOT the default
696706
yaml.dump(userPreferences, stream=f, allow_unicode=True)
697707

698-
def onRelativeSpeedChanged(self, event):
699-
rate = self._sliderRelativeSpeed.GetValue()
708+
def onRelativeSpeedChanged(self, event: wx.ScrollEvent) -> None:
709+
rate: int = self._sliderRelativeSpeed.GetValue()
700710
# Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared"
701-
text = _("<prosody rate='XXX%'>the square root of x squared plus y squared</prosody>").replace(
711+
text: str = _("<prosody rate='XXX%'>the square root of x squared plus y squared</prosody>").replace(
702712
"XXX",
703713
str(rate),
704714
1,
705715
)
706716
speak(convertSSMLTextForNVDA(text))
707717

708-
def onPauseFactorChanged(self, event):
709-
rate = self._sliderRelativeSpeed.GetValue()
718+
def onPauseFactorChanged(self, event: wx.ScrollEvent) -> None:
719+
rate: int = self._sliderRelativeSpeed.GetValue()
710720
pfSlider = self._sliderPauseFactor.GetValue()
711721
pauseFactor = (
712722
0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider))
713723
)
714-
text = _(
724+
text: str = _(
715725
# Translators: this is a test string that is spoken. Only translate "the fraction with numerator"
716726
# and other parts NOT inside '<.../>',
717727
"<prosody rate='{rate}%'>the fraction with numerator <break time='{pause_factor_300}ms'/>\
@@ -732,37 +742,37 @@ def onPauseFactorChanged(self, event):
732742
)
733743
speak(convertSSMLTextForNVDA(text))
734744

735-
def onClickOK(self, event):
745+
def onClickOK(self, event: wx.CommandEvent) -> None:
736746
UserInterface.getUIValues(self)
737747
UserInterface.writeUserPreferences()
738748
self.Destroy()
739749

740-
def onClickCancel(self, event):
750+
def onClickCancel(self, event: wx.CommandEvent) -> None:
741751
self.Destroy()
742752

743-
def onClickApply(self, event):
753+
def onClickApply(self, event: wx.CommandEvent) -> None:
744754
UserInterface.getUIValues(self)
745755
UserInterface.writeUserPreferences()
746756

747-
def onClickReset(self, event):
757+
def onClickReset(self, event: wx.CommandEvent) -> None:
748758
UserInterface.loadDefaultPreferences()
749759
UserInterface.validateUserPreferences()
750760
UserInterface.setUIValues(self)
751761

752-
def onClickHelp(self, event):
762+
def onClickHelp(self, event: wx.CommandEvent) -> None:
753763
webbrowser.open("https://nsoiffer.github.io/MathCAT/users.html")
754764

755-
def onListBoxCategories(self, event):
765+
def onListBoxCategories(self, event: wx.CommandEvent) -> None:
756766
# the category changed, now show the appropriate dialogue page
757767
self._simplebookPanelsCategories.SetSelection(self._listBoxPreferencesTopic.GetSelection())
758768

759-
def onLanguage(self, event):
769+
def onLanguage(self, event: wx.CommandEvent) -> None:
760770
# the language changed, get the SpeechStyles for the new language
761771
UserInterface.getSpeechStyles(self, self._choiceSpeechStyle.GetStringSelection())
762772

763-
def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent):
773+
def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent) -> None:
764774
# designed choice is that Enter is the same as clicking OK, and Escape is the same as clicking Cancel
765-
keyCode = event.GetKeyCode()
775+
keyCode: int = event.GetKeyCode()
766776
if keyCode == wx.WXK_ESCAPE:
767777
UserInterface.onClickCancel(self, event)
768778
return
@@ -771,7 +781,7 @@ def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent):
771781
if keyCode == wx.WXK_TAB:
772782
if event.GetModifiers() == wx.MOD_CONTROL:
773783
# cycle the category forward
774-
newCategory = self._listBoxPreferencesTopic.GetSelection() + 1
784+
newCategory: int = self._listBoxPreferencesTopic.GetSelection() + 1
775785
if newCategory == 3:
776786
newCategory = 0
777787
self._listBoxPreferencesTopic.SetSelection(newCategory)
@@ -783,7 +793,7 @@ def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent):
783793
return
784794
if event.GetModifiers() == wx.MOD_CONTROL | wx.MOD_SHIFT:
785795
# cycle the category back
786-
newCategory = self._listBoxPreferencesTopic.GetSelection() - 1
796+
newCategory: int = self._listBoxPreferencesTopic.GetSelection() - 1
787797
if newCategory == -1:
788798
newCategory = 2
789799
self._listBoxPreferencesTopic.SetSelection(newCategory)

0 commit comments

Comments
 (0)