5252
5353from . import types
5454from .types import SpeechSequence , SequenceItemT
55- from typing import Optional , Dict , List , Any , Generator , Callable , Union
55+ from typing import Optional , Dict , List , Any , Generator , Union , Callable , Iterator , Tuple
5656from logHandler import log
5757import config
5858import aria
@@ -421,31 +421,48 @@ def getObjectPropertiesSpeech( # noqa: C901
421421 return speechSequence
422422
423423
424- def _speakPlaceholderIfEmpty (
425- info : textInfos .TextInfo ,
424+ def _getPlaceholderSpeechIfTextEmpty (
426425 obj ,
427426 reason : str ,
428- priority : Optional [Spri ] = None
429- ) -> bool :
430- """ attempt to speak placeholder attribute if the textInfo 'info' is empty
431- @return: True if info was considered empty, and we attempted to speak the placeholder value.
432- False if info was not considered empty.
427+ ) -> Tuple [bool , SpeechSequence ]:
428+ """ Attempt to get speech for placeholder attribute if text for 'obj' is empty. Don't report the placeholder
429+ value unless the text is empty, because it is confusing to hear the current value (presumably typed by the
430+ user) *and* the placeholder. The placeholder should "disappear" once the user types a value.
431+ @return: (True, SpeechSequence) if text for obj was considered empty and we attempted to get speech for the
432+ placeholder value. (False, []) if text for obj was not considered empty.
433433 """
434434 textEmpty = obj ._isTextEmpty
435435 if textEmpty :
436- speakObjectProperties (obj ,reason = reason ,placeholder = True ,priority = priority )
437- return True
438- return False
436+ return True , getObjectPropertiesSpeech (obj , reason = reason , placeholder = True )
437+ return False , []
439438
440439
441- # C901 'speakObject' is too complex
442- # Note: when working on speakObject, look for opportunities to simplify
443- # and move logic out into smaller helper functions.
444- def speakObject ( # noqa: C901
440+ def speakObject (
445441 obj ,
446442 reason : str = controlTypes .REASON_QUERY ,
447443 _prefixSpeechCommand : Optional [SpeechCommand ] = None ,
448444 priority : Optional [Spri ] = None
445+ ):
446+ sequence = getObjectSpeech (
447+ obj ,
448+ reason ,
449+ _prefixSpeechCommand ,
450+ )
451+ if sequence :
452+ speak (sequence , priority = priority )
453+
454+
455+ def _flattenNestedSequences (nestedSequences : Iterator [SpeechSequence ]) -> Iterator [SequenceItemT ]:
456+ return [i for seq in nestedSequences for i in seq ]
457+
458+
459+ # C901 'getObjectSpeech' is too complex
460+ # Note: when working on getObjectSpeech, look for opportunities to simplify
461+ # and move logic out into smaller helper functions.
462+ def getObjectSpeech ( # noqa: C901
463+ obj ,
464+ reason : str = controlTypes .REASON_QUERY ,
465+ _prefixSpeechCommand : Optional [SpeechCommand ] = None ,
449466):
450467 from NVDAObjects import NVDAObjectTextInfo
451468 role = obj .role
@@ -462,35 +479,57 @@ def speakObject( # noqa: C901
462479
463480 allowProperties = _objectSpeech_calculateAllowedProps (reason , shouldReportTextContent )
464481
465- if reason == controlTypes .REASON_FOCUSENTERED :
482+ if reason == controlTypes .REASON_FOCUSENTERED :
466483 # Aside from excluding some properties, focus entered should be spoken like focus.
467- reason = controlTypes .REASON_FOCUS
484+ reason = controlTypes .REASON_FOCUS
468485
469- speakObjectProperties (obj , reason = reason , _prefixSpeechCommand = _prefixSpeechCommand , priority = priority , ** allowProperties )
486+ sequence = getObjectPropertiesSpeech (
487+ obj ,
488+ reason = reason ,
489+ _prefixSpeechCommand = _prefixSpeechCommand ,
490+ ** allowProperties
491+ )
470492 if reason == controlTypes .REASON_ONLYCACHE :
471- return
493+ return sequence
472494 if shouldReportTextContent :
473495 try :
474496 info = obj .makeTextInfo (textInfos .POSITION_SELECTION )
475497 if not info .isCollapsed :
476498 # if there is selected text, then there is a value and we do not report placeholder
477- speakPreselectedText ( info .text , priority = priority )
499+ sequence . extend ( getPreselectedTextSpeech ( info .text ) )
478500 else :
479501 info .expand (textInfos .UNIT_LINE )
480- _speakPlaceholderIfEmpty (info , obj , reason ,priority = priority )
481- speakTextInfo (info ,unit = textInfos .UNIT_LINE ,reason = controlTypes .REASON_CARET ,priority = priority )
502+ textEmpty , placeholderSeq = _getPlaceholderSpeechIfTextEmpty (obj , reason )
503+ sequence .extend (placeholderSeq )
504+ speechGen = getTextInfoSpeech (
505+ info ,
506+ unit = textInfos .UNIT_LINE ,
507+ reason = controlTypes .REASON_CARET
508+ )
509+ sequence .extend (_flattenNestedSequences (speechGen ))
482510 except : # noqa E722 legacy bare except. Unknown what exceptions may be raised.
483511 newInfo = obj .makeTextInfo (textInfos .POSITION_ALL )
484- if not _speakPlaceholderIfEmpty (newInfo , obj , reason ,priority = priority ):
485- speakTextInfo (newInfo ,unit = textInfos .UNIT_PARAGRAPH ,reason = controlTypes .REASON_CARET ,priority = priority )
512+ textEmpty , placeholderSeq = _getPlaceholderSpeechIfTextEmpty (obj , reason )
513+ if textEmpty :
514+ sequence .extend (placeholderSeq )
515+ else :
516+ speechGen = getTextInfoSpeech (
517+ newInfo ,
518+ unit = textInfos .UNIT_PARAGRAPH ,
519+ reason = controlTypes .REASON_CARET ,
520+ )
521+ sequence .extend (_flattenNestedSequences (speechGen ))
486522 elif role == controlTypes .ROLE_MATH :
487523 import mathPres
488524 mathPres .ensureInit ()
489525 if mathPres .speechProvider :
490526 try :
491- speak (mathPres .speechProvider .getSpeechForMathMl (obj .mathMl ),priority = priority )
527+ sequence .extend (
528+ mathPres .speechProvider .getSpeechForMathMl (obj .mathMl )
529+ )
492530 except (NotImplementedError , LookupError ):
493531 pass
532+ return sequence
494533
495534
496535def _objectSpeech_calculateAllowedProps (reason , shouldReportTextContent ):
0 commit comments