Skip to content

Commit c12f422

Browse files
authored
Merge 69b2af1 into 38efb1c
2 parents 38efb1c + 69b2af1 commit c12f422

12 files changed

Lines changed: 257 additions & 40 deletions

File tree

projectDocs/dev/developerGuide/developerGuide.t2t

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,8 @@ For examples of how to define and use new extension points, please see the code
10411041
++ speech ++[speechExtPts]
10421042
|| Type | Extension Point | Description |
10431043
| ``Action`` | ``speechCanceled`` | Triggered when speech is canceled. |
1044+
| ``Action`` | ``pre_speechCanceled`` | Triggered before speech is canceled. |
1045+
| ``Action`` | ``pre_speech`` | Triggered when NVDA is about to speak something. |
10441046
| ``Filter`` | ``filter_speechSequence`` | Allows components or add-ons to filter speech sequence before it passes to the Synth driver. |
10451047

10461048
++ synthDriverHandler ++[synthDriverHandlerExtPts]

source/braille.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import driverHandler
2828
import pkgutil
2929
import importlib
30+
import contextlib
3031
import ctypes.wintypes
3132
import threading
3233
import time
@@ -41,6 +42,7 @@
4142
from config.configFlags import (
4243
ShowMessages,
4344
TetherTo,
45+
BrailleMode,
4446
ReportTableHeaders,
4547
)
4648
from config.featureFlagEnums import ReviewRoutingMovesSystemCaretFlag
@@ -66,6 +68,7 @@
6668

6769
if TYPE_CHECKING:
6870
from NVDAObjects import NVDAObject
71+
from speech.types import SpeechSequence
6972

7073

7174
roleLabels: typing.Dict[controlTypes.Role, str] = {
@@ -2067,8 +2070,19 @@ def __init__(self):
20672070
self.ackTimerHandle = winKernel.createWaitableTimer()
20682071

20692072
brailleViewer.postBrailleViewerToolToggledAction.register(self._onBrailleViewerChangedState)
2073+
# noqa: F401 avoid module level import to prevent cyclical dependency
2074+
# between speech and braille
2075+
from speech.extensions import pre_speech, pre_speechCanceled
2076+
pre_speech.register(self._showSpeechInBraille)
2077+
pre_speechCanceled.register(self.clearBrailleRegions)
2078+
20702079

20712080
def terminate(self):
2081+
# noqa: F401 avoid module level import to prevent cyclical dependency
2082+
# between speech and braille
2083+
from speech.extensions import pre_speech, pre_speechCanceled
2084+
pre_speechCanceled.unregister(self.clearBrailleRegions)
2085+
pre_speech.unregister(self._showSpeechInBraille)
20722086
self._disableDetection()
20732087
if self._messageCallLater:
20742088
self._messageCallLater.Stop()
@@ -2087,6 +2101,46 @@ def terminate(self):
20872101
self.ackTimerHandle = None
20882102
louisHelper.terminate()
20892103

2104+
# The list containing the regions that will be shown in braille when the speak function is called
2105+
# and the braille mode is set to speech output
2106+
_showSpeechInBrailleRegions: list[TextRegion] = []
2107+
2108+
def _showSpeechInBraille(self, speechSequence: "SpeechSequence"):
2109+
if config.conf["braille"]["mode"] == BrailleMode.FOLLOW_CURSORS.value:
2110+
return
2111+
_showSpeechInBrailleRegions = self._showSpeechInBrailleRegions
2112+
regionsText = "".join([i.rawText for i in _showSpeechInBrailleRegions])
2113+
if len(regionsText) > 100000:
2114+
return
2115+
text = " ".join([x for x in speechSequence if isinstance(x, str)])
2116+
currentRegions = False
2117+
if _showSpeechInBrailleRegions:
2118+
text = f" {text}"
2119+
currentRegions = True
2120+
2121+
region = TextRegion(text)
2122+
region.update()
2123+
_showSpeechInBrailleRegions.append(region)
2124+
self.mainBuffer.regions = _showSpeechInBrailleRegions.copy()
2125+
if not currentRegions:
2126+
handler.mainBuffer.focus(_showSpeechInBrailleRegions[0])
2127+
handler.mainBuffer.update()
2128+
handler.update()
2129+
2130+
_suppressClearBrailleRegions: bool = False
2131+
2132+
@contextlib.contextmanager
2133+
def suppressClearBrailleRegions(self, script: inputCore.ScriptT):
2134+
from globalCommands import commands
2135+
suppress = script in [commands.script_braille_scrollBack, commands.script_braille_scrollForward]
2136+
self._suppressClearBrailleRegions = suppress
2137+
yield
2138+
2139+
def clearBrailleRegions(self):
2140+
if not self._suppressClearBrailleRegions:
2141+
self._showSpeechInBrailleRegions.clear()
2142+
self._suppressClearBrailleRegions = False
2143+
20902144
def getTether(self):
20912145
return self._tether
20922146

@@ -2385,6 +2439,7 @@ def message(self, text):
23852439
not self.enabled
23862440
or config.conf["braille"]["showMessages"] == ShowMessages.DISABLED
23872441
or text is None
2442+
or config.conf["braille"]["mode"] == BrailleMode.SPEECH_OUTPUT.value
23882443
):
23892444
return
23902445
if self.buffer is self.messageBuffer:
@@ -2427,7 +2482,7 @@ def _dismissMessage(self, shouldUpdate: bool = True):
24272482
self.update()
24282483

24292484
def handleGainFocus(self, obj: "NVDAObject", shouldAutoTether: bool = True) -> None:
2430-
if not self.enabled:
2485+
if not self.enabled or config.conf["braille"]["mode"] == BrailleMode.SPEECH_OUTPUT.value:
24312486
return
24322487
if objectBelowLockScreenAndWindowsIsLocked(obj):
24332488
return
@@ -2472,7 +2527,7 @@ def handleCaretMove(
24722527
obj: "NVDAObject",
24732528
shouldAutoTether: bool = True
24742529
) -> None:
2475-
if not self.enabled:
2530+
if not self.enabled or config.conf["braille"]["mode"] == BrailleMode.SPEECH_OUTPUT.value:
24762531
return
24772532
if objectBelowLockScreenAndWindowsIsLocked(obj):
24782533
return
@@ -2582,7 +2637,7 @@ def handleUpdate(self, obj: "NVDAObject") -> None:
25822637
self._regionsPendingUpdate.add(region)
25832638

25842639
def handleReviewMove(self, shouldAutoTether=True):
2585-
if not self.enabled:
2640+
if not self.enabled or config.conf["braille"]["mode"] == BrailleMode.SPEECH_OUTPUT.value:
25862641
return
25872642
reviewPos = api.getReviewPosition()
25882643
if shouldAutoTether:

source/config/configFlags.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ def _displayStringLabels(self):
9797
}
9898

9999

100+
@unique
101+
class BrailleMode(DisplayStringStrEnum):
102+
"""Enumeration containing the possible config values for "Braille mode" option in braille settings.
103+
Use BrailleMode.MEMBER.value to compare with the config;
104+
use BrailleMode.MEMBER.displayString in the UI for a translatable description of this member.
105+
"""
106+
FOLLOW_CURSORS = "followCursors"
107+
SPEECH_OUTPUT = "speechOutput"
108+
109+
@property
110+
def _displayStringLabels(self) -> dict["BrailleMode", str]:
111+
return {
112+
# Translators: The label for a braille mode
113+
BrailleMode.FOLLOW_CURSORS: _("follow cursors"),
114+
# Translators: The label for a braille mode
115+
BrailleMode.SPEECH_OUTPUT: _("display speech output")
116+
}
117+
118+
100119
@unique
101120
class ReportLineIndentation(DisplayStringIntEnum):
102121
"""Enumeration containing the possible config values to report line indent.

source/config/configSpec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
# Braille settings
6464
[braille]
6565
display = string(default=auto)
66+
mode = option("followCursors", "speechOutput", default="followCursors")
6667
translationTable = string(default=en-ueb-g1.ctb)
6768
inputTable = string(default=en-ueb-g1.ctb)
6869
expandAtCursor = boolean(default=true)

source/globalCommands.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from config.configFlags import (
4444
TetherTo,
4545
ShowMessages,
46+
BrailleMode,
4647
)
4748
from config.featureFlag import FeatureFlag
4849
from config.featureFlagEnums import BoolFlag
@@ -122,6 +123,7 @@
122123
# (example: when there is no setting for language).
123124
NO_SETTINGS_MSG = _("No settings")
124125

126+
125127
class GlobalCommands(ScriptableObject):
126128
"""Commands that are available at all times, regardless of the current focus.
127129
"""
@@ -3371,6 +3373,7 @@ def script_toggleBrailleViewer(self, gesture: inputCore.InputGesture):
33713373
category=SCRCAT_BRAILLE,
33723374
gesture="kb:NVDA+control+t"
33733375
)
3376+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
33743377
def script_braille_toggleTether(self, gesture):
33753378
values = [x.value for x in TetherTo]
33763379
index = values.index(config.conf["braille"]["tetherTo"])
@@ -3388,12 +3391,38 @@ def script_braille_toggleTether(self, gesture):
33883391
# (braille can be tethered automatically or to either focus or review position).
33893392
ui.message(_("Braille tethered %s") % TetherTo(newTetherChoice).displayString)
33903393

3394+
@script(
3395+
# Translators: Input help mode message for toggle braille mode command
3396+
description=_("Toggles braille mode"),
3397+
category=SCRCAT_BRAILLE,
3398+
gesture="kb:nvda+alt+t"
3399+
)
3400+
def script_toggleBrailleMode(self, gesture: inputCore.InputGesture):
3401+
curMode = BrailleMode(config.conf["braille"]["mode"])
3402+
modeList = list(BrailleMode)
3403+
index = modeList.index(curMode)
3404+
index = index + 1 if not index == len(modeList) - 1 else 0
3405+
newMode = modeList[index]
3406+
config.conf["braille"]["mode"] = newMode.value
3407+
if braille.handler.buffer == braille.handler.messageBuffer:
3408+
braille.handler._dismissMessage()
3409+
braille.handler.mainBuffer.clear()
3410+
# Translators: The message reported when switching braille modes
3411+
ui.message(_("Braille mode {brailleMode}").format(brailleMode=newMode.displayString))
3412+
if newMode == BrailleMode.SPEECH_OUTPUT:
3413+
return
3414+
if braille.handler.getTether() == TetherTo.REVIEW.value:
3415+
braille.handler.handleReviewMove(shouldAutoTether=braille.handler.shouldAutoTether)
3416+
return
3417+
braille.handler.handleGainFocus(api.getFocusObject())
3418+
33913419
@script(
33923420
# Translators: Input help mode message for cycle through
33933421
# braille move system caret when routing review cursor command.
33943422
description=_("Cycle through the braille move system caret when routing review cursor states"),
33953423
category=SCRCAT_BRAILLE
33963424
)
3425+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
33973426
def script_braille_cycleReviewRoutingMovesSystemCaret(self, gesture: inputCore.InputGesture) -> None:
33983427
# If braille is not tethered to focus, set next state of
33993428
# braille Move system caret when routing review cursor.
@@ -3429,6 +3458,7 @@ def script_braille_cycleReviewRoutingMovesSystemCaret(self, gesture: inputCore.I
34293458
description=_("Toggle the way context information is presented in braille"),
34303459
category=SCRCAT_BRAILLE
34313460
)
3461+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
34323462
def script_braille_toggleFocusContextPresentation(self, gesture):
34333463
values = [x[0] for x in braille.focusContextPresentations]
34343464
labels = [x[1] for x in braille.focusContextPresentations]
@@ -3450,6 +3480,7 @@ def script_braille_toggleFocusContextPresentation(self, gesture):
34503480
description=_("Toggle the braille cursor on and off"),
34513481
category=SCRCAT_BRAILLE
34523482
)
3483+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
34533484
def script_braille_toggleShowCursor(self, gesture):
34543485
if config.conf["braille"]["showCursor"]:
34553486
# Translators: The message announced when toggling the braille cursor.
@@ -3468,6 +3499,7 @@ def script_braille_toggleShowCursor(self, gesture):
34683499
description=_("Cycle through the braille cursor shapes"),
34693500
category=SCRCAT_BRAILLE
34703501
)
3502+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
34713503
def script_braille_cycleCursorShape(self, gesture):
34723504
if not config.conf["braille"]["showCursor"]:
34733505
# Translators: A message reported when changing the braille cursor shape when the braille cursor is turned off.
@@ -3494,6 +3526,7 @@ def script_braille_cycleCursorShape(self, gesture):
34943526
description=_("Cycle through the braille show messages modes"),
34953527
category=SCRCAT_BRAILLE
34963528
)
3529+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
34973530
def script_braille_cycleShowMessages(self, gesture: inputCore.InputGesture) -> None:
34983531
"""Set next state of braille show messages and reports it with ui.message."""
34993532
values = [x.value for x in ShowMessages]
@@ -3511,6 +3544,7 @@ def script_braille_cycleShowMessages(self, gesture: inputCore.InputGesture) -> N
35113544
description=_("Cycle through the braille show selection states"),
35123545
category=SCRCAT_BRAILLE
35133546
)
3547+
@gui.blockAction.when(gui.blockAction.Context.BRAILLE_MODE_SPEECH_OUTPUT)
35143548
def script_braille_cycleShowSelection(self, gesture: inputCore.InputGesture) -> None:
35153549
"""Set next state of braille show selection and reports it with ui.message."""
35163550
featureFlag: FeatureFlag = config.conf["braille"]["showSelection"]

source/gui/blockAction.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# See the file COPYING for more details.
55

66
import config
7+
from config.configFlags import BrailleMode
78
from dataclasses import dataclass
89
from enum import Enum
910
from functools import wraps
@@ -50,6 +51,11 @@ class Context(_Context, Enum):
5051
# version
5152
_("Action unavailable in a temporary version of NVDA"),
5253
)
54+
BRAILLE_MODE_SPEECH_OUTPUT = (
55+
lambda: config.conf["braille"]["mode"] == BrailleMode.SPEECH_OUTPUT.value,
56+
# Translators: Reported when trying to toggle an unsupported setting in speech output mode.
57+
_("Action unavailable while the braille mode is set to speech output"),
58+
)
5359

5460

5561
def when(*contexts: Context):

0 commit comments

Comments
 (0)