diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 3d25dadc8bc..402ad284906 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -32,6 +32,8 @@ # The synthesizer to use synth = string(default=auto) symbolLevel = integer(default=100) + # Speak all symbols when reviewing by word, uses editor specific implementation if false + symbolLevelWordAll = boolean(default=true) trustVoiceLanguage = boolean(default=true) includeCLDR = boolean(default=True) beepSpeechModePitch = integer(default=10000,min=50,max=11025) diff --git a/source/globalCommands.py b/source/globalCommands.py index e2521d025e0..f42782c6657 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -921,6 +921,27 @@ def script_cycleSpeechSymbolLevel(self,gesture): # %s will be replaced with the symbol level; e.g. none, some, most and all. ui.message(_("Symbol level %s") % name) + @script( + # Translators: Input help mode message for a command. + description=_("Toggle the announcement of all punctuation and symbols when reviewing by word"), + category=SCRCAT_SPEECH + ) + def script_toggleSpeechSymbolLevelWordAll(self, gesture): + symbolLevelWordAll = config.conf["speech"]["symbolLevelWordAll"] + if symbolLevelWordAll: + # Translators: Used to report the new state of a setting which is toggled via a command. + reportedState = pgettext("command toggle", "off") + else: + # Translators: Used to report the new state of a setting which is toggled via a command. + reportedState = pgettext("command toggle", "on") + config.conf["speech"]["symbolLevelWordAll"] = not symbolLevelWordAll + ui.message( + # Translators: Reported when toggling a speech setting + _("Speak all punctuation and symbols when reviewing by word: {state}").format( + state=reportedState + ) + ) + @script( # Translators: Input help mode message for move mouse to navigator object command. description=_("Moves the mouse pointer to the current navigator object"), diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 02c5dcf9f9f..10f563c1586 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1495,6 +1495,16 @@ def makeSettings(self, settingsSizer): characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS.index(curLevel) ) + self.symbolLevelWordAll = settingsSizerHelper.addItem( + wx.CheckBox( + self, + # Translators: The label for a setting in the Speech category + label=_("Speak all punctuation and symbols when reviewing by &word"), + ) + ) + self.bindHelpEvent("SpeechSettingsSymbolLevelWord", self.symbolLevelWordAll) + self.symbolLevelWordAll.SetValue(config.conf["speech"]["symbolLevelWordAll"]) + # Translators: This is the label for a checkbox in the # voice settings panel (if checked, text will be read using the voice for the language of the text). trustVoiceLanguageText = _("Trust voice's language when processing characters and symbols") @@ -1585,6 +1595,7 @@ def onSave(self): config.conf["speech"]["symbolLevel"] = characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[ self.symbolLevelList.GetSelection() ].value + config.conf["speech"]["symbolLevelWordAll"] = self.symbolLevelWordAll.IsChecked() config.conf["speech"]["trustVoiceLanguage"] = self.trustVoiceLanguageCheckbox.IsChecked() currentIncludeCLDR = config.conf["speech"]["includeCLDR"] config.conf["speech"]["includeCLDR"] = newIncludeCldr = self.includeCLDRCheckbox.IsChecked() diff --git a/source/speech/speech.py b/source/speech/speech.py index f58590acde0..6c0cf64824e 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -1126,8 +1126,14 @@ def speakTextInfo( ) speechGen = GeneratorWithReturn(speechGen) + symbolLevel: Optional[characterProcessing.SymbolLevel] = None + if unit == textInfos.UNIT_CHARACTER: + symbolLevel = characterProcessing.SymbolLevel.ALL + elif unit == textInfos.UNIT_WORD: + if config.conf["speech"]["symbolLevelWordAll"]: + symbolLevel = characterProcessing.SymbolLevel.ALL for seq in speechGen: - speak(seq, priority=priority) + speak(seq, symbolLevel=symbolLevel, priority=priority) return speechGen.returnValue @@ -1323,7 +1329,7 @@ def isControlEndFieldCommand(x): if onlyInitialFields or ( isWordOrCharUnit and len(textWithFields) > 0 - and len(textWithFields[0].strip() if not textWithFields[0].isspace() else textWithFields[0]) == 1 + and len(textWithFields[0]) == 1 and all(isControlEndFieldCommand(x) for x in itertools.islice(textWithFields, 1, None)) ): if not onlyCache: diff --git a/tests/system/robot/notepadTests.py b/tests/system/robot/notepadTests.py index 74cdf7a5fbd..53fe3dd9dd6 100644 --- a/tests/system/robot/notepadTests.py +++ b/tests/system/robot/notepadTests.py @@ -37,8 +37,6 @@ def _pressKeyAndCollectSpeech(key: str, numberOfTimes: int) -> typing.List[str]: def test_moveByWord_symbolLevelWord(): - """Disabled due to revert of PR #11856 is: "Speak all symbols when moving by words (#11779) - """ spy = _NvdaLib.getSpyLib() spy.set_configValue(["speech", "symbolLevelWordAll"], True) @@ -49,7 +47,7 @@ def test_moveByWord_symbolLevelWord(): '"Hello,': 'quote Hello comma,', 'Jim".': 'Jim quote dot.', '➔': 'right-pointing arrow', # Speech for symbols shouldn't change - '👕': 't-shirt', # Speech for symbols shouldn't change + '👕': 't dash shirt', # Speech for symbols shouldn't change } textStr = ' '.join(_wordsToExpected.keys()) @@ -67,7 +65,7 @@ def test_moveByWord(): '(quietly)': '(quietly)', '"Hello,': 'Hello,', 'Jim".': 'Jim .', - '➔': 'right pointing arrow', + '➔': 'right-pointing arrow', '👕': 't shirt', } @@ -96,6 +94,25 @@ def test_moveByLine(): builtIn.should_be_equal(actual, list(_wordsToExpected.values())) +def test_moveByLine_symbolLevelWord(): + spy = _NvdaLib.getSpyLib() + spy.set_configValue(["speech", "symbolLevelWordAll"], True) + + _wordsToExpected = { + 'Say': 'Say', + '(quietly)': '(quietly)', + '"Hello,': 'Hello,', + 'Jim".': 'Jim .', + '➔': 'right-pointing arrow', + '👕': 't-shirt', + } + + textStr = '\n'.join(_wordsToExpected.keys()) + _notepad.prepareNotepad(f"Test:\n{textStr}") # initial new line which isn't spoken + actual = _pressKeyAndCollectSpeech(navToNextLineKey, numberOfTimes=len(_wordsToExpected)) + builtIn.should_be_equal(actual, list(_wordsToExpected.values())) + + def test_moveByChar(): spy = _NvdaLib.getSpyLib() spy.set_configValue(["speech", "symbolLevelWordAll"], False) @@ -108,8 +125,29 @@ def test_moveByChar(): 'right paren', 'e', 'comma', - 'right pointing arrow', - 't shirt', + 'right dash pointing arrow', + 't dash shirt', + ] + + _notepad.prepareNotepad(f" {_text}") + actual = _pressKeyAndCollectSpeech(navToNextCharKey, numberOfTimes=len(_expected)) + builtIn.should_be_equal(actual, _expected) + + +def test_moveByChar_symbolLevelWord(): + spy = _NvdaLib.getSpyLib() + spy.set_configValue(["speech", "symbolLevelWordAll"], True) + + _text = 'S ()e,➔👕' # to speed up test, reduce superfluous characters + _expected = [ + 'S', + 'space', + 'left paren', + 'right paren', + 'e', + 'comma', + 'right dash pointing arrow', + 't dash shirt', ] _notepad.prepareNotepad(f" {_text}") diff --git a/tests/system/robot/notepadTests.robot b/tests/system/robot/notepadTests.robot index 8fb0f23e9ff..fefb7934abb 100644 --- a/tests/system/robot/notepadTests.robot +++ b/tests/system/robot/notepadTests.robot @@ -28,8 +28,6 @@ default setup *** Test Cases *** moveByWord with symbolLevelWord - # Disabled due to revert of PR #11856 is: "Speak all symbols when moving by words (#11779) - [Tags] excluded_from_build [Documentation] Ensure all symbols are read when navigating by word. test_moveByWord_symbolLevelWord moveByWord @@ -38,6 +36,12 @@ moveByWord moveByLine [Documentation] Ensure symbols announced as expected when navigating by line (numpad 9). test_moveByLine +moveByLine with symbolLevelWord + [Documentation] Ensure all symbols are read when navigating by line. + test_moveByLine_symbolLevelWord moveByCharacter [Documentation] Ensure symbols announced as expected when navigating by character (numpad 3). - test_moveByChar \ No newline at end of file + test_moveByChar +moveByCharacter with symbolLevelWord + [Documentation] Ensure all symbols are read when navigating by character. + test_moveByChar_symbolLevelWord diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 7e2e7e73b68..bc146bfbc8d 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -56,6 +56,8 @@ To ensure security, confirm that screen curtain works properly with new or exper == Changes == +- NVDA now speaks by default every symbol when navigating by word. +This can be toggled in the Preferences dialog or with a new (unassigned) command. (#11779) - Espeak-ng has been updated to 1.51-dev commit ``ab11439b18238b7a08b965d1d5a6ef31cbb05cbb``. (#12449, #12202, #12280, #12568) - If article is enabled in the user preferences for document formatting, NVDA announces "article" after the content. (#11103) - Updated liblouis braille translator to [3.18.0 https://github.com/liblouis/liblouis/releases/tag/v3.18.0]. (#12526) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index da44f538549..538ca2bba22 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1183,6 +1183,12 @@ This allows you to choose the amount of punctuation and other symbols that shoul For example, when set to all, all symbols will be spoken as words. This option applies to all synthesizers, not just the currently active synthesizer. +==== Speak all punctuation and symbols when reviewing by word ====[SpeechSettingsSymbolLevelWord] + +When reviewing text by word, it is often desirable to get more details than when reading a whole sentence. +When checked (by default), NVDA speaks the name of all punctuation and other symbols when reviewing by word, as if the above level was set to "all". +Note: In any case, when reviewing text by character, NVDA always reports the name of punctuation and other symbols. + ==== Trust voice's language when processing characters and symbols ====[SpeechSettingsTrust] On by default, this option tells NVDA if the current voice's language can be trusted when processing symbols and characters. If you find that NVDA is reading punctuation in the wrong language for a particular synthesizer or voice, you may wish to turn this off to force NVDA to use its global language setting instead.