Skip to content

Commit a3f90c3

Browse files
committed
Don't report out of errors when errors shouldn't be reported with speech
1 parent b9d2839 commit a3f90c3

File tree

2 files changed

+74
-17
lines changed

2 files changed

+74
-17
lines changed

include/liblouis

Submodule liblouis updated 78 files

source/speech/speech.py

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
SuppressUnicodeNormalizationCommand,
4141
CharacterModeCommand,
4242
WaveFileCommand,
43+
CallbackCommand,
44+
_CancellableSpeechCommand,
4345
)
4446
from .shortcutKeys import getKeyboardShortcutsSpeech
4547

@@ -87,6 +89,8 @@
8789
_speechState: Optional["SpeechState"] = None
8890
_curWordChars: List[str] = []
8991
IDEOGRAPHIC_COMMA: Final[str] = "\u3001"
92+
_lastSpeech: tuple[SpeechSequence, characterProcessing.SymbolLevel | None] | None = None
93+
"""Last spoken text and the symbol level with which it was spoken."""
9094

9195

9296
class SpeechMode(DisplayStringIntEnum):
@@ -141,6 +145,24 @@ def setSpeechMode(newMode: SpeechMode):
141145
_speechState.speechMode = newMode
142146

143147

148+
def _setLastSpeechString(
149+
speechSequence: SpeechSequence,
150+
symbolLevel: characterProcessing.SymbolLevel | None,
151+
priority: Spri,
152+
):
153+
# Check if the speech sequence contains text to speak
154+
if any(isinstance(item, str) for item in speechSequence):
155+
global _lastSpeech
156+
_lastSpeech = (
157+
[
158+
item
159+
for item in speechSequence
160+
if not isinstance(item, (CallbackCommand, _CancellableSpeechCommand))
161+
],
162+
symbolLevel,
163+
)
164+
165+
144166
def initialize():
145167
global _speechState
146168
_speechState = SpeechState()
@@ -365,6 +387,8 @@ def _getSpellingSpeechAddCharMode(
365387
yield CharacterModeCommand(False)
366388
charMode = False
367389
yield item
390+
if charMode:
391+
yield CharacterModeCommand(False)
368392

369393

370394
def _getSpellingCharAddCapNotification(
@@ -425,6 +449,7 @@ def _getSpellingSpeechWithoutCharMode(
425449
fallbackToCharIfNoDescription: bool = True,
426450
unicodeNormalization: bool = False,
427451
reportNormalizedForCharacterNavigation: bool = False,
452+
endsUtterance: bool = True,
428453
) -> Generator[SequenceItemT, None, None]:
429454
"""
430455
Processes text when spelling by character.
@@ -446,6 +471,7 @@ def _getSpellingSpeechWithoutCharMode(
446471
:param unicodeNormalization: Whether to use Unicode normalization for the given text.
447472
:param reportNormalizedForCharacterNavigation: When unicodeNormalization is true, indicates if 'normalized'
448473
should be reported along with the currently spelled character.
474+
:param endsUtterance: Whether an EndUtteranceCommand should be yielded at the end.
449475
:returns: A speech sequence generator.
450476
"""
451477
defaultLanguage = getCurrentLanguage()
@@ -521,7 +547,8 @@ def _getSpellingSpeechWithoutCharMode(
521547
uppercase and beepForCapitals,
522548
itemIsNormalized and reportNormalizedForCharacterNavigation,
523549
)
524-
yield EndUtteranceCommand()
550+
if endsUtterance:
551+
yield EndUtteranceCommand()
525552

526553

527554
def getSingleCharDescriptionDelayMS() -> int:
@@ -574,9 +601,20 @@ def getSingleCharDescription(
574601

575602
def getSpellingSpeech(
576603
text: str,
577-
locale: Optional[str] = None,
604+
locale: str | None = None,
578605
useCharacterDescriptions: bool = False,
606+
endsUtterance: bool = True,
607+
useCharMode: bool = True,
579608
) -> Generator[SequenceItemT, None, None]:
609+
"""
610+
Gets a speech sequence for spelling text.
611+
:param text: The text to be spelled.
612+
:param locale: The locale to use for character descriptions, if applicable.
613+
:param useCharacterDescriptions: Whether or not to use character descriptions, e.g. speak "a" as "alpha".
614+
:param endsUtterance: Whether an EndUtteranceCommand should be yielded at the end.
615+
:param useCharMode: Whether to wrap the sequence in CharacterModeCommand.
616+
:returns: A speech sequence generator.
617+
"""
580618
synth = getSynth()
581619
synthConfig = config.conf["speech"][synth.name]
582620

@@ -598,8 +636,9 @@ def getSpellingSpeech(
598636
reportNormalizedForCharacterNavigation=config.conf["speech"][
599637
"reportNormalizedForCharacterNavigation"
600638
],
639+
endsUtterance=endsUtterance,
601640
)
602-
if synthConfig["useSpellingFunctionality"]:
641+
if useCharMode and synthConfig["useSpellingFunctionality"]:
603642
seq = _getSpellingSpeechAddCharMode(seq)
604643
# This function applies Unicode normalization as appropriate.
605644
# Therefore, suppress the global normalization that might still occur
@@ -1189,7 +1228,7 @@ def speak( # noqa: C901
11891228

11901229
def speakPreselectedText(
11911230
text: str,
1192-
priority: Optional[Spri] = None,
1231+
priority: Spri | None = None,
11931232
):
11941233
"""Helper method to announce that a newly focused control already has
11951234
text selected. This method is in contrast with L{speakTextSelected}.
@@ -1228,8 +1267,8 @@ def getPreselectedTextSpeech(
12281267

12291268

12301269
def speakTextSelected(
1231-
text: str,
1232-
priority: Optional[Spri] = None,
1270+
text: str | SpeechSequence,
1271+
priority: Spri | None = None,
12331272
):
12341273
"""Helper method to announce that the user has caused text to be selected.
12351274
This method is in contrast with L{speakPreselectedText}.
@@ -1246,8 +1285,8 @@ def speakTextSelected(
12461285

12471286
def speakSelectionMessage(
12481287
message: str,
1249-
text: str,
1250-
priority: Optional[Spri] = None,
1288+
text: str | SpeechSequence,
1289+
priority: Spri | None = None,
12511290
):
12521291
seq = _getSelectionMessageSpeech(message, text)
12531292
if seq:
@@ -1259,8 +1298,26 @@ def speakSelectionMessage(
12591298

12601299
def _getSelectionMessageSpeech(
12611300
message: str,
1262-
text: str,
1301+
text: str | SpeechSequence,
12631302
) -> SpeechSequence:
1303+
if isinstance(text, list):
1304+
# If text is a speech sequence, we can't use string formatting.
1305+
# Instead, split the message by %s and insert the sequence.
1306+
# This allows for correct localization order (e.g. prefix vs suffix).
1307+
prefix, sep, suffix = message.partition("%s")
1308+
if not sep:
1309+
log.warning("Selection message '%s' does not contain '%%s'", message)
1310+
return _getSpeakMessageSpeech(message) + text
1311+
1312+
seq = list(text)
1313+
# Insert prefix/suffix as separate items so they remain outside any
1314+
# speech commands (e.g. PitchCommand for capitals).
1315+
if prefix:
1316+
seq.insert(0, prefix)
1317+
if suffix:
1318+
seq.append(suffix)
1319+
return seq
1320+
12641321
if len(text) < MAX_LENGTH_FOR_SELECTION_REPORTING:
12651322
return _getSpeakMessageSpeech(message % text)
12661323
textLength = len(text)
@@ -1279,7 +1336,7 @@ def speakSelectionChange( # noqa: C901
12791336
speakSelected: bool = True,
12801337
speakUnselected: bool = True,
12811338
generalize: bool = False,
1282-
priority: Optional[Spri] = None,
1339+
priority: Spri | None = None,
12831340
):
12841341
"""Speaks a change in selection, either selected or unselected text.
12851342
@param oldInfo: a TextInfo instance representing what the selection was before
@@ -1327,25 +1384,25 @@ def speakSelectionChange( # noqa: C901
13271384
if not generalize:
13281385
for text in selectedTextList:
13291386
if len(text) == 1:
1330-
text = characterProcessing.processSpeechSymbol(locale, text)
1387+
text = list(getSpellingSpeech(text, locale, endsUtterance=False, useCharMode=False))
13311388
speakTextSelected(text, priority=priority)
13321389
elif len(selectedTextList) > 0:
13331390
text = newInfo.text
13341391
if len(text) == 1:
1335-
text = characterProcessing.processSpeechSymbol(locale, text)
1392+
text = list(getSpellingSpeech(text, locale, endsUtterance=False, useCharMode=False))
13361393
speakTextSelected(text, priority=priority)
13371394
if speakUnselected:
13381395
if not generalize:
13391396
for text in unselectedTextList:
13401397
if len(text) == 1:
1341-
text = characterProcessing.processSpeechSymbol(locale, text)
1398+
text = list(getSpellingSpeech(text, locale, endsUtterance=False, useCharMode=False))
13421399
# Translators: This is spoken to indicate what has been unselected. for example 'hello unselected'
13431400
speakSelectionMessage(_("%s unselected"), text, priority=priority)
13441401
elif len(unselectedTextList) > 0:
13451402
if not newInfo.isCollapsed:
13461403
text = newInfo.text
13471404
if len(text) == 1:
1348-
text = characterProcessing.processSpeechSymbol(locale, text)
1405+
text = list(getSpellingSpeech(text, locale, endsUtterance=False, useCharMode=False))
13491406
# Translators: This is spoken to indicate when the previous selection was removed and a new selection was made. for example 'hello world selected instead'
13501407
speakSelectionMessage(_("%s selected instead"), text, priority=priority)
13511408
else:
@@ -3018,7 +3075,7 @@ def getFormatFieldSpeech( # noqa: C901
30183075
if formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SPEECH.value:
30193076
# Translators: Reported when text contains a spelling error.
30203077
texts.append(_("spelling error"))
3021-
elif extraDetail:
3078+
elif extraDetail and formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SPEECH.value:
30223079
# Translators: Reported when moving out of text containing a spelling error.
30233080
texts.append(_("out of spelling error"))
30243081
textList.extend(texts)
@@ -3032,7 +3089,7 @@ def getFormatFieldSpeech( # noqa: C901
30323089
if formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SPEECH.value:
30333090
# Translators: Reported when text contains a grammar error.
30343091
texts.append(_("grammar error"))
3035-
elif extraDetail:
3092+
elif extraDetail and formatConfig["reportSpellingErrors2"] & ReportSpellingErrors.SPEECH.value:
30363093
# Translators: Reported when moving out of text containing a grammar error.
30373094
texts.append(_("out of grammar error"))
30383095
textList.extend(texts)

0 commit comments

Comments
 (0)