Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speech commands use sequences #10371

Merged
merged 21 commits into from Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
81e3353
Speech functions now return a SpeechSequence
LeonarddeR Aug 14, 2019
dcb6145
Rename getSpeechTextForProperties to getPropertiesSpeech
feerrenrut Oct 10, 2019
cfbf173
Unify line endings (use Windows line endings)
feerrenrut Oct 10, 2019
86c7df9
Passing None to LangChangeCommand is accepted
feerrenrut Oct 14, 2019
99086de
Add type hints to help track down issues with sequence building
feerrenrut Oct 14, 2019
ddd1ecb
Add speechSequence checking
feerrenrut Oct 14, 2019
d349bfc
Fix several found issues with building speech sequences.
feerrenrut Oct 14, 2019
49f282f
Fix lint errors
feerrenrut Oct 15, 2019
eb67825
Other review actions.
feerrenrut Oct 15, 2019
7a76f02
Remove separator argument from getFormatFieldSpeech method
feerrenrut Oct 15, 2019
de1343e
Include padding added to strings for speech in test
feerrenrut Oct 15, 2019
e1707de
Fix speak role too often in browse mode
feerrenrut Nov 4, 2019
4f90c99
Merge remote-tracking branch 'origin/master' into i10098-speechComman…
feerrenrut Nov 4, 2019
9bbc10b
Fix up lint errors.
feerrenrut Nov 4, 2019
e96081c
Fix: remove debug logging
feerrenrut Nov 5, 2019
6d8008b
Rename "speechSequences" logging category to "speech"
feerrenrut Nov 6, 2019
5613a28
Fix unnecessary newlines between speech items in Speech viewer
feerrenrut Nov 6, 2019
8d5ca11
Fix wrong "types" import.
feerrenrut Nov 6, 2019
cf8ff2f
Fix type annotations for getIndentationSpeech
feerrenrut Nov 6, 2019
a0e3ffa
Remove unnecessary logBadSequenceTypes wrapper
feerrenrut Nov 6, 2019
adc11f4
Fix missing EOF newline
feerrenrut Nov 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 44 additions & 37 deletions source/NVDAObjects/window/excel.py
Expand Up @@ -227,52 +227,48 @@ def report(self,readUnit=None):

class ExcelChartQuickNavItem(ExcelQuickNavItem):

def __init__( self , nodeType , document , chartObject , chartCollection ):
def __init__(self, nodeType, document, chartObject, chartCollection):
self.chartIndex = chartObject.Index
if chartObject.Chart.HasTitle:

self.label = chartObject.Chart.ChartTitle.Text + " " + chartObject.TopLeftCell.address(False,False,1,False) + "-" + chartObject.BottomRightCell.address(False,False,1,False)

else:

self.label = chartObject.Name + " " + chartObject.TopLeftCell.address(False,False,1,False) + "-" + chartObject.BottomRightCell.address(False,False,1,False)

super( ExcelChartQuickNavItem ,self).__init__( nodeType , document , chartObject , chartCollection )
topLeftAddress = chartObject.TopLeftCell.address(False, False, 1, False)
bottomRightAddress = chartObject.BottomRightCell.address(False, False, 1, False)
if chartObject.Chart.HasTitle:
nameText = chartObject.Chart.ChartTitle.Text
else:
nameText = chartObject.Name
self.label = f"{nameText} {topLeftAddress}-{bottomRightAddress}"
super(ExcelChartQuickNavItem, self).__init__(
nodeType,
document,
chartObject,
chartCollection
)

def __lt__(self,other):
return self.chartIndex < other.chartIndex

def moveTo(self):
try:
self.excelItemObject.Activate()

# After activate(), though the chart object is selected,

# pressing arrow keys moves the object, rather than

# let use go inside for sub-objects. Somehow
# calling an COM function on a different object fixes that !

log.debugWarning( self.excelItemCollection.Count )

except(COMError):

pass
self.excelItemObject.Activate()
# After activate(), though the chart object is selected,
# pressing arrow keys moves the object, rather than
# let us go inside for sub-objects. Somehow
# calling a COM function on a different object fixes that!
log.debugWarning(self.excelItemCollection.Count)
except COMError:
pass
focus=api.getDesktopObject().objectWithFocus()
if not focus or not isinstance(focus,ExcelBase):
return
# Charts are not yet automatically detected with objectFromFocus, so therefore use selection
sel=focus._getSelection()
if not sel:
return
eventHandler.queueEvent("gainFocus",sel)
eventHandler.queueEvent("gainFocus", sel)


@property
def isAfterSelection(self):
activeCell = self.document.Application.ActiveCell
#log.debugWarning("active row: {} active column: {} current row: {} current column: {}".format ( activeCell.row , activeCell.column , self.excelCommentObject.row , self.excelCommentObject.column ) )

if self.excelItemObject.TopLeftCell.row == activeCell.row:
if self.excelItemObject.TopLeftCell.column > activeCell.column:
return False
Expand All @@ -289,13 +285,20 @@ def __lt__(self,other):
return self.excelItemObject.row < other.excelItemObject.row

def moveTo(self):
self.excelItemObject.Activate()
self.excelItemObject.Activate()
eventHandler.queueEvent("gainFocus",api.getDesktopObject().objectWithFocus())

@property
def isAfterSelection(self):
activeCell = self.document.Application.ActiveCell
log.debugWarning("active row: {} active column: {} current row: {} current column: {}".format ( activeCell.row , activeCell.column , self.excelItemObject.row , self.excelItemObject.column ) )
log.debugWarning(
"active row: {} active column: {} current row: {} current column: {}".format(
activeCell.row,
activeCell.column,
self.excelItemObject.row,
self.excelItemObject.column
)
)

if self.excelItemObject.row == activeCell.row:
if self.excelItemObject.column > activeCell.column:
Expand Down Expand Up @@ -374,7 +377,7 @@ class CommentExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
def collectionFromWorksheet( self , worksheetObject ):
try:
return worksheetObject.cells.SpecialCells( xlCellTypeComments )
except(COMError):
except(COMError):
return None

def filter(self,item):
Expand All @@ -385,7 +388,7 @@ class FormulaExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
def collectionFromWorksheet( self , worksheetObject ):
try:
return worksheetObject.cells.SpecialCells( xlCellTypeFormulas )
except(COMError):
except(COMError):

return None

Expand Down Expand Up @@ -420,7 +423,7 @@ def isAfterSelection(self):
return False
else:
return True

class SheetsExcelCollectionQuicknavIterator(ExcelQuicknavIterator):
"""
Allows iterating over an MS excel Sheets collection emitting L{QuickNavItem} object.
Expand Down Expand Up @@ -1433,10 +1436,14 @@ def reportFocus(self):
if isinstance(field,textInfos.FieldCommand) and isinstance(field.field,textInfos.FormatField):
formatField.update(field.field)
if not hasattr(self.parent,'_formatFieldSpeechCache'):
self.parent._formatFieldSpeechCache={}
text=speech.getFormatFieldSpeech(formatField,attrsCache=self.parent._formatFieldSpeechCache,formatConfig=formatConfig) if formatField else None
if text:
speech.speakText(text)
self.parent._formatFieldSpeechCache = textInfos.Field()
if formatField:
sequence = speech.getFormatFieldSpeech(
formatField,
attrsCache=self.parent._formatFieldSpeechCache,
formatConfig=formatConfig
)
speech.speak(sequence)
super(ExcelCell,self).reportFocus()

__gestures = {
Expand Down Expand Up @@ -1602,7 +1609,7 @@ class ExcelMergedCell(ExcelCell):

def _get_cellCoordsText(self):
return self.getCellAddress(self.excelCellObject.mergeArea)

def _get_rowSpan(self):
return self.excelCellObject.mergeArea.rows.count

Expand Down
52 changes: 39 additions & 13 deletions source/appModules/kindle.py
Expand Up @@ -2,14 +2,13 @@
#Copyright (C) 2016-2017 NV Access Limited
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
from typing import Optional, Dict

from comtypes import COMError
import time
from comtypes.hresult import S_OK
import appModuleHandler
import speech
import sayAllHandler
import eventHandler
import api
from scriptHandler import willSayAllResume, isScriptWaiting
import controlTypes
Expand All @@ -18,6 +17,7 @@
import browseMode
from browseMode import BrowseModeDocumentTreeInterceptor
import textInfos
from speech.types import SpeechSequence
from textInfos import DocumentWithPageTurns
from IAccessibleHandler import IAccessible2
from NVDAObjects.IAccessible import IAccessible
Expand Down Expand Up @@ -272,29 +272,55 @@ def getTextWithFields(self, formatConfig=None):
item.field.pop("content", None)
return items

def getFormatFieldSpeech(self, attrs, attrsCache=None, formatConfig=None, reason=None, unit=None, extraDetail=False , initialFormat=False, separator=speech.CHUNK_SEPARATOR):
out = ""
def getFormatFieldSpeech(
self,
attrs: textInfos.Field,
attrsCache: Optional[textInfos.Field] = None,
formatConfig: Optional[Dict[str, bool]] = None,
reason: Optional[str] = None,
unit: Optional[str] = None,
extraDetail: bool = False,
initialFormat: bool = False
) -> SpeechSequence:
out: SpeechSequence = []
comment = attrs.get("kindle-user-note")
if comment:
# For now, we report this the same way we do comments.
attrs["comment"] = comment
highlight = attrs.get("kindle-highlight")
oldHighlight = attrsCache.get("kindle-highlight") if attrsCache is not None else None
if oldHighlight != highlight:
# Translators: Reported when text is highlighted.
out += (_("highlight") if highlight
translation = (
# Translators: Reported when text is highlighted.
_("highlight") if highlight else
# Translators: Reported when text is not highlighted.
else _("no highlight")) + separator
_("no highlight")
)
out.append(translation)
popular = attrs.get("kindle-popular-highlight-count")
oldPopular = attrsCache.get("kindle-popular-highlight-count") if attrsCache is not None else None
if oldPopular != popular:
# Translators: Reported in Kindle when text has been identified as a popular highlight;
# i.e. it has been highlighted by several people.
# %s is replaced with the number of people who have highlighted this text.
out += (_("%s highlighted") % popular if popular
translation = (
# Translators: Reported in Kindle when text has been identified as a popular highlight;
# i.e. it has been highlighted by several people.
# %s is replaced with the number of people who have highlighted this text.
_("%s highlighted") % popular if popular else
# Translators: Reported when moving out of a popular highlight.
else _("out of popular highlight")) + separator
out += super(BookPageViewTextInfo, self).getFormatFieldSpeech(attrs, attrsCache=attrsCache, formatConfig=formatConfig, reason=reason, unit=unit, extraDetail=extraDetail , initialFormat=initialFormat, separator=separator)
_("out of popular highlight")
)
out.append(translation)

superSpeech = super(BookPageViewTextInfo, self).getFormatFieldSpeech(
attrs,
attrsCache=attrsCache,
formatConfig=formatConfig,
reason=reason,
unit=unit,
extraDetail=extraDetail,
initialFormat=initialFormat
)
out.extend(superSpeech)
textInfos._logBadSequenceTypes(out)
return out

def updateSelection(self):
Expand Down
9 changes: 5 additions & 4 deletions source/aria.py
Expand Up @@ -2,10 +2,11 @@
# Copyright (C) 2009-2019 NV Access Limited, Leonard de Ruijter
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
from typing import Dict

import controlTypes

ariaRolesToNVDARoles={
ariaRolesToNVDARoles: Dict[str, int] = {
"description":controlTypes.ROLE_STATICTEXT,
"search":controlTypes.ROLE_SECTION,
"alert":controlTypes.ROLE_ALERT,
Expand Down Expand Up @@ -58,13 +59,13 @@
"treeitem":controlTypes.ROLE_TREEVIEWITEM,
}

ariaSortValuesToNVDAStates={
ariaSortValuesToNVDAStates: Dict[str, int] = {
'descending':controlTypes.STATE_SORTED_DESCENDING,
'ascending':controlTypes.STATE_SORTED_ASCENDING,
'other':controlTypes.STATE_SORTED,
}

landmarkRoles = {
landmarkRoles: Dict[str, str] = {
# Translators: Reported for the banner landmark, normally found on web pages.
"banner": pgettext("aria", "banner"),
# Translators: Reported for the complementary landmark, normally found on web pages.
Expand All @@ -84,7 +85,7 @@
"region": pgettext("aria", "region"),
}

htmlNodeNameToAriaRoles = {
htmlNodeNameToAriaRoles: Dict[str, str] = {
"header": "banner",
"nav": "navigation",
"main": "main",
Expand Down
2 changes: 2 additions & 0 deletions source/browseMode.py
Expand Up @@ -9,6 +9,7 @@
import winsound
import time
import weakref

import wx
import core
from logHandler import log
Expand Down Expand Up @@ -1131,6 +1132,7 @@ def move():
# If it didn't, and it directly or indirectly called wx.Yield, it could start executing NVDA's core pump from within the yield, causing recursion.
core.callLater(100, move)


class BrowseModeDocumentTextInfo(textInfos.TextInfo):

def _get_focusableNVDAObjectAtStart(self):
Expand Down
1 change: 1 addition & 0 deletions source/config/configSpec.py
Expand Up @@ -221,6 +221,7 @@
louis = boolean(default=false)
timeSinceInput = boolean(default=false)
vision = boolean(default=false)
speechSequences = boolean(default=false)
feerrenrut marked this conversation as resolved.
Show resolved Hide resolved

[uwpOcr]
language = string(default="")
Expand Down
26 changes: 14 additions & 12 deletions source/controlTypes.py
Expand Up @@ -3,6 +3,7 @@
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2007-2016 NV Access Limited, Babbage B.V.
from typing import Dict, Union, Set, Any, Optional, List

ROLE_UNKNOWN=0
ROLE_WINDOW=1
Expand Down Expand Up @@ -199,7 +200,7 @@
STATE_OVERFLOWING=0x10000000000
STATE_UNLOCKED=0x20000000000

roleLabels={
roleLabels: Dict[int, str] = {
# Translators: The word for an unknown control type.
ROLE_UNKNOWN:_("unknown"),
# Translators: The word for window of a program such as document window.
Expand Down Expand Up @@ -500,7 +501,7 @@
ROLE_ARTICLE: _("article"),
}

stateLabels={
stateLabels: Dict[int, str] = {
# Translators: This is presented when a control or document is unavailable.
STATE_UNAVAILABLE:_("unavailable"),
# Translators: This is presented when a control has focus.
Expand Down Expand Up @@ -639,7 +640,7 @@

#: Text to use for 'current' values. These describe if an item is the current item
#: within a particular kind of selection.
isCurrentLabels = {
isCurrentLabels: Dict[Union[bool, str], str] = {
# Translators: Presented when an item is marked as current in a collection of items
True:_("current"),
# Translators: Presented when a page item is marked as current in a collection of page items
Expand Down Expand Up @@ -776,24 +777,25 @@ def processNegativeStates(role, states, reason, negativeStates=None):
# Return all negative states which should be spoken, excluding the positive states.
return speakNegatives - states

def processAndLabelStates(role, states, reason, positiveStates=None, negativeStates=None, positiveStateLabelDict={}, negativeStateLabelDict={}):

def processAndLabelStates(
role: int,
states: Set[Any],
reason: str,
positiveStates: Optional[Set[Any]] = None,
negativeStates: Optional[Set[Any]] = None,
positiveStateLabelDict: Dict[int, str] = {},
negativeStateLabelDict: Dict[int, str] = {},
) -> List[str]:
"""Processes the states for an object and returns the appropriate state labels for both positive and negative states.
@param role: The role of the object to process states for (e.g. C{ROLE_CHECKBOX}.
@type role: int
@param states: The raw states for an object to process.
@type states: set
@param reason: The reason to process the states (e.g. C{REASON_FOCUS}.
@type reason: str
@param positiveStates: Used for C{REASON_CHANGE}, specifies states changed from negative to positive;
@type positiveStates: set
@param negativeStates: Used for C{REASON_CHANGE}, specifies states changed from positive to negative;
@type negativeStates: setpositiveStateLabelDict={}, negativeStateLabelDict
@param positiveStateLabelDict: Dictionary containing state identifiers as keys and associated positive labels as their values.
@type positiveStateLabelDict: dict
@param negativeStateLabelDict: Dictionary containing state identifiers as keys and associated negative labels as their values.
@type negativeStateLabelDict: dict
@return: The labels of the relevant positive and negative states.
@rtype: [str, ...]
"""
mergedStateLabels=[]
positiveStates = processPositiveStates(role, states, reason, positiveStates)
Expand Down