Skip to content

Commit f7535cc

Browse files
authored
Merge abea825 into d1ce3bc
2 parents d1ce3bc + abea825 commit f7535cc

3 files changed

Lines changed: 107 additions & 7 deletions

File tree

source/globalCommands.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,12 +2577,13 @@ def script_reportStatusLine(self, gesture):
25772577
def script_reportFocusObjectAccelerator(self, gesture: inputCore.InputGesture) -> None:
25782578
obj = api.getFocusObject()
25792579
if obj.keyboardShortcut:
2580-
res = obj.keyboardShortcut
2580+
shortcut = obj.keyboardShortcut
2581+
speech.speakKeyboardShortcuts(shortcut)
2582+
braille.handler.message(shortcut)
25812583
else:
25822584
# Translators: reported when a user requests the accelerator key
25832585
# of the currently focused object, but there is none set.
2584-
res = _("No shortcut key")
2585-
ui.message(res)
2586+
ui.message(_("No shortcut key"))
25862587

25872588
@script(
25882589
# Translators: Input help mode message for toggle mouse tracking command.

source/speech/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# A part of NonVisual Desktop Access (NVDA)
22
# This file is covered by the GNU General Public License.
33
# See the file COPYING for more details.
4-
# Copyright (C) 2006-2021 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Babbage B.V., Bill Dengler,
5-
# Julien Cochuyt
4+
# Copyright (C) 2006-2023 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Babbage B.V., Bill Dengler,
5+
# Julien Cochuyt, Cyrille Bougot
66

77
from .speech import (
88
_extendSpeechSequence_addMathForTextInfo,
@@ -46,6 +46,7 @@
4646
RE_INDENTATION_SPLIT,
4747
setSpeechMode,
4848
speak,
49+
speakKeyboardShortcuts,
4950
speakMessage,
5051
speakObject,
5152
speakObjectProperties,

source/speech/speech.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,8 +1725,7 @@ def getPropertiesSpeech( # noqa: C901
17251725
textList.append(description)
17261726
# sometimes keyboardShortcut key is present but value is None
17271727
keyboardShortcut: Optional[str] = propertyValues.get('keyboardShortcut')
1728-
if keyboardShortcut:
1729-
textList.append(keyboardShortcut)
1728+
textList.extend(getKeyboardShortcutsSpeech(keyboardShortcut))
17301729
if includeTableCellCoords and cellCoordsText:
17311730
textList.append(cellCoordsText)
17321731
if cellCoordsText or rowNumber or columnNumber:
@@ -1861,6 +1860,105 @@ def getPropertiesSpeech( # noqa: C901
18611860
return textList
18621861

18631862

1863+
def speakKeyboardShortcuts(keyboardShortcutsStr: Optional[str]) -> SpeechSequence:
1864+
speak(getKeyboardShortcutsSpeech(keyboardShortcutsStr))
1865+
1866+
1867+
def getKeyboardShortcutsSpeech(keyboardShortcutsStr: Optional[str]) -> SpeechSequence:
1868+
"""Gets the speech sequence for a shortcuts string containing one or more shortcuts.
1869+
@param keyboardShortcutsStr: the shortcuts string.
1870+
"""
1871+
1872+
SHORTCUT_KEY_LIST_SEPARATOR = ' '
1873+
seq = []
1874+
if not keyboardShortcutsStr:
1875+
return seq
1876+
try:
1877+
for shortcutKeyStr in keyboardShortcutsStr.split(SHORTCUT_KEY_LIST_SEPARATOR):
1878+
seq.extend(_getKeyboardShortcutSpeech(shortcutKeyStr))
1879+
seq.append(SHORTCUT_KEY_LIST_SEPARATOR)
1880+
seq.pop() # Remove last SHORTCUT_KEY_LIST_SEPARATOR in the sequence
1881+
except Exception:
1882+
log.warning(
1883+
f'Error parsing keyboard shortcut "{keyboardShortcutsStr}", reporting the string as a fallback.'
1884+
)
1885+
return [keyboardShortcutsStr]
1886+
1887+
# Merge consecutive strings in the list when possible
1888+
seqOut = []
1889+
for item in seq:
1890+
if len(seqOut) > 0 and isinstance(seqOut[-1], str) and isinstance(item, str):
1891+
seqOut[-1] = seqOut[-1] + item
1892+
else:
1893+
seqOut.append(item)
1894+
1895+
return seqOut
1896+
1897+
1898+
def _getKeyboardShortcutSpeech(keyboardShortcut: str) -> SpeechSequence:
1899+
"""Gets the speech sequence for a single shortcut string.
1900+
@param keyboardShortcutStr: the shortcuts string.
1901+
"""
1902+
1903+
keyList, separators = _splitShortcut(keyboardShortcut)
1904+
seq = []
1905+
for key, sep in zip(keyList[:-1], separators):
1906+
seq.extend(_getKeySpeech(key))
1907+
seq.append(sep)
1908+
seq.extend(_getKeySpeech(keyList[-1]))
1909+
return seq
1910+
1911+
1912+
def _getKeySpeech(key: str) -> SpeechSequence:
1913+
"""Gets the speech sequence for a string describing a key.
1914+
@param keyStr: the key string.
1915+
"""
1916+
if len(key) > 1:
1917+
return [key]
1918+
locale = getCurrentLanguage()
1919+
keySymbol = characterProcessing.processSpeechSymbol(locale, key)
1920+
if keySymbol != key:
1921+
return [keySymbol]
1922+
return [
1923+
CharacterModeCommand(True),
1924+
key,
1925+
CharacterModeCommand(False),
1926+
]
1927+
1928+
1929+
def _splitShortcut(shortcut: str) -> SpeechSequence:
1930+
if ', ' in shortcut:
1931+
return _splitSequentialShortcut(shortcut)
1932+
if ' + ' in shortcut:
1933+
separator = ' + '
1934+
elif '+' in shortcut:
1935+
separator = '+'
1936+
else:
1937+
separator = None
1938+
if separator:
1939+
keyList = shortcut.split(separator)
1940+
else:
1941+
keyList = [shortcut]
1942+
separators = [separator] * (len(keyList) - 1)
1943+
return keyList, separators
1944+
1945+
1946+
def _splitSequentialShortcut(shortcut: str) -> SpeechSequence:
1947+
keys = []
1948+
separators = []
1949+
RE_SEQ_SHORTCUT_SPLITTING = re.compile(r'^(?P<key>[^, ]+)(?P<sep> |, )(?P<tail>.+)')
1950+
tail = shortcut
1951+
while len(tail) > 0:
1952+
m = RE_SEQ_SHORTCUT_SPLITTING.match(tail)
1953+
if not m:
1954+
keys.append(tail)
1955+
return keys, separators
1956+
keys.append(m['key'])
1957+
separators.append(m['sep'])
1958+
tail = m['tail']
1959+
raise RuntimeError(f'Wrong sequential shortcut string format: {shortcut}')
1960+
1961+
18641962
def _shouldSpeakContentFirst(
18651963
reason: OutputReason,
18661964
role: int,

0 commit comments

Comments
 (0)