Skip to content

Commit

Permalink
Merge branch 'master' into appveyor-tagged
Browse files Browse the repository at this point in the history
  • Loading branch information
seanbudd committed Jul 9, 2021
2 parents 916b30a + cbf7407 commit ebd6351
Show file tree
Hide file tree
Showing 17 changed files with 424 additions and 75 deletions.
Expand Up @@ -119,19 +119,17 @@ def event_UIA_elementSelected(self, obj, nextHandler):
# However, in recent builds, name change event is also fired.
# For consistent experience, report the new category first by traversing through controls.
# #8189: do not announce candidates list itself (not items), as this is repeated each time candidate items are selected.
if obj.UIAElement.cachedAutomationID == "CandidateList": return
if obj.UIAElement.cachedAutomationID == "CandidateList":
return
speech.cancelSpeech()
# Sometimes, due to bad tree traversal or wrong item getting selected, something other than the selected item sees this event.
# Sometimes clipboard candidates list gets selected, so ask NvDA to descend one more level.
if obj.UIAElement.cachedAutomationID == "TEMPLATE_PART_ClipboardItemsList":
obj = obj.firstChild
# In build 18262, emoji panel may open to People group and skin tone modifier or the list housing them gets selected.
elif obj.UIAElement.cachedAutomationID == "SkinTonePanelModifier_ListView":
obj = obj.next
elif obj.parent.UIAElement.cachedAutomationID == "SkinTonePanelModifier_ListView":
# In build 18262, emoji panel may open to People group and skin tone modifier gets selected.
if obj.parent.UIAAutomationId == "SkinTonePanelModifier_ListView":
# But this will point to nothing if emoji search results are not people.
if obj.parent.next is not None: obj = obj.parent.next
else: obj = obj.parent.parent.firstChild
if obj.parent.next is not None:
obj = obj.parent.next
else:
obj = obj.parent.parent.firstChild
candidate = obj
if obj and obj.UIAElement.cachedClassName == "ListViewItem" and obj.parent and isinstance(obj.parent, UIA) and obj.parent.UIAElement.cachedAutomationID != "TEMPLATE_PART_ClipboardItemsList":
# The difference between emoji panel and suggestions list is absence of categories/emoji separation.
Expand All @@ -156,6 +154,12 @@ def event_UIA_elementSelected(self, obj, nextHandler):
ui.message(_("No emoji"))
nextHandler()

# Emoji panel for build 16299 and 17134.
_classicEmojiPanelAutomationIds = (
"TEMPLATE_PART_ExpressiveInputFullViewFuntionBarItemControl",
"TEMPLATE_PART_ExpressiveInputFullViewFuntionBarCloseButton"
)

def event_UIA_window_windowOpen(self, obj, nextHandler):
firstChild = obj.firstChild
# Handle Ime Candidate UI being shown
Expand All @@ -168,43 +172,58 @@ def event_UIA_window_windowOpen(self, obj, nextHandler):
# However, in build 17666 and later, child count is the same for both emoji panel and hardware keyboard candidates list.
# Thankfully first child automation ID's are different for each modern input technology.
# However this event is raised when the input panel closes.
if obj.firstChild is None:
if firstChild is None:
return
# #9104: different aspects of modern input panel are represented by automation iD's.
childAutomationID = obj.firstChild.UIAElement.cachedAutomationID
# #9104: different aspects of modern input panel are represented by Automation Id's.
childAutomationId = firstChild.UIAAutomationId
# Emoji panel for 1709 (build 16299) and 1803 (17134).
emojiPanelInitial = winVersion.WIN10_1709
# This event is properly raised in build 17134.
emojiPanelWindowOpenEvent = winVersion.WIN10_1803
if (
emojiPanelInitial <= winVersion.getWinVer() <= emojiPanelWindowOpenEvent
and childAutomationID in (
"TEMPLATE_PART_ExpressiveInputFullViewFuntionBarItemControl",
"TEMPLATE_PART_ExpressiveInputFullViewFuntionBarCloseButton"
)
childAutomationId in self._classicEmojiPanelAutomationIds
and emojiPanelInitial <= winVersion.getWinVer() <= emojiPanelWindowOpenEvent
):
self.event_UIA_elementSelected(obj.lastChild.firstChild, nextHandler)
eventHandler.queueEvent("UIA_elementSelected", obj.lastChild.firstChild)
# Handle hardware keyboard suggestions.
# Treat it the same as CJK composition list - don't announce this if candidate announcement setting is off.
elif childAutomationID == "CandidateWindowControl" and config.conf["inputComposition"]["autoReportAllCandidates"]:
elif (
childAutomationId == "CandidateWindowControl"
and config.conf["inputComposition"]["autoReportAllCandidates"]
):
try:
self.event_UIA_elementSelected(obj.firstChild.firstChild.firstChild, nextHandler)
eventHandler.queueEvent("UIA_elementSelected", firstChild.firstChild.firstChild)
except AttributeError:
# Because this is dictation window.
pass
# Emoji panel in build 17666 and later (unless this changes).
elif childAutomationID == "TEMPLATE_PART_ExpressionGroupedFullView":
# Emoji panel in Version 1809 (specifically, build 17666) and later.
elif childAutomationId == "TEMPLATE_PART_ExpressionGroupedFullView":
self._emojiPanelJustOpened = True
try:
self.event_UIA_elementSelected(obj.firstChild.children[-2].firstChild.firstChild, nextHandler)
except AttributeError:
# In build 18272's emoji panel, emoji list becomes empty in some situations.
pass
# #10377: on some systems (particularly non-English builds of Version 1903 and later),
# there is something else besides grouping controls, so another child control must be used.
emojisList = firstChild.children[-2]
if emojisList.UIAAutomationId != "TEMPLATE_PART_Items_GridView":
emojisList = emojisList.previous
if emojisList.firstChild and emojisList.firstChild.firstChild:
emojiItem = emojisList.firstChild.firstChild
# Avoid announcing skin tone modifiers if possible.
# For people emoji, the first emoji is actually next to skin tone modifiers list.
if emojiItem.UIAAutomationId == "SkinTonePanelModifier_ListView" and emojiItem.next:
emojiItem = emojiItem.next
eventHandler.queueEvent("UIA_elementSelected", emojiItem)
# Clipboard history.
# Move to clipboard list so element selected event can pick it up.
# #9103: if clipboard is empty, a status message is displayed instead, and luckily it is located where clipboard data items can be found.
elif childAutomationID == "TEMPLATE_PART_ClipboardTitleBar":
self.event_UIA_elementSelected(obj.children[-2], nextHandler)
elif childAutomationId == "TEMPLATE_PART_ClipboardTitleBar":
# Under some cases, clipboard tip text isn't shown on screen,
# causing clipboard history title to be announced instead of most recently copied item.
clipboardHistoryItem = obj.children[-2]
if clipboardHistoryItem.UIAAutomationId == childAutomationId:
clipboardHistoryItem = clipboardHistoryItem.next
# Make sure to move to actual clipboard history item if available.
if clipboardHistoryItem.firstChild is not None:
clipboardHistoryItem = clipboardHistoryItem.firstChild
eventHandler.queueEvent("UIA_elementSelected", clipboardHistoryItem)
nextHandler()

# Argh, name change event is fired right after emoji panel opens in build 17666 and later.
Expand Down
72 changes: 43 additions & 29 deletions source/characterProcessing.py
@@ -1,10 +1,11 @@
#characterProcessing.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2010-2018 NV Access Limited, World Light Information Limited, Hong Kong Blind Union, Babbage B.V.
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import time
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2010-2021 NV Access Limited, World Light Information Limited,
# Hong Kong Blind Union, Babbage B.V., Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

from versionInfo import version_year
from enum import IntEnum
import os
import codecs
import collections
Expand Down Expand Up @@ -124,26 +125,40 @@ def getCharacterDescription(locale,character):
desc=getCharacterDescription('en',character)
return desc


# Speech symbol levels
SYMLVL_NONE = 0
SYMLVL_SOME = 100
SYMLVL_MOST = 200
SYMLVL_ALL = 300
SYMLVL_CHAR = 1000
class SYMLVL(IntEnum):
NONE = 0
SOME = 100
MOST = 200
ALL = 300
CHAR = 1000
UNCHANGED = -1


# The following SYMLVL_ constants are deprecated in #11856 but remain to maintain backwards compatibility.
# Remove these in 2022.1 and replace instances using them with the SYMLVL IntEnum.
if version_year < 2022:
SYMLVL_NONE = SYMLVL.NONE
SYMLVL_SOME = SYMLVL.SOME
SYMLVL_MOST = SYMLVL.MOST
SYMLVL_ALL = SYMLVL.ALL
SYMLVL_CHAR = SYMLVL.CHAR

SPEECH_SYMBOL_LEVEL_LABELS = {
# Translators: The level at which the given symbol will be spoken.
SYMLVL_NONE: pgettext("symbolLevel", "none"),
SYMLVL.NONE: pgettext("symbolLevel", "none"),
# Translators: The level at which the given symbol will be spoken.
SYMLVL_SOME: pgettext("symbolLevel", "some"),
SYMLVL.SOME: pgettext("symbolLevel", "some"),
# Translators: The level at which the given symbol will be spoken.
SYMLVL_MOST: pgettext("symbolLevel", "most"),
SYMLVL.MOST: pgettext("symbolLevel", "most"),
# Translators: The level at which the given symbol will be spoken.
SYMLVL_ALL: pgettext("symbolLevel", "all"),
SYMLVL.ALL: pgettext("symbolLevel", "all"),
# Translators: The level at which the given symbol will be spoken.
SYMLVL_CHAR: pgettext("symbolLevel", "character"),
SYMLVL.CHAR: pgettext("symbolLevel", "character"),
}
CONFIGURABLE_SPEECH_SYMBOL_LEVELS = (SYMLVL_NONE, SYMLVL_SOME, SYMLVL_MOST, SYMLVL_ALL)
SPEECH_SYMBOL_LEVELS = CONFIGURABLE_SPEECH_SYMBOL_LEVELS + (SYMLVL_CHAR,)
CONFIGURABLE_SPEECH_SYMBOL_LEVELS = (SYMLVL.NONE, SYMLVL.SOME, SYMLVL.MOST, SYMLVL.ALL)
SPEECH_SYMBOL_LEVELS = CONFIGURABLE_SPEECH_SYMBOL_LEVELS + (SYMLVL.CHAR,)

# Speech symbol preserve modes
SYMPRES_NEVER = 0
Expand Down Expand Up @@ -254,11 +269,11 @@ def _loadSymbolField(self, input, inputMap=None):
}
IDENTIFIER_ESCAPES_OUTPUT = {v: k for k, v in IDENTIFIER_ESCAPES_INPUT.items()}
LEVEL_INPUT = {
"none": SYMLVL_NONE,
"some": SYMLVL_SOME,
"most": SYMLVL_MOST,
"all": SYMLVL_ALL,
"char": SYMLVL_CHAR,
"none": SYMLVL.NONE,
"some": SYMLVL.SOME,
"most": SYMLVL.MOST,
"all": SYMLVL.ALL,
"char": SYMLVL.CHAR,
}
LEVEL_OUTPUT = {v:k for k, v in LEVEL_INPUT.items()}
PRESERVE_INPUT = {
Expand Down Expand Up @@ -484,7 +499,7 @@ def __init__(self, locale):
pass
continue
if symbol.level is None:
symbol.level = SYMLVL_ALL
symbol.level = SYMLVL.ALL
if symbol.preserve is None:
symbol.preserve = SYMPRES_NEVER
if symbol.displayName is None:
Expand Down Expand Up @@ -660,13 +675,12 @@ def isBuiltin(self, symbolIdentifier):

_localeSpeechSymbolProcessors = LocaleDataMap(SpeechSymbolProcessor)

def processSpeechSymbols(locale, text, level):

def processSpeechSymbols(locale: str, text: str, level: SYMLVL):
"""Process some text, converting symbols according to desired pronunciation.
@param locale: The locale of the text.
@type locale: str
@param text: The text to process.
@type text: str
@param level: The symbol level to use; one of the SYMLVL_* constants.
@param level: The symbol level to use.
"""
try:
ss = _localeSpeechSymbolProcessors.fetchLocaleData(locale)
Expand Down
4 changes: 3 additions & 1 deletion source/config/configSpec.py
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*-
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2020 NV Access Limited, Babbage B.V., Davy Kager, Bill Dengler
# Copyright (C) 2006-2020 NV Access Limited, Babbage B.V., Davy Kager, Bill Dengler, Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand Down Expand Up @@ -31,6 +31,8 @@
# The synthesizer to use
synth = string(default=auto)
symbolLevel = integer(default=100)
# Speak all symbols when reviewing by word, uses editor specific implementation if false
symbolLevelWordAll = boolean(default=true)
trustVoiceLanguage = boolean(default=true)
includeCLDR = boolean(default=True)
beepSpeechModePitch = integer(default=10000,min=50,max=11025)
Expand Down
1 change: 1 addition & 0 deletions source/core.py
Expand Up @@ -757,6 +757,7 @@ def _doPostNvdaStartupAction():
"NVDA not already exiting, hit catch-all exit trigger."
" This likely indicates NVDA is exiting due to WM_QUIT."
)
queueHandler.pumpAll()
_terminate(gui)
config.saveOnExit()

Expand Down
21 changes: 21 additions & 0 deletions source/globalCommands.py
Expand Up @@ -895,6 +895,27 @@ def script_cycleSpeechSymbolLevel(self,gesture):
# %s will be replaced with the symbol level; e.g. none, some, most and all.
ui.message(_("Symbol level %s") % name)

@script(
# Translators: Input help mode message for a command.
description=_("Toggle the announcement of all punctuation and symbols when reviewing by word"),
category=SCRCAT_SPEECH
)
def script_toggleSpeechSymbolLevelWordAll(self, gesture):
symbolLevelWordAll = config.conf["speech"]["symbolLevelWordAll"]
if symbolLevelWordAll:
# Translators: Used to report the new state of a setting which is toggled via a command.
reportedState = pgettext("command toggle", "off")
else:
# Translators: Used to report the new state of a setting which is toggled via a command.
reportedState = pgettext("command toggle", "on")
config.conf["speech"]["symbolLevelWordAll"] = not symbolLevelWordAll
ui.message(
# Translators: Reported when toggling a speech setting
_("Speak all punctuation and symbols when reviewing by word: {state}").format(
state=reportedState
)
)

@script(
# Translators: Input help mode message for move mouse to navigator object command.
description=_("Moves the mouse pointer to the current navigator object"),
Expand Down
20 changes: 17 additions & 3 deletions source/gui/settingsDialogs.py
Expand Up @@ -2,7 +2,8 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2020 NV Access Limited, Peter Vágner, Aleksey Sadovoy,
# Rui Batista, Joseph Lee, Heiko Folkerts, Zahari Yurukov, Leonard de Ruijter,
# Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Bill Dengler, Thomas Stivers
# Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Bill Dengler, Thomas Stivers,
# Julien Cochuyt
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
import logging
Expand Down Expand Up @@ -1492,6 +1493,16 @@ def makeSettings(self, settingsSizer):
characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS.index(curLevel)
)

self.symbolLevelWordAll = settingsSizerHelper.addItem(
wx.CheckBox(
self,
# Translators: The label for a setting in the Speech category
label=_("Speak all punctuation and symbols when reviewing by &word"),
)
)
self.bindHelpEvent("SpeechSettingsSymbolLevelWord", self.symbolLevelWordAll)
self.symbolLevelWordAll.SetValue(config.conf["speech"]["symbolLevelWordAll"])

# Translators: This is the label for a checkbox in the
# voice settings panel (if checked, text will be read using the voice for the language of the text).
trustVoiceLanguageText = _("Trust voice's language when processing characters and symbols")
Expand Down Expand Up @@ -1579,8 +1590,11 @@ def onSave(self):

config.conf["speech"]["autoLanguageSwitching"] = self.autoLanguageSwitchingCheckbox.IsChecked()
config.conf["speech"]["autoDialectSwitching"] = self.autoDialectSwitchingCheckbox.IsChecked()
config.conf["speech"]["symbolLevel"]=characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[self.symbolLevelList.GetSelection()]
config.conf["speech"]["trustVoiceLanguage"]=self.trustVoiceLanguageCheckbox.IsChecked()
config.conf["speech"]["symbolLevel"] = characterProcessing.CONFIGURABLE_SPEECH_SYMBOL_LEVELS[
self.symbolLevelList.GetSelection()
]
config.conf["speech"]["symbolLevelWordAll"] = self.symbolLevelWordAll.IsChecked()
config.conf["speech"]["trustVoiceLanguage"] = self.trustVoiceLanguageCheckbox.IsChecked()
currentIncludeCLDR = config.conf["speech"]["includeCLDR"]
config.conf["speech"]["includeCLDR"] = newIncludeCldr = self.includeCLDRCheckbox.IsChecked()
if currentIncludeCLDR is not newIncludeCldr:
Expand Down
10 changes: 5 additions & 5 deletions source/locale/bn/characterDescriptions.dic
Expand Up @@ -16,12 +16,12 @@
খ খড়ম
গ গরু
ঘ ঘর
উঁয়ো
চ চড়াই
ছ ছাতা
জ জাল
ঝ ঝড়
ইঁয়ো
ট টুপি
ঠ ঠিকানা
ড ডালিম
Expand All @@ -40,12 +40,12 @@
য় অন্ত্যস্থ য
য যাতনা
র রাত্রি
ড য় শুন্য ড়
ঢ য় শুন্য ঢ়
ডয় শুন্য ড়
ঢয় শুন্য ঢ়
ল ললাট
শ শরীর
স সকাল
ষন্ড
ষাঁড়
হ হলুদ
ং অনুস্বর
ঁ চন্দ্রবিন্দু
Expand Down
10 changes: 8 additions & 2 deletions source/speech/speech.py
Expand Up @@ -1126,8 +1126,14 @@ def speakTextInfo(
)

speechGen = GeneratorWithReturn(speechGen)
symbolLevel: Optional[characterProcessing.SYMLVL] = None
if unit == textInfos.UNIT_CHARACTER:
symbolLevel = characterProcessing.SYMLVL.ALL
elif unit == textInfos.UNIT_WORD:
if config.conf["speech"]["symbolLevelWordAll"]:
symbolLevel = characterProcessing.SYMLVL.ALL
for seq in speechGen:
speak(seq, priority=priority)
speak(seq, symbolLevel=symbolLevel, priority=priority)
return speechGen.returnValue


Expand Down Expand Up @@ -1323,7 +1329,7 @@ def isControlEndFieldCommand(x):
if onlyInitialFields or (
isWordOrCharUnit
and len(textWithFields) > 0
and len(textWithFields[0].strip() if not textWithFields[0].isspace() else textWithFields[0]) == 1
and len(textWithFields[0]) == 1
and all(isControlEndFieldCommand(x) for x in itertools.islice(textWithFields, 1, None))
):
if not onlyCache:
Expand Down

0 comments on commit ebd6351

Please sign in to comment.