1010import gettext
1111import addonHandler
1212from logHandler import log # logging
13- from typing import List , Dict , Union , Callable
13+ from collections . abc import Callable
1414from .MathCAT import convertSSMLTextForNVDA
1515from speech import speak
1616from zipfile import ZipFile
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
2929Speech_DecimalSeparator = ("Auto" , "." , "," , "Custom" )
3030Speech_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