Skip to content

Commit a452470

Browse files
authored
Fix backtranslation for uncontracted braille when typing a sequence of characters consisting of multiple patterns (#12742)
when typing multiple characters consisting of multiple patterns, NVDA did not correctly clear the buffer after sending the translation of characters to a text field. NVDA no longer fails to translate braille input when multiple characters are typed that consist of multiple braille patterns (e.g. (1) in UEB)
1 parent c9d6729 commit a452470

4 files changed

Lines changed: 62 additions & 6 deletions

File tree

source/brailleInput.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
UNICODE_BRAILLE_PROTECTED = u"⣿" # All dots down
4545

4646

47+
def _isDebugForBrailleInput():
48+
return config.conf["debugLog"]["brailleInput"]
49+
50+
4751
class BrailleInputHandler(AutoPropertyObject):
4852
"""Handles braille input.
4953
"""
@@ -118,7 +122,21 @@ def _get_currentFocusIsTextObj(self):
118122
useContractedForCurrentFocus: bool
119123

120124
def _get_useContractedForCurrentFocus(self):
121-
return self._table.contracted and self.currentFocusIsTextObj and not self.currentModifiers
125+
return self._table.contracted and not self.inputKeysUsingEmulation
126+
127+
# Provided by auto property: L{_get_inputKeysUsingEmulation}
128+
inputKeysUsingEmulation: bool
129+
130+
def _get_inputKeysUsingEmulation(self):
131+
"""
132+
Returns whether keyboard gesture emulation should be used to input keyboard keys.
133+
If this is C{False}, L{sendChars} is used.
134+
This should only apply when:
135+
* No modifiers are pressed
136+
* We are in a text field
137+
"""
138+
return not self.currentFocusIsTextObj or self.currentModifiers
139+
122140

123141
def _translate(self, endWord: bool) -> bool:
124142
"""Translate buffered braille up to the cursor.
@@ -134,48 +152,64 @@ def _translate(self, endWord: bool) -> bool:
134152
pos = self.untranslatedStart + self.untranslatedCursorPos
135153
data = u"".join([chr(cell | LOUIS_DOTS_IO_START) for cell in self.bufferBraille[:pos]])
136154
mode = louis.dotsIO | louis.noUndefinedDots
137-
if (not self.currentFocusIsTextObj or self.currentModifiers) and self._table.contracted:
155+
if self.inputKeysUsingEmulation and self._table.contracted:
138156
mode |= louis.partialTrans
139157
self.bufferText = louis.backTranslate(
140158
[os.path.join(brailleTables.TABLES_DIR, self._table.fileName),
141159
"braille-patterns.cti"],
142160
data, mode=mode)[0]
143161
newText = self.bufferText[oldTextLen:]
162+
if _isDebugForBrailleInput():
163+
log.debug(f"bufferText after translation = {self.bufferText}, newText = {newText}")
144164
if newText:
145165
# New text was generated by the cells just entered.
146166
if self.useContractedForCurrentFocus or self.currentModifiers:
147167
# For contracted braille, an entire word is sent at once.
148168
# Don't speak characters as this is sent.
149169
# Also, suppress typed characters when emulating a command gesture.
170+
if _isDebugForBrailleInput():
171+
log.debug("Suppressing typed characters for contracted input")
150172
speech._suppressSpeakTypedCharacters(len(newText))
151173
else:
152174
self._uncontSentTime = time.time()
153175
self.untranslatedStart = pos
154176
self.untranslatedCursorPos = 0
155-
if self.currentModifiers or not self.currentFocusIsTextObj:
177+
if _isDebugForBrailleInput():
178+
log.debug(
179+
f"currentModifiers = {self.currentModifiers}, currentFocusIsTextObj = {self.currentFocusIsTextObj}"
180+
)
181+
if self.inputKeysUsingEmulation:
156182
if len(newText)>1:
157183
# Emulation of multiple characters at once is unsupported
158184
# Clear newText, so this function returns C{False} if not at end of word
185+
if _isDebugForBrailleInput():
186+
log.debug(f"Clearing newText, was {newText}")
159187
newText = u""
160188
else:
161189
self.emulateKey(newText)
162190
else:
163191
self.sendChars(newText)
164192

165-
if endWord or (newText and (not self.currentFocusIsTextObj or self.currentModifiers)):
193+
if endWord or (newText and (self.currentModifiers or not self.useContractedForCurrentFocus)):
166194
# We only need to buffer one word.
167195
# Clear the previous word (anything before the cursor) from the buffer.
196+
if _isDebugForBrailleInput():
197+
log.debug(f"Clearing previous word from buffer")
168198
del self.bufferBraille[:pos]
169-
self.bufferText = u""
199+
self.bufferText = ""
170200
self.cellsWithText.clear()
171201
self.currentModifiers.clear()
172202
self.untranslatedStart = 0
173203
self.untranslatedCursorPos = 0
174204

175205
if newText or endWord:
176206
self._updateUntranslated()
207+
if _isDebugForBrailleInput():
208+
log.debug("Translate returning True")
177209
return True
178210

211+
if _isDebugForBrailleInput():
212+
log.debug("Translate returning False")
179213
return False
180214

181215
def _translateForReportContractedCell(self, pos):
@@ -237,8 +271,12 @@ def _reportUntranslated(self, pos):
237271
def input(self, dots: int):
238272
"""Handle one cell of braille input.
239273
"""
274+
if _isDebugForBrailleInput():
275+
log.debug(f"Braille input received: 0X{dots:02x}")
240276
# Insert the newly entered cell into the buffer at the cursor position.
241277
pos = self.untranslatedStart + self.untranslatedCursorPos
278+
if _isDebugForBrailleInput():
279+
log.debug(f"Inserting 0X{dots:02x} at position {pos} in bufferBraille")
242280
self.bufferBraille.insert(pos, dots)
243281
self.untranslatedCursorPos += 1
244282
# Space ends the word.
@@ -249,10 +287,17 @@ def input(self, dots: int):
249287
# This is because later cells can change characters produced by previous cells.
250288
# For example, in English grade 2, "tg" produces just "tg",
251289
# but "tgr" produces "together".
290+
if _isDebugForBrailleInput():
291+
log.debug(
292+
f"useContractedForCurrentFocus = {self.useContractedForCurrentFocus}, "
293+
f"input is end of word = {endWord}"
294+
)
252295
if not self.useContractedForCurrentFocus or endWord:
253296
if self._translate(endWord):
254297
if not endWord:
255298
self.cellsWithText.add(pos)
299+
if _isDebugForBrailleInput():
300+
log.debug(f"Added {pos} to cellsWithText, now {self.cellsWithText}")
256301
elif self.bufferText and not self.useContractedForCurrentFocus:
257302
# Translators: Reported when translation didn't succeed due to unsupported input.
258303
speech.speakMessage(_("Unsupported input"))
@@ -374,6 +419,8 @@ def emulateKey(self, key: str, withModifiers: bool = True):
374419
@param withModifiers: Whether this key emulation should include the modifiers that are held virtually.
375420
Note that this method does not take care of clearing L{self.currentModifiers}.
376421
"""
422+
if _isDebugForBrailleInput():
423+
log.debug(f"Emulating {key}")
377424
if withModifiers:
378425
# The emulated key should be the last item in the identifier string.
379426
keys = list(self.currentModifiers)
@@ -384,13 +431,19 @@ def emulateKey(self, key: str, withModifiers: bool = True):
384431
try:
385432
inputCore.manager.emulateGesture(keyboardHandler.KeyboardInputGesture.fromName(gesture))
386433
except:
387-
log.debugWarning("Unable to emulate %r, falling back to sending unicode characters"%gesture, exc_info=True)
434+
if _isDebugForBrailleInput():
435+
log.debugWarning(
436+
f"Unable to emulate {gesture}, falling back to sending unicode characters",
437+
exc_info=True
438+
)
388439
self.sendChars(key)
389440

390441
def sendChars(self, chars: str):
391442
"""Sends the provided unicode characters to the system.
392443
@param chars: The characters to send to the system.
393444
"""
445+
if _isDebugForBrailleInput():
446+
log.debug(f"Sending characters: {chars}")
394447
inputs = []
395448
chars = ''.join(
396449
ch if ord(ch) <= 0xffff else ''.join(

source/config/configSpec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
speechManager = boolean(default=false)
263263
synthDriver = boolean(default=false)
264264
nvwave = boolean(default=false)
265+
brailleInput = boolean(default=false)
265266
266267
[uwpOcr]
267268
language = string(default="")

source/gui/settingsDialogs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,6 +2818,7 @@ def __init__(self, parent):
28182818
"speechManager",
28192819
"synthDriver",
28202820
"nvwave",
2821+
"brailleInput",
28212822
]
28222823
# Translators: This is the label for a list in the
28232824
# Advanced settings panel

user_docs/en/changes.t2t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ What's New in NVDA
2323

2424
== Bug Fixes ==
2525
- Tracking keyboard modifiers (such as Control, or Insert) is more robust when watchdog is recovering. (#12609)
26+
- NVDA no longer fails to translate braille input when multiple characters are typed that consist of multiple braille patterns (e.g. (1) in UEB). (#12667)
2627
- It is once again possible to check for NVDA updates on certain systems; e.g. clean Windows installs. (#12729)
2728
- NVDA correctly announces blank table cells in Microsoft Word when using UI automation. (#11043)
2829
-

0 commit comments

Comments
 (0)