From 86592b04b0fd5b03c8ea77df7256e4f631107833 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 23 Nov 2020 12:00:22 +0100 Subject: [PATCH 01/23] Speak all symbols when moving by words (#11779) --- source/speech/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/speech/__init__.py b/source/speech/__init__.py index b8348cad53c..614dd850018 100755 --- a/source/speech/__init__.py +++ b/source/speech/__init__.py @@ -1084,8 +1084,13 @@ def speakTextInfo( return _speakWithoutPauses.speakWithoutPauses(flatSpeechGen) speechGen = GeneratorWithReturn(speechGen) + # Force symbol level to all when moving by characters or words (#11779) + if unit in (textInfos.UNIT_CHARACTER, textInfos.UNIT_WORD): + symbolLevel = characterProcessing.SYMLVL_ALL + else: + symbolLevel = None for seq in speechGen: - speak(seq, priority=priority) + speak(seq, symbolLevel=symbolLevel, priority=priority) return speechGen.returnValue @@ -1281,7 +1286,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: From 2614f461e0ee7508df028a4f84af27c3e74b5a18 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Sun, 13 Dec 2020 22:34:25 +0100 Subject: [PATCH 02/23] Review action https://github.com/nvaccess/nvda/pull/11856#issuecomment-739700725 * New configuration setting: Speech > Speak all punctuations and symbols when reviewing by word `config.conf["speech"]["symbolLevelWord"]` * New constant: characterProcessing.SYMLVL_UNCHANGED = -1 --- source/characterProcessing.py | 11 ++++++----- source/config/configSpec.py | 4 +++- source/gui/settingsDialogs.py | 28 +++++++++++++++++++++++++--- source/speech/__init__.py | 8 ++++++-- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/source/characterProcessing.py b/source/characterProcessing.py index b725c79bd8c..9dc904d58c1 100644 --- a/source/characterProcessing.py +++ b/source/characterProcessing.py @@ -1,8 +1,8 @@ -#characterProcessing.py -#A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2010-2018 NV Access Limited, World Light Information Limited, Hong Kong Blind Union, Babbage B.V. -#This file is covered by the GNU General Public License. -#See the file COPYING for more details. +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2010-2020 NV Access Limited, World Light Information Limited, +# Hong Kong Blind Union, Babbage B.V., Julien Cochuyt +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. import time import os @@ -130,6 +130,7 @@ def getCharacterDescription(locale,character): SYMLVL_MOST = 200 SYMLVL_ALL = 300 SYMLVL_CHAR = 1000 +SYMLVL_UNCHANGED = -1 SPEECH_SYMBOL_LEVEL_LABELS = { # Translators: The level at which the given symbol will be spoken. SYMLVL_NONE: pgettext("symbolLevel", "none"), diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 9e23fbe4fe7..8a5ce1f7145 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2006-2020 NV Access Limited, Babbage B.V., Davy Kager, Bill Dengler +# Copyright (C) 2006-2020 NV Access Limited, Babbage B.V., Davy Kager, Bill Dengler, Julien Cochuyt # This file is covered by the GNU General Public License. # See the file COPYING for more details. @@ -31,6 +31,8 @@ # The synthesizer to use synth = string(default=auto) symbolLevel = integer(default=100) + # Symbol Level when reviewing by word (unchanged if -1) + symbolLevelWord = integer(default=300) trustVoiceLanguage = boolean(default=true) includeCLDR = boolean(default=True) beepSpeechModePitch = integer(default=10000,min=50,max=11025) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index d4fc5899d84..6bc5ebdbda0 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -2,7 +2,8 @@ # A part of NonVisual Desktop Access (NVDA) # Copyright (C) 2006-2020 NV Access Limited, Peter Vágner, Aleksey Sadovoy, # Rui Batista, Joseph Lee, Heiko Folkerts, Zahari Yurukov, Leonard de Ruijter, -# Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Bill Dengler, Thomas Stivers +# Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Bill Dengler, Thomas Stivers, +# Julien Cochuyt # This file is covered by the GNU General Public License. # See the file COPYING for more details. import logging @@ -1461,6 +1462,21 @@ def makeSettings(self, settingsSizer): characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS.index(curLevel) ) + self.symbolLevelWord = settingsSizerHelper.addItem( + wx.CheckBox( + self, + # Translators: The label for a setting in the Speech category + label=_("Speak all punctuations and symbols when reviewing by &word"), + style=wx.CHK_3STATE + ) + ) + if config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_ALL: + self.symbolLevelWord.Set3StateValue(wx.CHK_CHECKED) + elif config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_UNCHANGED: + self.symbolLevelWord.Set3StateValue(wx.CHK_UNCHECKED) + else: + self.symbolLevelWord.Set3StateValue(wx.CHK_UNDETERMINED) + # 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") @@ -1548,8 +1564,14 @@ def onSave(self): config.conf["speech"]["autoLanguageSwitching"] = self.autoLanguageSwitchingCheckbox.IsChecked() config.conf["speech"]["autoDialectSwitching"] = self.autoDialectSwitchingCheckbox.IsChecked() - config.conf["speech"]["symbolLevel"]=characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[self.symbolLevelList.GetSelection()] - config.conf["speech"]["trustVoiceLanguage"]=self.trustVoiceLanguageCheckbox.IsChecked() + config.conf["speech"]["symbolLevel"] = characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[ + self.symbolLevelList.GetSelection() + ] + if self.symbolLevelWord.Get3StateValue() == wx.CHK_CHECKED: + config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL_ALL + elif self.symbolLevelWord.Get3StateValue() == wx.CHK_UNCHECKED: + config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL_UNCHANGED + config.conf["speech"]["trustVoiceLanguage"] = self.trustVoiceLanguageCheckbox.IsChecked() currentIncludeCLDR = config.conf["speech"]["includeCLDR"] config.conf["speech"]["includeCLDR"] = newIncludeCldr = self.includeCLDRCheckbox.IsChecked() if currentIncludeCLDR is not newIncludeCldr: diff --git a/source/speech/__init__.py b/source/speech/__init__.py index 614dd850018..e3fc254be47 100755 --- a/source/speech/__init__.py +++ b/source/speech/__init__.py @@ -1084,9 +1084,13 @@ def speakTextInfo( return _speakWithoutPauses.speakWithoutPauses(flatSpeechGen) speechGen = GeneratorWithReturn(speechGen) - # Force symbol level to all when moving by characters or words (#11779) - if unit in (textInfos.UNIT_CHARACTER, textInfos.UNIT_WORD): + symbolLevel = None + if unit == textInfos.UNIT_CHARACTER: symbolLevel = characterProcessing.SYMLVL_ALL + elif unit == textInfos.UNIT_WORD: + symbolLevelWord = config.conf["speech"]["symbolLevelWord"] + if symbolLevelWord != characterProcessing.SYMLVL_UNCHANGED: + symbolLevel = symbolLevelWord else: symbolLevel = None for seq in speechGen: From 56d97fe8c03c073c6bd3d43ea2081fad8ac9f8e1 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 14 Dec 2020 08:43:19 +0100 Subject: [PATCH 03/23] Add an unassigned global command to toggle symbolLevelWord Review action https://github.com/nvaccess/nvda/pull/11856#issuecomment-739738547 --- source/globalCommands.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/globalCommands.py b/source/globalCommands.py index e9ecfdb5d77..c836faf8aef 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -781,6 +781,26 @@ def script_cycleSpeechSymbolLevel(self,gesture): script_cycleSpeechSymbolLevel.__doc__=_("Cycles through speech symbol levels which determine what symbols are spoken") script_cycleSpeechSymbolLevel.category=SCRCAT_SPEECH + @script( + # Translators: Input help mode message for a command. + description=_("Toggle the announce of all punctuations and symbols when reviewing by word"), + category=SCRCAT_SPEECH + ) + def script_toggleSpeechSymbolLevelWordAll(self, gesture): + curLevel = config.conf["speech"]["symbolLevelWord"] + if curLevel == characterProcessing.SYMLVL_ALL: + level = characterProcessing.SYMLVL_UNCHANGED + reportedState = _("off") + else: + level = characterProcessing.SYMLVL_ALL + reportedState = _("on") + config.conf["speech"]["symbolLevelWord"] = level + ui.message( + _("Speak all punctuations and symbols when reviewing by word: {state}").format( + state=reportedState + ) + ) + def script_moveMouseToNavigatorObject(self,gesture): try: p=api.getReviewPosition().pointAtStart From db91a19230bb7947f183cfdad2580729ecf50f7f Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 14 Dec 2020 09:24:24 +0100 Subject: [PATCH 04/23] Add missing translators comment --- source/globalCommands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/globalCommands.py b/source/globalCommands.py index c836faf8aef..f775a642d68 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -796,6 +796,7 @@ def script_toggleSpeechSymbolLevelWordAll(self, gesture): reportedState = _("on") config.conf["speech"]["symbolLevelWord"] = level ui.message( + # Translators: Reported when toggling a speech setting _("Speak all punctuations and symbols when reviewing by word: {state}").format( state=reportedState ) From 7f18e8df867621c5ce7dd522c6d173198be8753c Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Wed, 16 Dec 2020 08:28:31 +0100 Subject: [PATCH 05/23] User Guide: SpeechSettingsSymbolLevelWord Review action: https://github.com/nvaccess/nvda/pull/11856#discussion_r542531828 Review action: https://github.com/nvaccess/nvda/pull/11856#discussion_r542804259 --- source/gui/settingsDialogs.py | 1 + user_docs/en/userGuide.t2t | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 6bc5ebdbda0..278636f994a 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1470,6 +1470,7 @@ def makeSettings(self, settingsSizer): style=wx.CHK_3STATE ) ) + self.bindHelpEvent("SpeechSettingsSymbolLevelWord", self.symbolLevelWord) if config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_ALL: self.symbolLevelWord.Set3StateValue(wx.CHK_CHECKED) elif config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_UNCHANGED: diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 93fd0f6b898..443b8d66087 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1177,6 +1177,13 @@ 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 punctuations 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 punctuations 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 punctuations 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. From f4b99b453eec60834ca9e76ff47d785dbb474078 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 24 May 2021 10:42:53 +0200 Subject: [PATCH 06/23] Review action https://github.com/nvaccess/nvda/pull/11856#discussion_r637696682 Co-authored-by: Sean Budd --- source/speech/speech.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/speech/speech.py b/source/speech/speech.py index 9c642cf5fef..c97cc24e183 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -1132,8 +1132,6 @@ def speakTextInfo( symbolLevelWord = config.conf["speech"]["symbolLevelWord"] if symbolLevelWord != characterProcessing.SYMLVL_UNCHANGED: symbolLevel = symbolLevelWord - else: - symbolLevel = None for seq in speechGen: speak(seq, symbolLevel=symbolLevel, priority=priority) return speechGen.returnValue From 30cbe692da9b477f95719d2105a024c16943ba51 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 24 May 2021 10:44:55 +0200 Subject: [PATCH 07/23] Review action https://github.com/nvaccess/nvda/pull/11856#discussion_r637697218 Co-authored-by: Sean Budd --- source/gui/settingsDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 6cac17a8d61..db72fc5b998 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1497,7 +1497,7 @@ def makeSettings(self, settingsSizer): wx.CheckBox( self, # Translators: The label for a setting in the Speech category - label=_("Speak all punctuations and symbols when reviewing by &word"), + label=_("Speak all punctuation and symbols when reviewing by &word"), style=wx.CHK_3STATE ) ) From 9bc1df2ea4055832d9e28c943a4a06a9a2c04fdd Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 24 May 2021 10:45:28 +0200 Subject: [PATCH 08/23] Review action https://github.com/nvaccess/nvda/pull/11856#discussion_r637700053 Co-authored-by: Sean Budd --- source/globalCommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index 11d60d65d64..ef905160fcc 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -897,7 +897,7 @@ def script_cycleSpeechSymbolLevel(self,gesture): @script( # Translators: Input help mode message for a command. - description=_("Toggle the announce of all punctuations and symbols when reviewing by word"), + description=_("Toggle the announcement of all punctuation and symbols when reviewing by word"), category=SCRCAT_SPEECH ) def script_toggleSpeechSymbolLevelWordAll(self, gesture): From 06cf4dfed13afe7d25c4a48152947980459b589b Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 24 May 2021 10:46:00 +0200 Subject: [PATCH 09/23] Review action https://github.com/nvaccess/nvda/pull/11856#discussion_r637700149 Co-authored-by: Sean Budd --- source/globalCommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index ef905160fcc..b5531e171a0 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -911,7 +911,7 @@ def script_toggleSpeechSymbolLevelWordAll(self, gesture): config.conf["speech"]["symbolLevelWord"] = level ui.message( # Translators: Reported when toggling a speech setting - _("Speak all punctuations and symbols when reviewing by word: {state}").format( + _("Speak all punctuation and symbols when reviewing by word: {state}").format( state=reportedState ) ) From 576ce99eb0dc41d119d838ab08e1096d5f6a91f8 Mon Sep 17 00:00:00 2001 From: Julien Cochuyt Date: Mon, 24 May 2021 10:50:17 +0200 Subject: [PATCH 10/23] Review action https://github.com/nvaccess/nvda/pull/11856#discussion_r637701681 Co-authored-by: Sean Budd --- user_docs/en/userGuide.t2t | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 829f59e1f38..ab5002ac1e7 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1180,12 +1180,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 punctuations and symbols when reviewing by word ====[SpeechSettingsSymbolLevelWord] +==== 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 punctuations and other symbols when reviewing by word, +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 punctuations and other symbols. +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. From d89a533dd90907d2c240836c0488385c827ae56f Mon Sep 17 00:00:00 2001 From: buddsean Date: Mon, 7 Jun 2021 17:41:07 +1000 Subject: [PATCH 11/23] add notepad system tests for symbol level word safer attempt at closing notepad genericise notepad regex --- tests/system/libraries/NotepadLib.py | 136 ++++++++++++++++++ .../nvdaSettingsFiles/symbolLevelWordOff.ini | 18 +++ tests/system/robot/notepadTests.py | 40 ++++++ tests/system/robot/notepadTests.robot | 35 +++++ 4 files changed, 229 insertions(+) create mode 100644 tests/system/libraries/NotepadLib.py create mode 100644 tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini create mode 100644 tests/system/robot/notepadTests.py create mode 100644 tests/system/robot/notepadTests.robot diff --git a/tests/system/libraries/NotepadLib.py b/tests/system/libraries/NotepadLib.py new file mode 100644 index 00000000000..d59722a3643 --- /dev/null +++ b/tests/system/libraries/NotepadLib.py @@ -0,0 +1,136 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2021 NV Access Limited +# This file may be used under the terms of the GNU General Public License, version 2 or later. +# For more details see: https://www.gnu.org/licenses/gpl-2.0.plaintext + +""" This module provides the NotepadLib Robot Framework Library which allows system tests to start +Windows Notepad with a text sample and assert NVDA interacts with it in the expected way. +""" + +# imported methods start with underscore (_) so they don't get imported into robot files as keywords +from os.path import join as _pJoin +import tempfile as _tempfile +from typing import Optional as _Optional +from SystemTestSpy import ( + _blockUntilConditionMet, + _getLib, +) +from SystemTestSpy.windows import ( + GetForegroundWindowTitle, + GetVisibleWindowTitles, + SetForegroundWindow, +) +import re +from robot.libraries.BuiltIn import BuiltIn + +# Imported for type information +from robot.libraries.OperatingSystem import OperatingSystem as _OpSysLib +from robot.libraries.Process import Process as _ProcessLib +from AssertsLib import AssertsLib as _AssertsLib +import NvdaLib as _NvdaLib + +builtIn: BuiltIn = BuiltIn() +opSys: _OpSysLib = _getLib('OperatingSystem') +process: _ProcessLib = _getLib('Process') +assertsLib: _AssertsLib = _getLib('AssertsLib') + + +# In Robot libraries, class name must match the name of the module. Use caps for both. +class NotepadLib: + _testFileStagingPath = _tempfile.mkdtemp() + _testCaseTitle = "test" + + def __init__(self): + self.notepadHandle: _Optional[int] = None + + @staticmethod + def _getTestCasePath(filename): + return _pJoin(NotepadLib._testFileStagingPath, filename) + + def exit_notepad(self): + spy = _NvdaLib.getSpyLib() + spy.emulateKeyPress('alt+f4') + process.wait_for_process(self.notepadHandle, timeout="1 minute", on_timeout="continue") + + def start_notepad(self, filePath): + builtIn.log(f"starting notepad: {filePath}") + self.notepadHandle = process.start_process( + "start notepad" + f' "{filePath}"', + shell=True, + alias='NotepadAlias', + ) + process.process_should_be_running(self.notepadHandle) + return self.notepadHandle + + @staticmethod + def getUniqueTestCaseTitle(testCase: str) -> str: + return f"{NotepadLib._testCaseTitle} ({abs(hash(testCase))}).txt" + + @staticmethod + def getUniqueTestCaseTitleRegex(testCase: str) -> re.Pattern: + return re.compile(f"^{NotepadLib._testCaseTitle} \\({abs(hash(testCase))}\\)") + + @staticmethod + def _writeTestFile(testCase) -> str: + """ + Creates a file for a plaintext test case. + @param testCase: The plaintext sample that is to be tested. + @return: path to the plaintext file. + """ + filePath = NotepadLib._getTestCasePath(NotepadLib.getUniqueTestCaseTitle(testCase)) + with open(file=filePath, mode='w', encoding='UTF-8') as f: + f.write(testCase) + return filePath + + def _focusNotepad(self, startsWithTestCaseTitle: re.Pattern): + """ Ensure Notepad started and is focused. + """ + success, _success = _blockUntilConditionMet( + getValue=lambda: SetForegroundWindow(startsWithTestCaseTitle, builtIn.log), + giveUpAfterSeconds=3, + intervalBetweenSeconds=0.5 + ) + if success: + return + windowInformation = "" + try: + windowInformation = f"Foreground Window: {GetForegroundWindowTitle()}.\n" + windowInformation += f"Open Windows: {GetVisibleWindowTitles()}" + except OSError as e: + builtIn.log(f"Couldn't retrieve active window information.\nException: {e}") + raise AssertionError( + "Unable to focus Notepad.\n" + f"{windowInformation}" + ) + + def prepareNotepad(self, testCase: str) -> None: + """ + Starts Notepad opening a file containing the plaintext sample. + Different versions of notepad/windows have variations in how the title is presented. + This may mean that there is a file extension in the title. + E.G. "test.txt - Notepad" or "test – Notepad". + @param testCase - The plaintext sample to test. + """ + spy = _NvdaLib.getSpyLib() + path = self._writeTestFile(testCase) + + spy.wait_for_speech_to_finish() + self.start_notepad(path) + self._focusNotepad(NotepadLib.getUniqueTestCaseTitleRegex(testCase)) + # Move to the start of file + spy.emulateKeyPress('home') + spy.wait_for_speech_to_finish() + + @staticmethod + def getSpeechAfterKey(key) -> str: + """Ensure speech has stopped, press key, and get speech until it stops. + @return: The speech after key press. + """ + spy = _NvdaLib.getSpyLib() + spy.wait_for_speech_to_finish() + nextSpeechIndex = spy.get_next_speech_index() + spy.emulateKeyPress(key) + spy.wait_for_speech_to_finish(speechStartedIndex=nextSpeechIndex) + speech = spy.get_speech_at_index_until_now(nextSpeechIndex) + return speech diff --git a/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini b/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini new file mode 100644 index 00000000000..cea495ae923 --- /dev/null +++ b/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini @@ -0,0 +1,18 @@ +schemaVersion = 2 +[general] + showWelcomeDialogAtStartup = False +[update] + askedAllowUsageStats = True + autoCheck = False + startupNotification = False + allowUsageStats = False +[speech] + synth = speechSpySynthDriver + symbolLevelWord = -1 +[development] + enableScratchpadDir = True +[virtualBuffers] + autoSayAllOnPageLoad = False + passThroughAudioIndication = False +[annotations] + reportDetails = True diff --git a/tests/system/robot/notepadTests.py b/tests/system/robot/notepadTests.py new file mode 100644 index 00000000000..8c7b205ab23 --- /dev/null +++ b/tests/system/robot/notepadTests.py @@ -0,0 +1,40 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2021 NV Access Limited +# This file may be used under the terms of the GNU General Public License, version 2 or later. +# For more details see: https://www.gnu.org/licenses/gpl-2.0.html + +"""Logic for reading text using NVDA in the notepad text editor. +""" +# imported methods start with underscore (_) so they don't get imported into robot files as keywords +from SystemTestSpy import ( + _getLib, +) + +# Imported for type information +from NotepadLib import NotepadLib as _NotepadLib +from AssertsLib import AssertsLib as _AssertsLib + +_notepad: _NotepadLib = _getLib("NotepadLib") +_asserts: _AssertsLib = _getLib("AssertsLib") + + +def test_symbolLevelWord(isSymbolLevelWordAllExpected=True): + wordsWithSymbols = ['He', 'said', '(quietly),', '"Hello,', 'Jim".'] + symbolMap = { + '(': 'left paren', + ')': 'right paren', + ',': 'comma', + '"': 'quote', + '.': 'dot', + } + textStr = ' '.join(wordsWithSymbols) + _asserts.strings_match(textStr, 'He said (quietly), "Hello, Jim".') + + _notepad.prepareNotepad(f"Test: {textStr}") + for expectedWord in wordsWithSymbols: + wordSpoken = _notepad.getSpeechAfterKey("numpad6") + for symbol in symbolMap.keys(): + if isSymbolLevelWordAllExpected: + expectedWord = expectedWord.replace(symbol, f" {symbolMap[symbol]}{symbol}") + expectedWord = expectedWord.replace('"', ' ').strip() # always strip quote symbols + _asserts.strings_match(wordSpoken, expectedWord) diff --git a/tests/system/robot/notepadTests.robot b/tests/system/robot/notepadTests.robot new file mode 100644 index 00000000000..36ea7ec456c --- /dev/null +++ b/tests/system/robot/notepadTests.robot @@ -0,0 +1,35 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2021 NV Access Limited +# This file may be used under the terms of the GNU General Public License, version 2 or later. +# For more details see: https://www.gnu.org/licenses/gpl-2.0.html +*** Settings *** +Documentation Plaintext test cases in notepad +Force Tags NVDA smoke test notepad + +# for start & quit in Test Setup and Test Test Teardown +Library NvdaLib.py +# for test cases +Library notepadTests.py +Library ScreenCapLibrary + +Test Setup default setup +Test Teardown default teardown + +*** Keywords *** +default teardown + ${screenshotName}= create_preserved_test_output_filename failedTest.png + Run Keyword If Test Failed Take Screenshot ${screenShotName} + exit notepad + quit NVDA + +default setup + start NVDA standard-dontShowWelcomeDialog.ini + +*** Test Cases *** +symbolLevelWord + [Documentation] Ensure all symbols are read when navigating by word. + test symbolLevelWord +symbolLevelWord off + [Documentation] Use default behaviour of reading symbols when navigating by word. + [Setup] start NVDA symbolLevelWordOff.ini + test symbolLevelWord False From 304d940fb1a417041914894b26bf5a6c5d19485e Mon Sep 17 00:00:00 2001 From: buddsean Date: Mon, 7 Jun 2021 17:42:10 +1000 Subject: [PATCH 12/23] unbreak sentence in userguide for translators --- user_docs/en/userGuide.t2t | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index a842d38c6e1..9c21dcb9859 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -1183,8 +1183,7 @@ This option applies to all synthesizers, not just the currently active synthesiz ==== 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". +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] From 2f61ca6faea2303559b948e7ed2cc04c0ba0754f Mon Sep 17 00:00:00 2001 From: buddsean Date: Mon, 7 Jun 2021 17:42:54 +1000 Subject: [PATCH 13/23] convert symlvl to intenum --- source/characterProcessing.py | 58 ++++++++++++++++++++++------------- source/globalCommands.py | 2 +- source/gui/settingsDialogs.py | 4 +-- source/speech/speech.py | 4 +-- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/source/characterProcessing.py b/source/characterProcessing.py index 9dc904d58c1..36a1ecc1a6d 100644 --- a/source/characterProcessing.py +++ b/source/characterProcessing.py @@ -1,10 +1,11 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2010-2020 NV Access Limited, World Light Information Limited, +# Copyright (C) 2010-2021 NV Access Limited, World Light Information Limited, # Hong Kong Blind Union, Babbage B.V., Julien Cochuyt # This file is covered by the GNU General Public License. # See the file COPYING for more details. -import time +from versionInfo import version_year +from enum import IntEnum import os import codecs import collections @@ -124,27 +125,40 @@ def getCharacterDescription(locale,character): desc=getCharacterDescription('en',character) return desc + # Speech symbol levels -SYMLVL_NONE = 0 -SYMLVL_SOME = 100 -SYMLVL_MOST = 200 -SYMLVL_ALL = 300 -SYMLVL_CHAR = 1000 -SYMLVL_UNCHANGED = -1 +class SYMLVL(IntEnum): + NONE = 0 + SOME = 100 + MOST = 200 + ALL = 300 + CHAR = 1000 + UNCHANGED = -1 + + +# The following SYMLVL_ constants are deprecated in #11856 but remain to maintain backwards compatibility. +# Remove these in 2022.1 and replace instances using them with the SYMLVL IntEnum. +if version_year < 2022: + SYMLVL_NONE = SYMLVL.NONE + SYMLVL_SOME = SYMLVL.SOME + SYMLVL_MOST = SYMLVL.MOST + SYMLVL_ALL = SYMLVL.ALL + SYMLVL_CHAR = SYMLVL.CHAR + SPEECH_SYMBOL_LEVEL_LABELS = { # Translators: The level at which the given symbol will be spoken. - SYMLVL_NONE: pgettext("symbolLevel", "none"), + SYMLVL.NONE: pgettext("symbolLevel", "none"), # Translators: The level at which the given symbol will be spoken. - SYMLVL_SOME: pgettext("symbolLevel", "some"), + SYMLVL.SOME: pgettext("symbolLevel", "some"), # Translators: The level at which the given symbol will be spoken. - SYMLVL_MOST: pgettext("symbolLevel", "most"), + SYMLVL.MOST: pgettext("symbolLevel", "most"), # Translators: The level at which the given symbol will be spoken. - SYMLVL_ALL: pgettext("symbolLevel", "all"), + SYMLVL.ALL: pgettext("symbolLevel", "all"), # Translators: The level at which the given symbol will be spoken. - SYMLVL_CHAR: pgettext("symbolLevel", "character"), + SYMLVL.CHAR: pgettext("symbolLevel", "character"), } -CONFIGURABLE_SPEECH_SYMBOL_LEVELS = (SYMLVL_NONE, SYMLVL_SOME, SYMLVL_MOST, SYMLVL_ALL) -SPEECH_SYMBOL_LEVELS = CONFIGURABLE_SPEECH_SYMBOL_LEVELS + (SYMLVL_CHAR,) +CONFIGURABLE_SPEECH_SYMBOL_LEVELS = (SYMLVL.NONE, SYMLVL.SOME, SYMLVL.MOST, SYMLVL.ALL) +SPEECH_SYMBOL_LEVELS = CONFIGURABLE_SPEECH_SYMBOL_LEVELS + (SYMLVL.CHAR,) # Speech symbol preserve modes SYMPRES_NEVER = 0 @@ -255,11 +269,11 @@ def _loadSymbolField(self, input, inputMap=None): } IDENTIFIER_ESCAPES_OUTPUT = {v: k for k, v in IDENTIFIER_ESCAPES_INPUT.items()} LEVEL_INPUT = { - "none": SYMLVL_NONE, - "some": SYMLVL_SOME, - "most": SYMLVL_MOST, - "all": SYMLVL_ALL, - "char": SYMLVL_CHAR, + "none": SYMLVL.NONE, + "some": SYMLVL.SOME, + "most": SYMLVL.MOST, + "all": SYMLVL.ALL, + "char": SYMLVL.CHAR, } LEVEL_OUTPUT = {v:k for k, v in LEVEL_INPUT.items()} PRESERVE_INPUT = { @@ -485,7 +499,7 @@ def __init__(self, locale): pass continue if symbol.level is None: - symbol.level = SYMLVL_ALL + symbol.level = SYMLVL.ALL if symbol.preserve is None: symbol.preserve = SYMPRES_NEVER if symbol.displayName is None: @@ -667,7 +681,7 @@ def processSpeechSymbols(locale, text, level): @type locale: str @param text: The text to process. @type text: str - @param level: The symbol level to use; one of the SYMLVL_* constants. + @param level: The symbol level to use; one of the SYMLVL.* constants. """ try: ss = _localeSpeechSymbolProcessors.fetchLocaleData(locale) diff --git a/source/globalCommands.py b/source/globalCommands.py index b5531e171a0..1f3172777fe 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -903,7 +903,7 @@ def script_cycleSpeechSymbolLevel(self,gesture): def script_toggleSpeechSymbolLevelWordAll(self, gesture): curLevel = config.conf["speech"]["symbolLevelWord"] if curLevel == characterProcessing.SYMLVL_ALL: - level = characterProcessing.SYMLVL_UNCHANGED + level = characterProcessing.SYMLVL.UNCHANGED reportedState = _("off") else: level = characterProcessing.SYMLVL_ALL diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 9524d336b79..633b8f5d6b7 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1504,7 +1504,7 @@ def makeSettings(self, settingsSizer): self.bindHelpEvent("SpeechSettingsSymbolLevelWord", self.symbolLevelWord) if config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_ALL: self.symbolLevelWord.Set3StateValue(wx.CHK_CHECKED) - elif config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_UNCHANGED: + elif config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL.UNCHANGED: self.symbolLevelWord.Set3StateValue(wx.CHK_UNCHECKED) else: self.symbolLevelWord.Set3StateValue(wx.CHK_UNDETERMINED) @@ -1602,7 +1602,7 @@ def onSave(self): if self.symbolLevelWord.Get3StateValue() == wx.CHK_CHECKED: config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL_ALL elif self.symbolLevelWord.Get3StateValue() == wx.CHK_UNCHECKED: - config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL_UNCHANGED + config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL.UNCHANGED 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 c97cc24e183..00142b7b592 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -1125,12 +1125,12 @@ def speakTextInfo( ) speechGen = GeneratorWithReturn(speechGen) - symbolLevel = None + symbolLevel: Optional[characterProcessing.SYMLVL] = None if unit == textInfos.UNIT_CHARACTER: symbolLevel = characterProcessing.SYMLVL_ALL elif unit == textInfos.UNIT_WORD: symbolLevelWord = config.conf["speech"]["symbolLevelWord"] - if symbolLevelWord != characterProcessing.SYMLVL_UNCHANGED: + if symbolLevelWord != characterProcessing.SYMLVL.UNCHANGED: symbolLevel = symbolLevelWord for seq in speechGen: speak(seq, symbolLevel=symbolLevel, priority=priority) From 5cc726b7f009954b0c93a71c12b556c469e7915e Mon Sep 17 00:00:00 2001 From: buddsean Date: Mon, 7 Jun 2021 17:43:14 +1000 Subject: [PATCH 14/23] update changes Add issue num to changes.t2t --- user_docs/en/changes.t2t | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 231a73a3ba0..2ee64865ba1 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -14,6 +14,9 @@ What's New in NVDA == 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) +- == Bug Fixes == @@ -25,6 +28,8 @@ What's New in NVDA == Changes for Developers == +- `characterProcessing.SYMLVL_*` constants should be replaced using their equivalent `SYMLVL.*` before 2022.1. (#11856) +- = 2021.1 = From 1709685730f4264bc10afb21c1ff087376647f17 Mon Sep 17 00:00:00 2001 From: buddsean Date: Mon, 7 Jun 2021 17:43:14 +1000 Subject: [PATCH 15/23] update changes --- user_docs/en/changes.t2t | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 231a73a3ba0..d8dc758bd07 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -14,6 +14,9 @@ What's New in NVDA == 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. +- == Bug Fixes == @@ -25,6 +28,8 @@ What's New in NVDA == Changes for Developers == +- `characterProcessing.SYMLVL_*` constants should be replaced using their equivalent `SYMLVL.*` before 2022.1. (#11856) +- = 2021.1 = From d4f18b829c7582ed25df58a772a2ab6510c1cb0e Mon Sep 17 00:00:00 2001 From: buddsean Date: Tue, 8 Jun 2021 15:51:52 +1000 Subject: [PATCH 16/23] undo speech chmod --- source/speech/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 source/speech/__init__.py diff --git a/source/speech/__init__.py b/source/speech/__init__.py old mode 100755 new mode 100644 From 76dec02affc99919793e1b759981c56e652a19f5 Mon Sep 17 00:00:00 2001 From: buddsean Date: Tue, 8 Jun 2021 15:52:12 +1000 Subject: [PATCH 17/23] fix typing --- source/characterProcessing.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/source/characterProcessing.py b/source/characterProcessing.py index 36a1ecc1a6d..c859609d43c 100644 --- a/source/characterProcessing.py +++ b/source/characterProcessing.py @@ -675,13 +675,12 @@ def isBuiltin(self, symbolIdentifier): _localeSpeechSymbolProcessors = LocaleDataMap(SpeechSymbolProcessor) -def processSpeechSymbols(locale, text, level): + +def processSpeechSymbols(locale: str, text: str, level: SYMLVL): """Process some text, converting symbols according to desired pronunciation. @param locale: The locale of the text. - @type locale: str @param text: The text to process. - @type text: str - @param level: The symbol level to use; one of the SYMLVL.* constants. + @param level: The symbol level to use. """ try: ss = _localeSpeechSymbolProcessors.fetchLocaleData(locale) From d4ccb859249db06c68c055b9af40ff42eccfe944 Mon Sep 17 00:00:00 2001 From: buddsean Date: Tue, 8 Jun 2021 16:16:53 +1000 Subject: [PATCH 18/23] eliminate the tristate checkbox --- source/config/configSpec.py | 4 ++-- source/globalCommands.py | 11 +++-------- source/gui/settingsDialogs.py | 17 ++++------------- source/speech/speech.py | 5 ++--- .../nvdaSettingsFiles/symbolLevelWordOff.ini | 2 +- 5 files changed, 12 insertions(+), 27 deletions(-) diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 60ef8c09eac..b3d9bf51b31 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -31,8 +31,8 @@ # The synthesizer to use synth = string(default=auto) symbolLevel = integer(default=100) - # Symbol Level when reviewing by word (unchanged if -1) - symbolLevelWord = integer(default=300) + # 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 1f3172777fe..a3bb08940aa 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -901,14 +901,9 @@ def script_cycleSpeechSymbolLevel(self,gesture): category=SCRCAT_SPEECH ) def script_toggleSpeechSymbolLevelWordAll(self, gesture): - curLevel = config.conf["speech"]["symbolLevelWord"] - if curLevel == characterProcessing.SYMLVL_ALL: - level = characterProcessing.SYMLVL.UNCHANGED - reportedState = _("off") - else: - level = characterProcessing.SYMLVL_ALL - reportedState = _("on") - config.conf["speech"]["symbolLevelWord"] = level + curLevel = config.conf["speech"]["symbolLevelWordAll"] + reportedState = _("off") if curLevel else _("on") + config.conf["speech"]["symbolLevelWordAll"] = not curLevel ui.message( # Translators: Reported when toggling a speech setting _("Speak all punctuation and symbols when reviewing by word: {state}").format( diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 633b8f5d6b7..8181053c06b 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1493,21 +1493,15 @@ def makeSettings(self, settingsSizer): characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS.index(curLevel) ) - self.symbolLevelWord = settingsSizerHelper.addItem( + 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"), - style=wx.CHK_3STATE ) ) - self.bindHelpEvent("SpeechSettingsSymbolLevelWord", self.symbolLevelWord) - if config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL_ALL: - self.symbolLevelWord.Set3StateValue(wx.CHK_CHECKED) - elif config.conf["speech"]["symbolLevelWord"] == characterProcessing.SYMLVL.UNCHANGED: - self.symbolLevelWord.Set3StateValue(wx.CHK_UNCHECKED) - else: - self.symbolLevelWord.Set3StateValue(wx.CHK_UNDETERMINED) + 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). @@ -1599,10 +1593,7 @@ def onSave(self): config.conf["speech"]["symbolLevel"] = characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[ self.symbolLevelList.GetSelection() ] - if self.symbolLevelWord.Get3StateValue() == wx.CHK_CHECKED: - config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL_ALL - elif self.symbolLevelWord.Get3StateValue() == wx.CHK_UNCHECKED: - config.conf["speech"]["symbolLevelWord"] = characterProcessing.SYMLVL.UNCHANGED + 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 00142b7b592..dd475a7cc12 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -1129,9 +1129,8 @@ def speakTextInfo( if unit == textInfos.UNIT_CHARACTER: symbolLevel = characterProcessing.SYMLVL_ALL elif unit == textInfos.UNIT_WORD: - symbolLevelWord = config.conf["speech"]["symbolLevelWord"] - if symbolLevelWord != characterProcessing.SYMLVL.UNCHANGED: - symbolLevel = symbolLevelWord + if config.conf["speech"]["symbolLevelWordAll"]: + symbolLevel = characterProcessing.SYMLVL.ALL for seq in speechGen: speak(seq, symbolLevel=symbolLevel, priority=priority) return speechGen.returnValue diff --git a/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini b/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini index cea495ae923..1d9d28b38da 100644 --- a/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini +++ b/tests/system/nvdaSettingsFiles/symbolLevelWordOff.ini @@ -8,7 +8,7 @@ schemaVersion = 2 allowUsageStats = False [speech] synth = speechSpySynthDriver - symbolLevelWord = -1 + symbolLevelWordAll = False [development] enableScratchpadDir = True [virtualBuffers] From 2853f5d09c58d080b77a87ab8f2eafd1575fd96a Mon Sep 17 00:00:00 2001 From: buddsean Date: Tue, 8 Jun 2021 16:19:26 +1000 Subject: [PATCH 19/23] remove sanity check in tests --- tests/system/robot/notepadTests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/system/robot/notepadTests.py b/tests/system/robot/notepadTests.py index 8c7b205ab23..1d22fceb27d 100644 --- a/tests/system/robot/notepadTests.py +++ b/tests/system/robot/notepadTests.py @@ -28,8 +28,6 @@ def test_symbolLevelWord(isSymbolLevelWordAllExpected=True): '.': 'dot', } textStr = ' '.join(wordsWithSymbols) - _asserts.strings_match(textStr, 'He said (quietly), "Hello, Jim".') - _notepad.prepareNotepad(f"Test: {textStr}") for expectedWord in wordsWithSymbols: wordSpoken = _notepad.getSpeechAfterKey("numpad6") From 5972e8463d564bcf186d8e7b413c3024dbb65c44 Mon Sep 17 00:00:00 2001 From: buddsean Date: Tue, 8 Jun 2021 16:23:50 +1000 Subject: [PATCH 20/23] translation category --- source/globalCommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index a3bb08940aa..94fb2e8feb1 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -902,7 +902,7 @@ def script_cycleSpeechSymbolLevel(self,gesture): ) def script_toggleSpeechSymbolLevelWordAll(self, gesture): curLevel = config.conf["speech"]["symbolLevelWordAll"] - reportedState = _("off") if curLevel else _("on") + reportedState = pgettext("command toggle", "off") if curLevel else pgettext("command toggle", "on") config.conf["speech"]["symbolLevelWordAll"] = not curLevel ui.message( # Translators: Reported when toggling a speech setting From 9f038575e31a43c1c97ec0981cd496a8a4887fac Mon Sep 17 00:00:00 2001 From: buddsean Date: Wed, 9 Jun 2021 10:23:51 +1000 Subject: [PATCH 21/23] add translator comment --- source/globalCommands.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index 94fb2e8feb1..8cd3388ea35 100644 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -901,9 +901,14 @@ def script_cycleSpeechSymbolLevel(self,gesture): category=SCRCAT_SPEECH ) def script_toggleSpeechSymbolLevelWordAll(self, gesture): - curLevel = config.conf["speech"]["symbolLevelWordAll"] - reportedState = pgettext("command toggle", "off") if curLevel else pgettext("command toggle", "on") - config.conf["speech"]["symbolLevelWordAll"] = not curLevel + 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( From 2daaca256bc4b9f2fe817f15ce4c22e24fe17ce1 Mon Sep 17 00:00:00 2001 From: buddsean Date: Wed, 7 Jul 2021 13:55:54 +1000 Subject: [PATCH 22/23] fixup! Merge branch 'master' into i11779-moveByWordSymbolLevel --- source/speech/speech.py | 2 +- tests/system/robot/notepadTests.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/speech/speech.py b/source/speech/speech.py index d6f2f738a6c..f69b0f2f50c 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -1128,7 +1128,7 @@ def speakTextInfo( speechGen = GeneratorWithReturn(speechGen) symbolLevel: Optional[characterProcessing.SYMLVL] = None if unit == textInfos.UNIT_CHARACTER: - symbolLevel = characterProcessing.SYMLVL_ALL + symbolLevel = characterProcessing.SYMLVL.ALL elif unit == textInfos.UNIT_WORD: if config.conf["speech"]["symbolLevelWordAll"]: symbolLevel = characterProcessing.SYMLVL.ALL diff --git a/tests/system/robot/notepadTests.py b/tests/system/robot/notepadTests.py index 1d22fceb27d..91c792a1e42 100644 --- a/tests/system/robot/notepadTests.py +++ b/tests/system/robot/notepadTests.py @@ -34,5 +34,6 @@ def test_symbolLevelWord(isSymbolLevelWordAllExpected=True): for symbol in symbolMap.keys(): if isSymbolLevelWordAllExpected: expectedWord = expectedWord.replace(symbol, f" {symbolMap[symbol]}{symbol}") - expectedWord = expectedWord.replace('"', ' ').strip() # always strip quote symbols + # unlike other symbols used, symbols.dic doesn't preserve quote symbols with SYMPRES_ALWAYS + expectedWord = expectedWord.replace('"', ' ').strip() _asserts.strings_match(wordSpoken, expectedWord) From cf1b1ed71b0756de4e47847fe0e0b0213c089079 Mon Sep 17 00:00:00 2001 From: buddsean Date: Wed, 7 Jul 2021 15:47:28 +1000 Subject: [PATCH 23/23] change system tests --- tests/system/robot/notepadTests.py | 44 ++++++++++++++++----------- tests/system/robot/notepadTests.robot | 4 +-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/tests/system/robot/notepadTests.py b/tests/system/robot/notepadTests.py index 91c792a1e42..52348359c4f 100644 --- a/tests/system/robot/notepadTests.py +++ b/tests/system/robot/notepadTests.py @@ -18,22 +18,32 @@ _asserts: _AssertsLib = _getLib("AssertsLib") -def test_symbolLevelWord(isSymbolLevelWordAllExpected=True): - wordsWithSymbols = ['He', 'said', '(quietly),', '"Hello,', 'Jim".'] - symbolMap = { - '(': 'left paren', - ')': 'right paren', - ',': 'comma', - '"': 'quote', - '.': 'dot', - } - textStr = ' '.join(wordsWithSymbols) +# unlike other symbols used, symbols.dic doesn't preserve quote symbols with SYMPRES_ALWAYS +_wordsToExpectedSymbolLevelAllSpeech = { + 'Say': 'Say', + '(quietly)': 'left paren(quietly right paren)', + '"Hello,': 'quote Hello comma,', + 'Jim".': 'Jim quote dot.', +} +_wordsToExpectedSymbolLevelDefaultSpeech = { + 'Say': 'Say', + '(quietly)': '(quietly)', + '"Hello,': 'Hello,', + 'Jim".': 'Jim .', +} + + +def test_symbolLevelWord_all(): + textStr = ' '.join(_wordsToExpectedSymbolLevelAllSpeech.keys()) + _notepad.prepareNotepad(f"Test: {textStr}") + for expectedWord in _wordsToExpectedSymbolLevelAllSpeech.values(): + wordSpoken = _notepad.getSpeechAfterKey("numpad6") # navigate to next word + _asserts.strings_match(wordSpoken, expectedWord) + + +def test_symbolLevelWord_default(): + textStr = ' '.join(_wordsToExpectedSymbolLevelDefaultSpeech.keys()) _notepad.prepareNotepad(f"Test: {textStr}") - for expectedWord in wordsWithSymbols: - wordSpoken = _notepad.getSpeechAfterKey("numpad6") - for symbol in symbolMap.keys(): - if isSymbolLevelWordAllExpected: - expectedWord = expectedWord.replace(symbol, f" {symbolMap[symbol]}{symbol}") - # unlike other symbols used, symbols.dic doesn't preserve quote symbols with SYMPRES_ALWAYS - expectedWord = expectedWord.replace('"', ' ').strip() + for expectedWord in _wordsToExpectedSymbolLevelDefaultSpeech.values(): + wordSpoken = _notepad.getSpeechAfterKey("numpad6") # navigate to next word _asserts.strings_match(wordSpoken, expectedWord) diff --git a/tests/system/robot/notepadTests.robot b/tests/system/robot/notepadTests.robot index 36ea7ec456c..da7f275e763 100644 --- a/tests/system/robot/notepadTests.robot +++ b/tests/system/robot/notepadTests.robot @@ -28,8 +28,8 @@ default setup *** Test Cases *** symbolLevelWord [Documentation] Ensure all symbols are read when navigating by word. - test symbolLevelWord + test symbolLevelWord all symbolLevelWord off [Documentation] Use default behaviour of reading symbols when navigating by word. [Setup] start NVDA symbolLevelWordOff.ini - test symbolLevelWord False + test symbolLevelWord default