diff --git a/addon/globalPlugins/MathCAT/MathCAT.py b/addon/globalPlugins/MathCAT/MathCAT.py index fa7daaa..93983bb 100644 --- a/addon/globalPlugins/MathCAT/MathCAT.py +++ b/addon/globalPlugins/MathCAT/MathCAT.py @@ -23,14 +23,17 @@ import gui from . import libmathcat_py as libmathcat -from typing import List, Dict +from typing import Type +from collections.abc import Generator, Callable from keyboardHandler import KeyboardInputGesture # navigation key strokes from logHandler import log # logging from os import path # set rule dir path from scriptHandler import script # copy MathML via ctrl-c -from synthDriverHandler import getSynth # speech engine param setting +from synthDriverHandler import ( + getSynth, + SynthDriver, +) from ctypes import windll # register clipboard formats -from typing import Optional from speech import getCurrentLanguage from speech.types import SpeechSequence @@ -45,10 +48,13 @@ CharacterModeCommand, PhonemeCommand, IndexCommand, + ProsodyCommand, + SpeechCommand, + SynthCommand, ) from textUtils import WCHAR_ENCODING -from ctypes import c_wchar, WinError +from ctypes import c_wchar, WinError, Array from api import getClipData from synthDrivers import _espeak @@ -56,7 +62,7 @@ addonHandler.initTranslation() -RE_MATHML_SPEECH = re.compile( +RE_MATHML_SPEECH: re.Pattern = re.compile( # Break. r" ?" # Pronunciation of characters. @@ -73,12 +79,12 @@ r"|(?P[^<]+)", ) -PROSODY_COMMANDS = { +PROSODY_COMMANDS: dict[str, ProsodyCommand] = { "pitch": PitchCommand, "volume": VolumeCommand, "rate": RateCommand, } -RE_MATH_LANG = re.compile(r"""""") +RE_MATH_LANG: re.Pattern = re.compile(r"""""") # try to get around espeak bug where voice slows down (for other voices, just a waste of time) # we use a global that gets set at a time when the rate is probably good (SetMathML) @@ -87,7 +93,7 @@ def getLanguageToUse(mathMl: str = "") -> str: """Get the language specified in a math tag if the language pref is Auto, else the language preference.""" - mathCATLanguageSetting = "Auto" + mathCATLanguageSetting: str = "Auto" try: # ignore regional differences if the MathCAT language setting doesn't have it. mathCATLanguageSetting = libmathcat.GetPreference("Language") @@ -98,8 +104,8 @@ def getLanguageToUse(mathMl: str = "") -> str: if mathCATLanguageSetting != "Auto": return mathCATLanguageSetting - languageMatch = RE_MATH_LANG.search(mathMl) - language = ( + languageMatch: re.Match | None = RE_MATH_LANG.search(mathMl) + language: str = ( languageMatch.group(2) if languageMatch else getCurrentLanguage() ) # seems to be current voice's language language = language.lower().replace("_", "-") @@ -110,7 +116,7 @@ def getLanguageToUse(mathMl: str = "") -> str: return language -def convertSSMLTextForNVDA(text: str) -> list: +def convertSSMLTextForNVDA(text: str) -> list[str | SpeechCommand]: """Change the SSML in the text into NVDA's command structure. The environment is examined to determine whether a language switch is needed""" # MathCAT's default rate is 180 wpm. @@ -119,31 +125,31 @@ def convertSSMLTextForNVDA(text: str) -> list: # find MathCAT's language setting and store it away (could be "Auto") # if MathCAT's setting doesn't match NVDA's language setting, change the language that is used - mathCATLanguageSetting = "en" # set in case GetPreference fails + mathCATLanguageSetting: str = "en" # set in case GetPreference fails try: mathCATLanguageSetting = libmathcat.GetPreference("Language") except Exception as e: log.exception(e) - language = getLanguageToUse() - nvdaLanguage = getCurrentLanguage().replace("_", "-") + language: str = getLanguageToUse() + nvdaLanguage: str = getCurrentLanguage().replace("_", "-") # log.info(f"mathCATLanguageSetting={mathCATLanguageSetting}, lang={language}, NVDA={nvdaLanguage}") _monkeyPatchESpeak() - synth = getSynth() + synth: SynthDriver = getSynth() # I tried the engines on a 180 word excerpt. The speeds do not change linearly and differ a it between engines # At "50" espeak finished in 46 sec, sapi in 75 sec, and one core in 70; at '100' one core was much slower than the others - wpm = 2 * getSynth()._get_rate() - breakMulti = 180.0 / wpm - supportedCommands = synth.supportedCommands - useBreak = BreakCommand in supportedCommands - usePitch = PitchCommand in supportedCommands + wpm: int = 2 * getSynth()._get_rate() + breakMulti: float = 180.0 / wpm + supportedCommands: set[Type["SynthCommand"]] = synth.supportedCommands + useBreak: bool = BreakCommand in supportedCommands + usePitch: bool = PitchCommand in supportedCommands # use_rate = RateCommand in supported_commands # use_volume = VolumeCommand in supported_commands - usePhoneme = PhonemeCommand in supportedCommands + usePhoneme: bool = PhonemeCommand in supportedCommands # as of 7/23, oneCore voices do not implement the CharacterModeCommand despite it being in supported_commands - useCharacter = CharacterModeCommand in supportedCommands and synth.name != "oneCore" - out = [] + useCharacter: bool = CharacterModeCommand in supportedCommands and synth.name != "oneCore" + out: list[str | SpeechCommand] = [] if mathCATLanguageSetting != language: # log.info(f"Setting language to {language}") try: @@ -154,14 +160,14 @@ def convertSSMLTextForNVDA(text: str) -> list: if language != nvdaLanguage: out.append(LangChangeCommand(language)) - resetProsody = [] + resetProsody: list[Type["ProsodyCommand"]] = [] # log.info(f"\ntext: {text}") for m in RE_MATHML_SPEECH.finditer(text): if m.lastgroup == "break": if useBreak: out.append(BreakCommand(time=int(int(m.group("break")) * breakMulti))) elif m.lastgroup == "char": - ch = m.group("char") + ch: str = m.group("char") if useCharacter: out.extend((CharacterModeCommand(True), ch, CharacterModeCommand(False))) else: @@ -173,13 +179,13 @@ def convertSSMLTextForNVDA(text: str) -> list: out.append(PitchCommand(multiplier=int(m.group(m.lastgroup)))) resetProsody.append(PitchCommand) elif m.lastgroup in PROSODY_COMMANDS: - command = PROSODY_COMMANDS[m.lastgroup] + command: Type["ProsodyCommand"] = PROSODY_COMMANDS[m.lastgroup] if command in supportedCommands: out.append(command(multiplier=int(m.group(m.lastgroup)) / 100.0)) resetProsody.append(command) elif m.lastgroup == "prosodyReset": # for command in resetProsody: # only supported commands were added, so no need to check - command = resetProsody.pop() + command: Type["ProsodyCommand"] = resetProsody.pop() out.append(command(multiplier=1)) elif m.lastgroup == "phonemeText": if usePhoneme: @@ -210,27 +216,31 @@ class MathCATInteraction(mathPres.MathInteractionNVDAObject): # MathML is put on the clipboard using the two formats below (defined by MathML spec) # We use both formats because some apps may only use one or the other # Note: filed https://github.com/nvaccess/nvda/issues/13240 to make this usable outside of MathCAT - CF_MathML = windll.user32.RegisterClipboardFormatW("MathML") - CF_MathML_Presentation = windll.user32.RegisterClipboardFormatW( + CF_MathML: int = windll.user32.RegisterClipboardFormatW("MathML") + CF_MathML_Presentation: int = windll.user32.RegisterClipboardFormatW( "MathML Presentation", ) # log.info("2**** MathCAT registering data formats: # CF_MathML %x, CF_MathML_Presentation %x" % (CF_MathML, CF_MathML_Presentation)) - def __init__(self, provider=None, mathMl: Optional[str] = None): + def __init__( + self, + provider: mathPres.MathPresentationProvider = None, + mathMl: str | None = None, + ): super(MathCATInteraction, self).__init__(provider=provider, mathMl=mathMl) if mathMl is None: self.initMathML = "" else: self.initMathML = mathMl - def reportFocus(self): + def reportFocus(self) -> None: super(MathCATInteraction, self).reportFocus() # try to get around espeak bug where voice slows down if _synthesizerRate and getSynth().name == "espeak": getSynth()._set_rate(_synthesizerRate) try: - text = libmathcat.DoNavigateCommand("ZoomIn") + text: str = libmathcat.DoNavigateCommand("ZoomIn") speech.speak(convertSSMLTextForNVDA(text)) except Exception as e: log.exception(e) @@ -242,10 +252,13 @@ def reportFocus(self): # log.info(f'reportFocus: reset to {_synthesizer_rate}') getSynth()._set_rate(_synthesizerRate) - def getBrailleRegions(self, review: bool = False): + def getBrailleRegions( + self, + review: bool = False, + ) -> Generator[braille.Region, None, None]: # log.info("***MathCAT start getBrailleRegions") yield braille.NVDAObjectRegion(self, appendText=" ") - region = braille.Region() + region: braille.Region = braille.Region() region.focusToHardLeft = True # libmathcat.SetBrailleWidth(braille.handler.displaySize) try: @@ -259,7 +272,10 @@ def getBrailleRegions(self, review: bool = False): # log.info("***MathCAT end getBrailleRegions ***") yield region - def getScript(self, gesture: KeyboardInputGesture): + def getScript( + self, + gesture: KeyboardInputGesture, + ) -> Callable[KeyboardInputGesture, None] | None: # Pass most keys to MathCAT. Pretty ugly. if ( isinstance(gesture, KeyboardInputGesture) @@ -292,13 +308,13 @@ def getScript(self, gesture: KeyboardInputGesture): else: return super().getScript(gesture) - def script_navigate(self, gesture: KeyboardInputGesture): + def script_navigate(self, gesture: KeyboardInputGesture) -> None: try: # try to get around espeak bug where voice slows down if _synthesizerRate and getSynth().name == "espeak": getSynth()._set_rate(_synthesizerRate) if gesture is not None: # == None when initial focus -- handled in reportFocus() - modNames = gesture.modifierNames + modNames: list[str] = gesture.modifierNames text = libmathcat.DoNavigateKeyPress( gesture.vkCode, "shift" in modNames, @@ -323,10 +339,10 @@ def script_navigate(self, gesture: KeyboardInputGesture): try: # update the braille to reflect the nav position (might be excess code, but it works) - navNode = libmathcat.GetNavigationMathMLId() + navNode: tuple[str, int] = libmathcat.GetNavigationMathMLId() brailleChars = libmathcat.GetBraille(navNode[0]) # log.info(f'braille display = {config.conf["braille"]["display"]}, braille_chars: {braille_chars}') - region = braille.Region() + region: braille.Region = braille.Region() region.rawText = brailleChars region.focusToHardLeft = True region.update() @@ -339,7 +355,7 @@ def script_navigate(self, gesture: KeyboardInputGesture): # Translators: this message directs users to look in the log file speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) - _startsWithMath = re.compile("\\s*? None: try: - copyAs = "mathml" # value used even if "CopyAs" pref is invalid - textToCopy = "" + copyAs: str = "mathml" # value used even if "CopyAs" pref is invalid + textToCopy: str = "" try: copyAs = libmathcat.GetPreference("CopyAs").lower() except Exception as e: @@ -365,7 +381,7 @@ def script_rawdataToClip(self, gesture: KeyboardInputGesture): if copyAs == "asciimath": copyAs = "ASCIIMath" # speaks better in at least some voices else: - mathml = libmathcat.GetNavigationMathML()[0] + mathml: str = libmathcat.GetNavigationMathML()[0] if not re.match(self._startsWithMath, mathml): mathml = "\n" + mathml + "" # copy will fix up name spacing elif self.initMathML != "": @@ -373,7 +389,7 @@ def script_rawdataToClip(self, gesture: KeyboardInputGesture): if copyAs == "speech": # save the old MathML, set the navigation MathML as MathMl, get the speech, then reset the MathML savedMathML: str = self.initMathML - savedTTS = libmathcat.GetPreference("TTS") + savedTTS: str = libmathcat.GetPreference("TTS") if savedMathML == "": # shouldn't happen raise Exception("Internal error -- MathML not set for copy") libmathcat.SetPreference("TTS", "None") @@ -395,14 +411,14 @@ def script_rawdataToClip(self, gesture: KeyboardInputGesture): # not a perfect match sequence, but should capture normal MathML # not a perfect match sequence, but should capture normal MathML - _mathTagHasNameSpace = re.compile("") - _hasAddedId = re.compile(" id='[^'].+' data-id-added='true'") - _hasDataAttr = re.compile(" data-[^=]+='[^']*'") + _mathTagHasNameSpace: re.Pattern = re.compile("") + _hasAddedId: re.Pattern = re.compile(" id='[^'].+' data-id-added='true'") + _hasDataAttr: re.Pattern = re.compile(" data-[^=]+='[^']*'") def _wrapMathMLForClipBoard(self, text: str) -> str: # cleanup the MathML a little text = re.sub(self._hasAddedId, "", text) - mathMLWithNS = re.sub(self._hasDataAttr, "", text) + mathMLWithNS: str = re.sub(self._hasDataAttr, "", text) if not re.match(self._mathTagHasNameSpace, mathMLWithNS): mathMLWithNS = mathMLWithNS.replace( "math", @@ -411,7 +427,12 @@ def _wrapMathMLForClipBoard(self, text: str) -> str: ) return mathMLWithNS - def _copyToClipAsMathML(self, text: str, isMathML: bool, notify: Optional[bool] = False) -> bool: + def _copyToClipAsMathML( + self, + text: str, + isMathML: bool, + notify: bool | None = False, + ) -> bool: """Copies the given text to the windows clipboard. @returns: True if it succeeds, False otherwise. @param text: the text which will be copied to the clipboard @@ -428,7 +449,7 @@ def _copyToClipAsMathML(self, text: str, isMathML: bool, notify: Optional[bool] self._setClipboardData(self.CF_MathML, '' + text) self._setClipboardData(self.CF_MathML_Presentation, '' + text) self._setClipboardData(winUser.CF_UNICODETEXT, text) - got = getClipData() + got: str = getClipData() except OSError: if notify: ui.reportTextCopiedToClipboard() # No argument reports a failure. @@ -441,19 +462,19 @@ def _copyToClipAsMathML(self, text: str, isMathML: bool, notify: Optional[bool] ui.reportTextCopiedToClipboard() # No argument reports a failure. return False - def _setClipboardData(self, format, data: str): + def _setClipboardData(self, format: int, data: str) -> None: # Need to support MathML Presentation, so this copied from winUser.py and the first two lines are commented out # For now only unicode is a supported format # if format!=CF_UNICODETEXT: # raise ValueError("Unsupported format") - text = data - bufLen = len(text.encode(WCHAR_ENCODING, errors="surrogatepass")) + 2 + text: str = data + bufLen: int = len(text.encode(WCHAR_ENCODING, errors="surrogatepass")) + 2 # Allocate global memory - h = winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE, bufLen) + h: winKernel.HGLOBAL = winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE, bufLen) # Acquire a lock to the global memory receiving a local memory address with h.lock() as addr: # Write the text into the allocated memory - buf = (c_wchar * bufLen).from_address(addr) + buf: Array[c_wchar] = (c_wchar * bufLen).from_address(addr) buf.value = text # Set the clipboard data with the global memory if not windll.user32.SetClipboardData(format, h): @@ -468,7 +489,7 @@ def __init__(self): try: # IMPORTANT -- SetRulesDir must be the first call to libmathcat besides GetVersion() - rulesDir = path.join(path.dirname(path.abspath(__file__)), "Rules") + rulesDir: str = path.join(path.dirname(path.abspath(__file__)), "Rules") log.info(f"MathCAT {libmathcat.GetVersion()} installed. Using rules dir: {rulesDir}") libmathcat.SetRulesDir(rulesDir) libmathcat.SetPreference("TTS", "SSML") @@ -477,18 +498,21 @@ def __init__(self): # Translators: this message directs users to look in the log file speech.speakMessage(_("MathCAT initialization failed: see NVDA error log for details")) - def getSpeechForMathMl(self, mathml: str): + def getSpeechForMathMl( + self, + mathml: str, + ) -> list[str | SpeechCommand]: global _synthesizerRate - synth = getSynth() + synth: SynthDriver = getSynth() synthConfig = config.conf["speech"][synth.name] if synth.name == "espeak": - _synthesizerRate = synthConfig["rate"] + _synthesizerRate: int = synthConfig["rate"] # log.info(f'_synthesizer_rate={_synthesizer_rate}, get_rate()={getSynth()._get_rate()}') getSynth()._set_rate(_synthesizerRate) # log.info(f'..............get_rate()={getSynth()._get_rate()}, name={synth.name}') try: # need to set Language before the MathML for DecimalSeparator canonicalization - language = getLanguageToUse(mathml) + language: str = getLanguageToUse(mathml) # MathCAT should probably be extended to accept "extlang" tagging, but it uses lang-region tagging at the moment libmathcat.SetPreference("Language", language) libmathcat.SetMathML(mathml) @@ -499,7 +523,7 @@ def getSpeechForMathMl(self, mathml: str): speech.speakMessage(_("Illegal MathML found: see NVDA error log for details")) libmathcat.SetMathML("") # set it to something try: - supportedCommands = synth.supportedCommands + supportedCommands: set[Type["SynthCommand"]] = synth.supportedCommands # Set preferences for capital letters libmathcat.SetPreference( "CapitalLetters_Beep", @@ -532,14 +556,14 @@ def getSpeechForMathMl(self, mathml: str): # log.info(f'getSpeechForMathMl: reset to {_synthesizer_rate}') getSynth()._set_rate(_synthesizerRate) - def _addSounds(self): + def _addSounds(self) -> bool: try: return libmathcat.GetPreference("SpeechSound") != "None" except Exception as e: log.exception(f"MathCAT: An exception occurred in _add_sounds: {e}") return False - def getBrailleForMathMl(self, mathml: str): + def getBrailleForMathMl(self, mathml: str) -> str: # log.info("***MathCAT getBrailleForMathMl") try: libmathcat.SetMathML(mathml) @@ -557,17 +581,17 @@ def getBrailleForMathMl(self, mathml: str): speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) return "" - def interactWithMathMl(self, mathml: str): + def interactWithMathMl(self, mathml: str) -> None: MathCATInteraction(provider=self, mathMl=mathml).setFocus() MathCATInteraction(provider=self, mathMl=mathml).script_navigate(None) -CACHED_SYNTH = None +CACHED_SYNTH: SynthDriver | None = None -def _monkeyPatchESpeak(): +def _monkeyPatchESpeak() -> None: global CACHED_SYNTH - currentSynth = getSynth() + currentSynth: SynthDriver = getSynth() if currentSynth.name != "espeak" or CACHED_SYNTH == currentSynth: return # already patched @@ -575,11 +599,11 @@ def _monkeyPatchESpeak(): currentSynth.speak = patchedSpeak.__get__(currentSynth, type(currentSynth)) -def patchedSpeak(self, speechSequence: SpeechSequence): # noqa: C901 +def patchedSpeak(self, speechSequence: SpeechSequence) -> None: # noqa: C901 # log.info(f"\npatched_speak input: {speechSequence}") - textList: List[str] = [] + textList: list[str] = [] langChanged = False - prosody: Dict[str, int] = {} + prosody: dict[str, int] = {} # We output malformed XML, as we might close an outer tag after opening an inner one; e.g. # . # However, eSpeak doesn't seem to mind. @@ -624,7 +648,7 @@ def patchedSpeak(self, speechSequence: SpeechSequence): # noqa: C901 elif isinstance(item, PhonemeCommand): # We can't use str.translate because we want to reject unknown characters. try: - phonemes = "".join([self.IPA_TO_ESPEAK[char] for char in item.ipa]) + phonemes: str = "".join([self.IPA_TO_ESPEAK[char] for char in item.ipa]) # There needs to be a space after the phoneme command. # Otherwise, eSpeak will announce a subsequent SSML tag instead of processing it. textList.append("[[%s]] " % phonemes) diff --git a/addon/globalPlugins/MathCAT/MathCATPreferences.py b/addon/globalPlugins/MathCAT/MathCATPreferences.py index ddd314a..8f2ed1e 100644 --- a/addon/globalPlugins/MathCAT/MathCATPreferences.py +++ b/addon/globalPlugins/MathCAT/MathCATPreferences.py @@ -10,7 +10,7 @@ import gettext import addonHandler from logHandler import log # logging -from typing import List, Dict, Union, Callable +from collections.abc import Callable from .MathCAT import convertSSMLTextForNVDA from speech import speak from zipfile import ZipFile @@ -20,11 +20,11 @@ # two constants to scale "PauseFactor" # these work out so that a slider that goes [0,14] has value ~100 at 7 and ~1000 at 14 -PAUSE_FACTOR_SCALE = 9.5 -PAUSE_FACTOR_LOG_BASE = 1.4 +PAUSE_FACTOR_SCALE: float = 9.5 +PAUSE_FACTOR_LOG_BASE: float = 1.4 # initialize the user preferences tuples -userPreferences: Dict[str, Dict[str, Union[int, str, bool]]] = {} +userPreferences: dict[str, dict[str, int | str | bool]] = {} # Speech_Language is derived from the folder structures Speech_DecimalSeparator = ("Auto", ".", ",", "Custom") Speech_Impairment = ("LearningDisability", "Blindness", "LowVision") @@ -47,7 +47,7 @@ def __init__(self, parent): MathCATgui.MathCATPreferencesDialog.__init__(self, parent) # load the logo into the dialog - fullPathToLogo = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png") + fullPathToLogo: str = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png") if os.path.exists(fullPathToLogo): self._bitmapLogo.SetBitmap(wx.Bitmap(fullPathToLogo)) @@ -78,17 +78,17 @@ def __init__(self, parent): UserInterface.setUIValues(self) @staticmethod - def pathToLanguagesFolder(): + def pathToLanguagesFolder() -> str: # the user preferences file is stored at: MathCAT\Rules\Languages return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Languages") @staticmethod - def pathToBrailleFolder(): + def pathToBrailleFolder() -> str: # the user preferences file is stored at: MathCAT\Rules\Languages return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Braille") @staticmethod - def languagesDict() -> Dict[str, str]: + def languagesDict() -> dict[str, str]: languages = { "aa": "Afar", "ab": "Аҧсуа", @@ -268,10 +268,12 @@ def languagesDict() -> Dict[str, str]: def getRulesFiles( self, pathToDir: str, - processSubDirs: Callable[[str, str], List[str]] | None, - ) -> List[str]: - language = os.path.basename(pathToDir) - ruleFiles = [os.path.basename(file) for file in glob.glob(os.path.join(pathToDir, "*_Rules.yaml"))] + processSubDirs: Callable[[str, str], list[str]] | None, + ) -> list[str]: + language: str = os.path.basename(pathToDir) + ruleFiles: list[str] = [ + os.path.basename(file) for file in glob.glob(os.path.join(pathToDir, "*_Rules.yaml")) + ] for dir in os.listdir(pathToDir): if os.path.isdir(os.path.join(pathToDir, dir)): if processSubDirs: @@ -280,7 +282,7 @@ def getRulesFiles( if len(ruleFiles) == 0: # look in the .zip file for the style files, including regional subdirs -- it might not have been unzipped try: - zip_file = ZipFile(f"{pathToDir}\\{language}.zip", "r") + zip_file: ZipFile = ZipFile(f"{pathToDir}\\{language}.zip", "r") for file in zip_file.namelist(): if file.endswith("_Rules.yaml"): ruleFiles.append(file) @@ -291,15 +293,15 @@ def getRulesFiles( return ruleFiles - def getLanguages(self): - def addRegionalLanguages(subDir: str, language: str) -> List[str]: + def getLanguages(self) -> None: + def addRegionalLanguages(subDir: str, language: str) -> list[str]: # the language variants are in folders named using ISO 3166-1 alpha-2 # codes https://en.wikipedia.org/wiki/ISO_3166-2 # check if there are language variants in the language folder if subDir != "SharedRules": - languagesDict = UserInterface.languagesDict() + languagesDict: dict[str, str] = UserInterface.languagesDict() # add to the listbox the text for this language variant together with the code - regionalCode = language + "-" + subDir.upper() + regionalCode: str = language + "-" + subDir.upper() if languagesDict.get(regionalCode, "missing") != "missing": self._choiceLanguage.Append(f"{languagesDict[regionalCode]} ({language}-{subDir})") elif languagesDict.get(language, "missing") != "missing": @@ -310,7 +312,7 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]: return [] # initialise the language list - languagesDict = UserInterface.languagesDict() + languagesDict: dict[str, str] = UserInterface.languagesDict() # clear the language names in the dialog self._choiceLanguage.Clear() # Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog @@ -319,9 +321,9 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]: # populate the available language names in the dialog # the implemented languages are in folders named using the relevant ISO 639-1 # code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes - languageDir = UserInterface.pathToLanguagesFolder() + languageDir: str = UserInterface.pathToLanguagesFolder() for language in os.listdir(languageDir): - pathToLanguageDir = os.path.join(UserInterface.pathToLanguagesFolder(), language) + pathToLanguageDir: str = os.path.join(UserInterface.pathToLanguagesFolder(), language) if os.path.isdir(pathToLanguageDir): # only add this language if there is a xxx_Rules.yaml file if len(self.getRulesFiles(pathToLanguageDir, addRegionalLanguages)) > 0: @@ -331,10 +333,10 @@ def addRegionalLanguages(subDir: str, language: str) -> List[str]: else: self._choiceLanguage.Append(language + " (" + language + ")") - def getLanguageCode(self): - lang_selection = self._choiceLanguage.GetStringSelection() - lang_code = lang_selection[lang_selection.find("(") + 1 : lang_selection.find(")")] - return lang_code + def getLanguageCode(self) -> str: + langSelection: str = self._choiceLanguage.GetStringSelection() + langCode: str = langSelection[langSelection.find("(") + 1 : langSelection.find(")")] + return langCode def getSpeechStyles(self, thisSpeechStyle: str): """Get all the speech styles for the current language. @@ -346,10 +348,12 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]: The 'lang', if it has a region dialect, is of the form 'en\uk' The returned list is sorted alphabetically""" # start with the regional dialect, then add on any (unique) styles in the main dir - mainLang = lang.split("\\")[0] # does the right thing even if there is no regional directory - allStyleFiles = [] + mainLang: str = lang.split("\\")[0] # does the right thing even if there is no regional directory + allStyleFiles: list[str] = [] if lang.find("\\") >= 0: - allStyleFiles = [os.path.basename(name) for name in glob.glob(dir + lang + "\\*_Rules.yaml")] + allStyleFiles: list[str] = [ + os.path.basename(name) for name in glob.glob(dir + lang + "\\*_Rules.yaml") + ] allStyleFiles.extend( [os.path.basename(name) for name in glob.glob(dir + mainLang + "\\*_Rules.yaml")], ) @@ -357,8 +361,8 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]: if len(allStyleFiles) == 0: # look in the .zip file for the style files -- this will have regional variants, but also have that dir try: - zipFile = dir + mainLang + "\\" + mainLang + ".zip" - zipFile = ZipFile(zipFile, "r") # file might not exist + zipFilePath: str = dir + mainLang + "\\" + mainLang + ".zip" + zipFile: ZipFile = ZipFile(zipFilePath, "r") # file might not exist allStyleFiles = [ name.split("/")[-1] for name in zipFile.namelist() if name.endswith("_Rules.yaml") ] @@ -370,7 +374,7 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]: # clear the SpeechStyle choices self._choiceSpeechStyle.Clear() # get the currently selected language code - languageCode = UserInterface.getLanguageCode(self) + languageCode: str = UserInterface.getLanguageCode(self) if languageCode == "Auto": # list the speech styles for the current voice rather than have none listed @@ -402,28 +406,28 @@ def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]: # that didn't work, choose the first in the list self._choiceSpeechStyle.SetSelection(0) - def getBrailleCodes(self): + def getBrailleCodes(self) -> None: # initialise the braille code list self._choiceBrailleMathCode.Clear() # populate the available braille codes in the dialog # the dir names are used, not the rule file names because the dir names have to be unique - pathToBrailleFolder = UserInterface.pathToBrailleFolder() + pathToBrailleFolder: str = UserInterface.pathToBrailleFolder() for brailleCode in os.listdir(pathToBrailleFolder): - pathToBrailleCode = os.path.join(pathToBrailleFolder, brailleCode) + pathToBrailleCode: str = os.path.join(pathToBrailleFolder, brailleCode) if os.path.isdir(pathToBrailleCode): if len(self.getRulesFiles(pathToBrailleCode, None)) > 0: self._choiceBrailleMathCode.Append(brailleCode) - def setUIValues(self): + def setUIValues(self) -> None: # set the UI elements to the ones read from the preference file(s) try: self._choiceImpairment.SetSelection( Speech_Impairment.index(userPreferences["Speech"]["Impairment"]), ) try: - langPref = userPreferences["Speech"]["Language"] + langPref: str = userPreferences["Speech"]["Language"] self._choiceLanguage.SetSelection(0) - i = 1 # no need to test i == 0 + i: int = 1 # no need to test i == 0 while i < self._choiceLanguage.GetCount(): if f"({langPref})" in self._choiceLanguage.GetString(i): self._choiceLanguage.SetSelection(i) @@ -489,7 +493,7 @@ def setUIValues(self): Braille_BrailleNavHighlight.index(userPreferences["Braille"]["BrailleNavHighlight"]), ) try: - braillePref = userPreferences["Braille"]["BrailleCode"] + braillePref: str = userPreferences["Braille"]["BrailleCode"] i = 0 while braillePref != self._choiceBrailleMathCode.GetString(i): i = i + 1 @@ -507,7 +511,7 @@ def setUIValues(self): except KeyError as err: print("Key not found", err) - def getUIValues(self): + def getUIValues(self) -> None: global userPreferences # read the values from the UI and update the user preferences dictionary userPreferences["Speech"]["Impairment"] = Speech_Impairment[self._choiceImpairment.GetSelection()] @@ -518,8 +522,8 @@ def getUIValues(self): userPreferences["Speech"]["SpeechStyle"] = self._choiceSpeechStyle.GetStringSelection() userPreferences["Speech"]["Verbosity"] = Speech_Verbosity[self._choiceSpeechAmount.GetSelection()] userPreferences["Speech"]["MathRate"] = self._sliderRelativeSpeed.GetValue() - pfSlider = self._sliderPauseFactor.GetValue() - pauseFactor = ( + pfSlider: int = self._sliderPauseFactor.GetValue() + pauseFactor: int = ( 0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider)) ) # avoid log(0) userPreferences["Speech"]["PauseFactor"] = pauseFactor @@ -551,21 +555,21 @@ def getUIValues(self): userPreferences["NVDAAddOn"]["LastCategory"] = self._listBoxPreferencesTopic.GetSelection() @staticmethod - def pathToDefaultPreferences(): + def pathToDefaultPreferences() -> str: return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "prefs.yaml") @staticmethod - def pathToUserPreferencesFolder(): + def pathToUserPreferencesFolder() -> str: # the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml return os.path.join(os.path.expandvars("%APPDATA%"), "MathCAT") @staticmethod - def pathToUserPreferences(): + def pathToUserPreferences() -> str: # the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml return os.path.join(UserInterface.pathToUserPreferencesFolder(), "prefs.yaml") @staticmethod - def loadDefaultPreferences(): + def loadDefaultPreferences() -> None: global userPreferences # load default preferences into the user preferences data structure (overwrites existing) if os.path.exists(UserInterface.pathToDefaultPreferences()): @@ -576,7 +580,7 @@ def loadDefaultPreferences(): userPreferences = yaml.load(f, Loader=yaml.FullLoader) @staticmethod - def loadUserPreferences(): + def loadUserPreferences() -> None: global userPreferences # merge user file values into the user preferences data structure if os.path.exists(UserInterface.pathToUserPreferences()): @@ -585,7 +589,12 @@ def loadUserPreferences(): userPreferences.update(yaml.load(f, Loader=yaml.FullLoader)) @staticmethod - def validate(key1: str, key2: str, validValues: List[Union[str, bool]], defaultValue: Union[str, bool]): + def validate( + key1: str, + key2: str, + validValues: list[str | bool], + defaultValue: str | bool, + ) -> None: global userPreferences try: if validValues == []: @@ -606,7 +615,12 @@ def validate(key1: str, key2: str, validValues: List[Union[str, bool]], defaultV userPreferences[key1][key2] = defaultValue @staticmethod - def validateInt(key1: str, key2: str, validValues: List[int], defaultValue: int): + def validateInt( + key1: str, + key2: str, + validValues: list[int], + defaultValue: int, + ) -> None: global userPreferences try: # any value between lower and upper bounds is valid @@ -678,7 +692,7 @@ def validateUserPreferences(): UserInterface.validate("Braille", "BrailleCode", [], "Nemeth") @staticmethod - def writeUserPreferences(): + def writeUserPreferences() -> None: # Language is special because it is set elsewhere by SetPreference which overrides the user_prefs -- so set it here from . import libmathcat_py as libmathcat @@ -695,23 +709,23 @@ def writeUserPreferences(): # write values to the user preferences file, NOT the default yaml.dump(userPreferences, stream=f, allow_unicode=True) - def onRelativeSpeedChanged(self, event): - rate = self._sliderRelativeSpeed.GetValue() + def onRelativeSpeedChanged(self, event: wx.ScrollEvent) -> None: + rate: int = self._sliderRelativeSpeed.GetValue() # Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" - text = _("the square root of x squared plus y squared").replace( + text: str = _("the square root of x squared plus y squared").replace( "XXX", str(rate), 1, ) speak(convertSSMLTextForNVDA(text)) - def onPauseFactorChanged(self, event): - rate = self._sliderRelativeSpeed.GetValue() + def onPauseFactorChanged(self, event: wx.ScrollEvent) -> None: + rate: int = self._sliderRelativeSpeed.GetValue() pfSlider = self._sliderPauseFactor.GetValue() pauseFactor = ( 0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider)) ) - text = _( + text: str = _( # Translators: this is a test string that is spoken. Only translate "the fraction with numerator" # and other parts NOT inside '<.../>', "the fraction with numerator \ @@ -732,37 +746,37 @@ def onPauseFactorChanged(self, event): ) speak(convertSSMLTextForNVDA(text)) - def onClickOK(self, event): + def onClickOK(self, event: wx.CommandEvent) -> None: UserInterface.getUIValues(self) UserInterface.writeUserPreferences() self.Destroy() - def onClickCancel(self, event): + def onClickCancel(self, event: wx.CommandEvent) -> None: self.Destroy() - def onClickApply(self, event): + def onClickApply(self, event: wx.CommandEvent) -> None: UserInterface.getUIValues(self) UserInterface.writeUserPreferences() - def onClickReset(self, event): + def onClickReset(self, event: wx.CommandEvent) -> None: UserInterface.loadDefaultPreferences() UserInterface.validateUserPreferences() UserInterface.setUIValues(self) - def onClickHelp(self, event): + def onClickHelp(self, event: wx.CommandEvent) -> None: webbrowser.open("https://nsoiffer.github.io/MathCAT/users.html") - def onListBoxCategories(self, event): + def onListBoxCategories(self, event: wx.CommandEvent) -> None: # the category changed, now show the appropriate dialogue page self._simplebookPanelsCategories.SetSelection(self._listBoxPreferencesTopic.GetSelection()) - def onLanguage(self, event): + def onLanguage(self, event: wx.CommandEvent) -> None: # the language changed, get the SpeechStyles for the new language UserInterface.getSpeechStyles(self, self._choiceSpeechStyle.GetStringSelection()) - def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent): + def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent) -> None: # designed choice is that Enter is the same as clicking OK, and Escape is the same as clicking Cancel - keyCode = event.GetKeyCode() + keyCode: int = event.GetKeyCode() if keyCode == wx.WXK_ESCAPE: UserInterface.onClickCancel(self, event) return @@ -771,7 +785,7 @@ def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent): if keyCode == wx.WXK_TAB: if event.GetModifiers() == wx.MOD_CONTROL: # cycle the category forward - newCategory = self._listBoxPreferencesTopic.GetSelection() + 1 + newCategory: int = self._listBoxPreferencesTopic.GetSelection() + 1 if newCategory == 3: newCategory = 0 self._listBoxPreferencesTopic.SetSelection(newCategory) @@ -783,7 +797,7 @@ def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent): return if event.GetModifiers() == wx.MOD_CONTROL | wx.MOD_SHIFT: # cycle the category back - newCategory = self._listBoxPreferencesTopic.GetSelection() - 1 + newCategory: int = self._listBoxPreferencesTopic.GetSelection() - 1 if newCategory == -1: newCategory = 2 self._listBoxPreferencesTopic.SetSelection(newCategory) diff --git a/addon/globalPlugins/MathCAT/MathCATgui.py b/addon/globalPlugins/MathCAT/MathCATgui.py index de214e5..e0bacf3 100644 --- a/addon/globalPlugins/MathCAT/MathCATgui.py +++ b/addon/globalPlugins/MathCAT/MathCATgui.py @@ -27,20 +27,20 @@ def __init__(self, parent): self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) - gbSizerMathCATPreferences = wx.GridBagSizer(0, 0) + gbSizerMathCATPreferences: wx.GridBagSizer = wx.GridBagSizer(0, 0) gbSizerMathCATPreferences.SetFlexibleDirection(wx.BOTH) gbSizerMathCATPreferences.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) - self._panelCategories = wx.Panel( + self._panelCategories: wx.Panel = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL, ) - bSizerCategories = wx.BoxSizer(wx.VERTICAL) + bSizerCategories: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) - self._staticTextCategories = wx.StaticText( + self._staticTextCategories: wx.StaticText = wx.StaticText( self._panelCategories, wx.ID_ANY, # Translators: A heading that labels three navigation pane tab names in the MathCAT dialog @@ -53,7 +53,7 @@ def __init__(self, parent): bSizerCategories.Add(self._staticTextCategories, 0, wx.ALL, 5) - listBoxPreferencesTopicChoices = [ + listBoxPreferencesTopicChoices: list[str] = [ # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" _("Speech"), # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" @@ -61,7 +61,7 @@ def __init__(self, parent): # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" _("Braille"), ] - self._listBoxPreferencesTopic = wx.ListBox( + self._listBoxPreferencesTopic: wx.ListBox = wx.ListBox( self._panelCategories, wx.ID_ANY, wx.Point(-1, -1), @@ -73,7 +73,7 @@ def __init__(self, parent): bSizerCategories.Add((0, 0), 1, wx.EXPAND, 5) - self._bitmapLogo = wx.StaticBitmap( + self._bitmapLogo: wx.StaticBitmap = wx.StaticBitmap( self._panelCategories, wx.ID_ANY, wx.NullBitmap, @@ -94,25 +94,25 @@ def __init__(self, parent): 5, ) - self._simplebookPanelsCategories = wx.Simplebook( + self._simplebookPanelsCategories: wx.Simplebook = wx.Simplebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0, ) - self._panelSpeech = wx.Panel( + self._panelSpeech: wx.Panel = wx.Panel( self._simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_SIMPLE | wx.TAB_TRAVERSAL, ) - bSizerSpeech = wx.BoxSizer(wx.VERTICAL) + bSizerSpeech: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) - bSizerImpairment = wx.BoxSizer(wx.HORIZONTAL) + bSizerImpairment: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextImpairment = wx.StaticText( + self._staticTextImpairment: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: this is the text label for whom to target the speech for (options are below) @@ -125,7 +125,7 @@ def __init__(self, parent): bSizerImpairment.Add(self._staticTextImpairment, 0, wx.ALL, 5) - impairmentChoices = [ + impairmentChoices: list[str] = [ # Translators: these are the categories of impairments that MathCAT supports # Translators: Learning disabilities includes dyslexia and ADHD _("Learning disabilities"), @@ -134,7 +134,7 @@ def __init__(self, parent): # Translators: target people who have low vision _("Low vision"), ] - self._choiceImpairment = wx.Choice( + self._choiceImpairment: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -147,9 +147,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerImpairment, 1, wx.EXPAND, 5) - bSizerLanguage = wx.BoxSizer(wx.HORIZONTAL) + bSizerLanguage: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextLanguage = wx.StaticText( + self._staticTextLanguage: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down allowing users to choose the speech language for math @@ -162,8 +162,8 @@ def __init__(self, parent): bSizerLanguage.Add(self._staticTextLanguage, 0, wx.ALL, 5) - languageChoices = ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] - self._choiceLanguage = wx.Choice( + languageChoices: list[str] = ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] + self._choiceLanguage: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -176,9 +176,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerLanguage, 1, wx.EXPAND, 5) - bSizerDecimalSeparator = wx.BoxSizer(wx.HORIZONTAL) + bSizerDecimalSeparator: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextDecimalSeparator = wx.StaticText( + self._staticTextDecimalSeparator: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down to specify what character to use in numbers as the decimal separator @@ -192,7 +192,7 @@ def __init__(self, parent): bSizerDecimalSeparator.Add(self._staticTextDecimalSeparator, 0, wx.ALL, 5) # Translators: options for decimal separator. - decimalSeparatorChoices = [ + decimalSeparatorChoices: list[str] = [ # Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language _("Auto"), # options for decimal separator -- use "." (and use ", " for block separators) @@ -203,7 +203,7 @@ def __init__(self, parent): # Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it _("Custom"), ] - self._choiceDecimalSeparator = wx.Choice( + self._choiceDecimalSeparator: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -216,9 +216,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerDecimalSeparator, 1, wx.EXPAND, 5) - bSizerSpeechStyle = wx.BoxSizer(wx.HORIZONTAL) + bSizerSpeechStyle: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextSpeechStyle = wx.StaticText( + self._staticTextSpeechStyle: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math @@ -231,8 +231,8 @@ def __init__(self, parent): bSizerSpeechStyle.Add(self._staticTextSpeechStyle, 0, wx.ALL, 5) - speechStyleChoices = ["xxxxxxxxxxxxxxxx"] - self._choiceSpeechStyle = wx.Choice( + speechStyleChoices: list[str] = ["xxxxxxxxxxxxxxxx"] + self._choiceSpeechStyle: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -245,9 +245,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerSpeechStyle, 1, wx.EXPAND, 5) - bSizerSpeechAmount = wx.BoxSizer(wx.HORIZONTAL) + bSizerSpeechAmount: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextSpeechAmount = wx.StaticText( + self._staticTextSpeechAmount: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down to specify how verbose/terse the speech should be @@ -261,7 +261,7 @@ def __init__(self, parent): bSizerSpeechAmount.Add(self._staticTextSpeechAmount, 0, wx.ALL, 5) # Translators: options for speech verbosity. - speechAmountChoices = [ + speechAmountChoices: list[str] = [ # Translators: options for speech verbosity -- "terse" = use less words _("Terse"), # Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words @@ -269,7 +269,7 @@ def __init__(self, parent): # Translators: options for speech verbosity -- "verbose" = use more words _("Verbose"), ] - self._choiceSpeechAmount = wx.Choice( + self._choiceSpeechAmount: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -282,9 +282,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerSpeechAmount, 1, wx.EXPAND, 5) - bSizerRelativeSpeed = wx.BoxSizer(wx.HORIZONTAL) + bSizerRelativeSpeed: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextRelativeSpeed = wx.StaticText( + self._staticTextRelativeSpeed: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math @@ -297,7 +297,7 @@ def __init__(self, parent): bSizerRelativeSpeed.Add(self._staticTextRelativeSpeed, 0, wx.ALL, 5) - self._sliderRelativeSpeed = wx.Slider( + self._sliderRelativeSpeed: wx.Slider = wx.Slider( self._panelSpeech, wx.ID_ANY, 100, @@ -312,9 +312,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerRelativeSpeed, 1, wx.EXPAND, 5) - bSizerPauseFactor = wx.BoxSizer(wx.HORIZONTAL) + bSizerPauseFactor: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticPauseFactor = wx.StaticText( + self._staticPauseFactor: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech @@ -327,7 +327,7 @@ def __init__(self, parent): bSizerPauseFactor.Add(self._staticPauseFactor, 0, wx.ALL, 5) - self._sliderPauseFactor = wx.Slider( + self._sliderPauseFactor: wx.Slider = wx.Slider( self._panelSpeech, wx.ID_ANY, 7, @@ -341,9 +341,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerPauseFactor, 1, wx.EXPAND, 5) - bSizerSpeechSound = wx.BoxSizer(wx.HORIZONTAL) + bSizerSpeechSound: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._checkBoxSpeechSound = wx.CheckBox( + self._checkBoxSpeechSound: wx.CheckBox = wx.CheckBox( self._panelSpeech, wx.ID_ANY, # Translators: label for check box controling a beep sound @@ -356,9 +356,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerSpeechSound, 1, wx.EXPAND, 5) - bSizerSubjectArea = wx.BoxSizer(wx.HORIZONTAL) + bSizerSubjectArea: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextSubjectArea = wx.StaticText( + self._staticTextSubjectArea: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) @@ -372,8 +372,8 @@ def __init__(self, parent): bSizerSubjectArea.Add(self._staticTextSubjectArea, 0, wx.ALL, 5) # Translators: a generic (non-specific) math subject area - subjectAreaChoices = [_("General")] - self._choiceSubjectArea = wx.Choice( + subjectAreaChoices: list[str] = [_("General")] + self._choiceSubjectArea: wx.Choice = wx.Choice( self._panelSpeech, wx.ID_ANY, wx.DefaultPosition, @@ -386,9 +386,9 @@ def __init__(self, parent): bSizerSpeech.Add(bSizerSubjectArea, 1, wx.EXPAND, 5) - bSizerSpeechForChemical = wx.BoxSizer(wx.HORIZONTAL) + bSizerSpeechForChemical: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextSpeechForChemical = wx.StaticText( + self._staticTextSpeechForChemical: wx.StaticText = wx.StaticText( self._panelSpeech, wx.ID_ANY, # Translators: label for pull down to specify how verbose/terse the speech should be @@ -401,7 +401,7 @@ def __init__(self, parent): bSizerSpeechForChemical.Add(self._staticTextSpeechForChemical, 0, wx.ALL, 5) - speechForChemicalChoices = [ + speechForChemicalChoices: list[str] = [ # Translators: values for chemistry options with example speech in parenthesis _("Spell it out (H 2 O)"), # Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) @@ -424,16 +424,16 @@ def __init__(self, parent): self._panelSpeech.Layout() bSizerSpeech.Fit(self._panelSpeech) self._simplebookPanelsCategories.AddPage(self._panelSpeech, "a page", False) - self._panelNavigation = wx.Panel( + self._panelNavigation: wx.Panel = wx.Panel( self._simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_SIMPLE | wx.TAB_TRAVERSAL, ) - bSizerNavigation = wx.BoxSizer(wx.VERTICAL) + bSizerNavigation: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) - sbSizerNavigationMode = wx.StaticBoxSizer( + sbSizerNavigationMode: wx.StaticBoxSizer = wx.StaticBoxSizer( wx.StaticBox( self._panelNavigation, wx.ID_ANY, @@ -443,7 +443,7 @@ def __init__(self, parent): wx.VERTICAL, ) - navigationModeChoices = [ + navigationModeChoices: list[str] = [ # Translators: names of different modes of navigation. "Enhanced" mode understands math structure _("Enhanced"), # Translators: "Simple" walks by character expect for things like fractions, roots, and scripts @@ -451,7 +451,7 @@ def __init__(self, parent): # Translators: "Character" moves around by character, automatically moving into fractions, etc _("Character"), ] - self._choiceNavigationMode = wx.Choice( + self._choiceNavigationMode: wx.Choice = wx.Choice( sbSizerNavigationMode.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, @@ -462,7 +462,7 @@ def __init__(self, parent): self._choiceNavigationMode.SetSelection(1) sbSizerNavigationMode.Add(self._choiceNavigationMode, 0, wx.ALL, 5) - self._checkBoxResetNavigationMode = wx.CheckBox( + self._checkBoxResetNavigationMode: wx.CheckBox = wx.CheckBox( sbSizerNavigationMode.GetStaticBox(), wx.ID_ANY, # Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved @@ -475,7 +475,7 @@ def __init__(self, parent): bSizerNavigation.Add(sbSizerNavigationMode, 1, wx.EXPAND, 5) - sbSizerNavigationSpeech = wx.StaticBoxSizer( + sbSizerNavigationSpeech: wx.StaticBoxSizer = wx.StaticBoxSizer( wx.StaticBox( self._panelNavigation, wx.ID_ANY, @@ -486,13 +486,13 @@ def __init__(self, parent): ) # Translators: either "Speak" the expression or give a description (overview) of the expression - navigationSpeechChoices = [ + navigationSpeechChoices: list[str] = [ # Translators: "Speak" the expression after moving to it _("Speak"), # Translators: "Describe" the expression after moving to it ("overview is a synonym") _("Describe/overview"), ] - self._choiceNavigationSpeech = wx.Choice( + self._choiceNavigationSpeech: wx.Choice = wx.Choice( sbSizerNavigationSpeech.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, @@ -503,7 +503,7 @@ def __init__(self, parent): self._choiceNavigationSpeech.SetSelection(1) sbSizerNavigationSpeech.Add(self._choiceNavigationSpeech, 0, wx.ALL, 5) - self._checkBoxResetNavigationSpeech = wx.CheckBox( + self._checkBoxResetNavigationSpeech: wx.CheckBox = wx.CheckBox( sbSizerNavigationSpeech.GetStaticBox(), wx.ID_ANY, # Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored @@ -517,9 +517,9 @@ def __init__(self, parent): bSizerNavigation.Add(sbSizerNavigationSpeech, 1, wx.EXPAND, 5) - bSizerNavigationZoom = wx.BoxSizer(wx.VERTICAL) + bSizerNavigationZoom: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) - self._checkBoxAutomaticZoom = wx.CheckBox( + self._checkBoxAutomaticZoom: wx.CheckBox = wx.CheckBox( self._panelNavigation, wx.ID_ANY, # Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., @@ -531,9 +531,9 @@ def __init__(self, parent): ) bSizerNavigationZoom.Add(self._checkBoxAutomaticZoom, 0, wx.ALL, 5) - bSizerSpeechAmountNavigation = wx.BoxSizer(wx.HORIZONTAL) + bSizerSpeechAmountNavigation: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextSpeechAmountNavigation = wx.StaticText( + self._staticTextSpeechAmountNavigation: wx.StaticText = wx.StaticText( self._panelNavigation, wx.ID_ANY, # Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands @@ -547,7 +547,7 @@ def __init__(self, parent): bSizerSpeechAmountNavigation.Add(self._staticTextSpeechAmountNavigation, 0, wx.ALL, 5) # Translators: options for navigation verbosity. - speechAmountNavigationChoices = [ + speechAmountNavigationChoices: list[str] = [ # Translators: options for navigation verbosity -- "terse" = use less words _("Terse"), # Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words @@ -555,7 +555,7 @@ def __init__(self, parent): # Translators: options for navigation verbosity -- "verbose" = use more words _("Verbose"), ] - self._choiceSpeechAmountNavigation = wx.Choice( + self._choiceSpeechAmountNavigation: wx.Choice = wx.Choice( self._panelNavigation, wx.ID_ANY, wx.DefaultPosition, @@ -570,9 +570,9 @@ def __init__(self, parent): bSizerNavigation.Add(bSizerNavigationZoom, 1, wx.EXPAND, 5) - bSizerCopyAs = wx.BoxSizer(wx.HORIZONTAL) + bSizerCopyAs: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextCopyMathAs = wx.StaticText( + self._staticTextCopyMathAs: wx.StaticText = wx.StaticText( self._panelNavigation, wx.ID_ANY, # Translators: label for pull down to specify how math will be copied to the clipboard @@ -586,7 +586,7 @@ def __init__(self, parent): bSizerCopyAs.Add(self._staticTextCopyMathAs, 0, wx.ALL, 5) # Translators: options for copy math as. - copyAsChoices = [ + copyAsChoices: list[str] = [ # Translators: options for Copy expression to clipboard as -- "MathML" _("MathML"), # Translators: options for Copy expression to clipboard as -- "LaTeX" @@ -596,7 +596,7 @@ def __init__(self, parent): # Translators: options for Copy expression to clipboard as -- speech text _("Speech"), ] - self._choiceCopyAs = wx.Choice( + self._choiceCopyAs: wx.Choice = wx.Choice( self._panelNavigation, wx.ID_ANY, wx.DefaultPosition, @@ -617,7 +617,7 @@ def __init__(self, parent): "a page", False, ) - self._panelBraille = wx.Panel( + self._panelBraille: wx.Panel = wx.Panel( self._simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, @@ -626,9 +626,9 @@ def __init__(self, parent): ) bSizerBraille = wx.BoxSizer(wx.VERTICAL) - bSizerBrailleMathCode = wx.BoxSizer(wx.HORIZONTAL) + bSizerBrailleMathCode: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextBrailleMathCode = wx.StaticText( + self._staticTextBrailleMathCode: wx.StaticText = wx.StaticText( self._panelBraille, wx.ID_ANY, # Translators: label for pull down to specify which braille code to use @@ -640,8 +640,8 @@ def __init__(self, parent): self._staticTextBrailleMathCode.Wrap(-1) bSizerBrailleMathCode.Add(self._staticTextBrailleMathCode, 0, wx.ALL, 5) - brailleMathCodeChoices = ["xxxxxxxxxxx"] - self._choiceBrailleMathCode = wx.Choice( + brailleMathCodeChoices: list[str] = ["xxxxxxxxxxx"] + self._choiceBrailleMathCode: wx.Choice = wx.Choice( self._panelBraille, wx.ID_ANY, wx.DefaultPosition, @@ -656,7 +656,7 @@ def __init__(self, parent): bSizerBrailleHighlights = wx.BoxSizer(wx.HORIZONTAL) - self._staticTextBrailleHighlights = wx.StaticText( + self._staticTextBrailleHighlights: wx.StaticText = wx.StaticText( self._panelBraille, wx.ID_ANY, # Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs @@ -669,7 +669,7 @@ def __init__(self, parent): bSizerBrailleHighlights.Add(self._staticTextBrailleHighlights, 0, wx.ALL, 5) - brailleHighlightsChoices = [ + brailleHighlightsChoices: list[str] = [ # Translators: options for using dots 7 and 8: # Translators: "off" -- don't highlight _("Off"), @@ -680,7 +680,7 @@ def __init__(self, parent): # Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 _("All"), ] - self._choiceBrailleHighlights = wx.Choice( + self._choiceBrailleHighlights: wx.Choice = wx.Choice( self._panelBraille, wx.ID_ANY, wx.DefaultPosition, @@ -716,7 +716,7 @@ def __init__(self, parent): 10, ) - self._staticlineAboveButtons = wx.StaticLine( + self._staticlineAboveButtons: wx.StaticLine = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, @@ -731,12 +731,12 @@ def __init__(self, parent): 5, ) - self._panelButtons = wx.Panel(self, wx.ID_ANY, wx.Point(-1, -1), wx.DefaultSize, 0) + self._panelButtons: wx.Panel = wx.Panel(self, wx.ID_ANY, wx.Point(-1, -1), wx.DefaultSize, 0) bSizerButtons = wx.BoxSizer(wx.HORIZONTAL) bSizerButtons.Add((0, 0), 1, wx.EXPAND, 5) - self._buttonOK = wx.Button( + self._buttonOK: wx.Button = wx.Button( self._panelButtons, wx.ID_ANY, # Translators: dialog "ok" button @@ -747,7 +747,7 @@ def __init__(self, parent): ) bSizerButtons.Add(self._buttonOK, 0, wx.ALL, 5) - self._buttonCancel = wx.Button( + self._buttonCancel: wx.Button = wx.Button( self._panelButtons, wx.ID_ANY, # Translators: dialog "cancel" button @@ -758,7 +758,7 @@ def __init__(self, parent): ) bSizerButtons.Add(self._buttonCancel, 0, wx.ALL, 5) - self._buttonApply = wx.Button( + self._buttonApply: wx.Button = wx.Button( self._panelButtons, wx.ID_ANY, # Translators: dialog "apply" button @@ -769,7 +769,7 @@ def __init__(self, parent): ) bSizerButtons.Add(self._buttonApply, 0, wx.ALL, 5) - self._buttonReset = wx.Button( + self._buttonReset: wx.Button = wx.Button( self._panelButtons, wx.ID_ANY, # Translators: button to reset all the preferences to their default values @@ -780,7 +780,7 @@ def __init__(self, parent): ) bSizerButtons.Add(self._buttonReset, 0, wx.ALL, 5) - self._buttonHelp = wx.Button( + self._buttonHelp: wx.Button = wx.Button( self._panelButtons, wx.ID_ANY, # Translators: button to bring up a help page @@ -828,35 +828,35 @@ def __del__(self): pass # Virtual event handlers, override them in your derived class - def mathCATPreferencesDialogOnCharHook(self, event): + def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent) -> None: event.Skip() - def mathCATPreferencesDialogOnKeyUp(self, event): + def mathCATPreferencesDialogOnKeyUp(self, event: wx.KeyEvent) -> None: event.Skip() - def onListBoxCategories(self, event): + def onListBoxCategories(self, event: wx.CommandEvent) -> None: event.Skip() - def onLanguage(self, event): + def onLanguage(self, event: wx.CommandEvent) -> None: event.Skip() - def onRelativeSpeedChanged(self, event): + def onRelativeSpeedChanged(self, event: wx.ScrollEvent) -> None: event.Skip() - def onPauseFactorChanged(self, event): + def onPauseFactorChanged(self, event: wx.ScrollEvent) -> None: event.Skip() - def onClickOK(self, event): + def onClickOK(self, event: wx.CommandEvent) -> None: event.Skip() - def onClickCancel(self, event): + def onClickCancel(self, event: wx.CommandEvent) -> None: event.Skip() - def onClickApply(self, event): + def onClickApply(self, event: wx.CommandEvent) -> None: event.Skip() - def onClickReset(self, event): + def onClickReset(self, event: wx.CommandEvent) -> None: event.Skip() - def onClickHelp(self, event): + def onClickHelp(self, event: wx.CommandEvent) -> None: event.Skip() diff --git a/addon/globalPlugins/MathCAT/__init__.py b/addon/globalPlugins/MathCAT/__init__.py index 3945b18..cfbae9f 100644 --- a/addon/globalPlugins/MathCAT/__init__.py +++ b/addon/globalPlugins/MathCAT/__init__.py @@ -31,17 +31,17 @@ def __init__(self, *args, **kwargs): # MathCAT.__init__(self) self.addMathCATMenu() - def addMathCATMenu(self): + def addMathCATMenu(self) -> None: if not globalVars.appArgs.secure: self.preferencesMenu = mainFrame.sysTrayIcon.preferencesMenu # Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog self.settings = self.preferencesMenu.Append(wx.ID_ANY, _("&MathCAT Settings...")) mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onSettings, self.settings) - def onSettings(self, evt): + def onSettings(self, evt: wx.CommandEvent) -> None: mainFrame.popupSettingsDialog(UserInterface) - def terminate(self): + def terminate(self) -> None: try: if not globalVars.appArgs.secure: self.preferencesMenu.Remove(self.settings) diff --git a/pyproject.toml b/pyproject.toml index 7dae399..f83a073 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ exclude = [ # Tell pyright where to load python code from extraPaths = [ "./addon", + "../nvda/source", ] # General config diff --git a/readme.md b/readme.md index 476c059..5a50d4e 100644 --- a/readme.md +++ b/readme.md @@ -212,3 +212,22 @@ Note: there is now an option to get Vietnam's braille standard as braille output * A preference setting to control the duration of pausing (works with changes to relative speech rate for math) * Support to recognize chemistry notation and speak it appropriately * Translations to Indonesian and Vietnamese + +## Development Environment + +How to set up your developer environment: + +1. Install Python 3.11 (32-bit). +1. Set up your virtual environment. + * `python -m venv .venv` +1. Install python dependencies to `.venv`. + * Activate the virtual environment + `.venv\Scripts\activate` + * Install lint dependencies + `pip install ruff==0.8.1 pre-commit==4.0.1 pyright==1.1.396` +1. Import NVDA code. + * NVDA source code needs to be discoverable to get type hints, namespace resolution, code completion, and other IDE hints. + * The relative path `../nvda/source` is included in the pyright configuration in `pyproject.toml`. + * The [NVDA repository](https://github.com/nvaccess/nvda) should be cloned into the same parent directory. + i.e. as a sibling to this repository. + Alternatively, update `../nvda/source` in `pyproject.toml` to another location where the NVDA repository has been cloned.