From 7a123c317ed3c755f61357066a223af910cadc35 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 12 Jun 2019 20:04:07 -0400 Subject: [PATCH 01/26] Remove bounds checking code from the console. --- source/NVDAObjects/UIA/winConsoleUIA.py | 27 +------------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 75aebc3dfc2..c1822fee14c 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -35,16 +35,6 @@ def __init__(self, obj, position, _rangeObj=None): ) def move(self, unit, direction, endPoint=None): - oldRange = None - if self.basePosition != textInfos.POSITION_CARET: - # Insure we haven't gone beyond the visible text. - # UIA adds thousands of blank lines to the end of the console. - visiRanges = self.obj.UIATextPattern.GetVisibleRanges() - visiLength = visiRanges.length - if visiLength > 0: - firstVisiRange = visiRanges.GetElement(0) - lastVisiRange = visiRanges.GetElement(visiLength - 1) - oldRange = self._rangeObj.clone() if unit == textInfos.UNIT_WORD and direction != 0: # UIA doesn't implement word movement, so we need to do it manually. # Relative to the current line, calculate our offset @@ -98,22 +88,7 @@ def move(self, unit, direction, endPoint=None): endPoint=endPoint ) else: # moving by a unit other than word - res = super(consoleUIATextInfo, self).move(unit, direction, endPoint) - if oldRange and ( - self._rangeObj.CompareEndPoints( - UIAHandler.TextPatternRangeEndpoint_Start, - firstVisiRange, - UIAHandler.TextPatternRangeEndpoint_Start - ) < 0 - or self._rangeObj.CompareEndPoints( - UIAHandler.TextPatternRangeEndpoint_Start, - lastVisiRange, - UIAHandler.TextPatternRangeEndpoint_End - ) >= 0 - ): - self._rangeObj = oldRange - return 0 - return res + return super(consoleUIATextInfo, self).move(unit, direction, endPoint) def expand(self, unit): if unit == textInfos.UNIT_WORD: From a61dab0d77514458052eab1af250184166c2171d Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 12 Jun 2019 20:46:48 -0400 Subject: [PATCH 02/26] Implement isOutOfBounds. --- source/NVDAObjects/UIA/__init__.py | 15 +++++++++++++++ source/textInfos/__init__.py | 13 +++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index f4479ca8429..7330fe69b68 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -702,6 +702,21 @@ def compareEndPoints(self,other,which): target=UIAHandler.TextPatternRangeEndpoint_End return self._rangeObj.CompareEndpoints(src,other._rangeObj,target) + def isOutOfBounds(self): + visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + visiLength = visiRanges.length + if visiLength > 0: + firstVisiRange = visiRanges.GetElement(0) + lastVisiRange = visiRanges.GetElement(visiLength - 1) + if self._rangeObj.CompareEndPoints( + UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange, + UIAHandler.TextPatternRangeEndpoint_Start + ) < 0 or self._rangeObj.CompareEndPoints( + UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange, + UIAHandler.TextPatternRangeEndpoint_End) >= 0: + return False + return True + def setEndPoint(self,other,which): if which.startswith('start'): src=UIAHandler.TextPatternRangeEndpoint_Start diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 4497e183436..9b9d98b1209 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -2,7 +2,7 @@ #A part of NonVisual Desktop Access (NVDA) #This file is covered by the GNU General Public License. #See the file COPYING for more details. -#Copyright (C) 2006-2018 NV Access Limited, Babbage B.V. +#Copyright (C) 2006-2019 NV Access Limited, Babbage B.V., Bill Dengler """Framework for accessing text content in widgets. The core component of this framework is the L{TextInfo} class. @@ -259,7 +259,7 @@ class TextInfo(baseObject.AutoPropertyObject): L{Points} must be supported as a position. To support routing to a screen point from a given position, L{pointAtStart} or L{boundingRects} must be implemented. In order to support text formatting or control information, L{getTextWithFields} should be overridden. - + To support review bounds configuration, implement the L{isOutOfBounds} method. @ivar bookmark: A unique identifier that can be used to make another textInfo object at this position. @type bookmark: L{Bookmark} """ @@ -551,6 +551,15 @@ def getMathMl(self, field): """ raise NotImplementedError + def isOutOfBounds(self): + """ + Returns True if this textInfo is positioned outside its object's + visible text. + @rtype: bool + """ + raise NotImplementedError + + RE_EOL = re.compile("\r\n|[\n\r]") def convertToCrlf(text): """Convert a string so that it contains only CRLF line endings. From a8b93bd4d76cb8d2a410db51c3cac1c6fb08c708 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 12 Jun 2019 22:10:34 -0400 Subject: [PATCH 03/26] Implement review bounds configuration. --- source/NVDAObjects/__init__.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index cb823d790dd..299ccca9c35 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -12,6 +12,7 @@ import re import weakref from logHandler import log +from scriptHandler import script import review import eventHandler from displayModel import DisplayModelTextInfo @@ -1267,3 +1268,32 @@ def getSelectedItemsCount(self,maxCount=2): For performance, this method will only count up to the given maxCount number, and if there is one more above that, then sys.maxint is returned stating that many items are selected. """ return 0 + + reviewBounded = False + + @script(gesture="kb:NVDA+o", + #Translators: A gesture description. + description=_(u"Toggles whether review is constrained to the currently visible text")) + def script_toggleReviewBounds(self, gesture): + try: + rp = api.getReviewPosition() + outOfBounds = rp.isOutOfBounds() + self.reviewBounded = not self.reviewBounded + if self.reviewBounded: + ui.message( + #Translators: Reported when review is constrained to this object's visible text. + _(u"Bounded review") + ) + if outOfBounds: + api.setReviewPosition( + self.makeTextInfo(textInfos.POSITION_CARET), isCaret=True) + else: + ui.message( + #Translators: Reported when review is unconstrained, so all of this object's text can be read. + _(u"Unbounded review") + ) + except NotImplementedError: + ui.message( + #Translators: Reported when review bound configuration isn't supported for this object. + _(u"Not supported here")) + \ No newline at end of file From 6bb21b2ed8cde632feed27899d495c54265123c7 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 12 Jun 2019 23:29:07 -0400 Subject: [PATCH 04/26] Fix isOutOfBounds for UIA, implement initial bounds checking, and bound the console by default to maintain previous behavior. --- source/NVDAObjects/UIA/__init__.py | 9 +++++---- source/NVDAObjects/UIA/winConsoleUIA.py | 3 +++ source/globalCommands.py | 19 +++++++++++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 7330fe69b68..d6e739dd94b 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -708,14 +708,15 @@ def isOutOfBounds(self): if visiLength > 0: firstVisiRange = visiRanges.GetElement(0) lastVisiRange = visiRanges.GetElement(visiLength - 1) - if self._rangeObj.CompareEndPoints( + return self._rangeObj.CompareEndPoints( UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange, UIAHandler.TextPatternRangeEndpoint_Start ) < 0 or self._rangeObj.CompareEndPoints( UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange, - UIAHandler.TextPatternRangeEndpoint_End) >= 0: - return False - return True + UIAHandler.TextPatternRangeEndpoint_End) >= 0 + else: + # Review bounds not available. + return False def setEndPoint(self,other,which): if which.startswith('start'): diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index c1822fee14c..36c7fd99d8b 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -175,6 +175,9 @@ def _get_focusRedirect(self): class winConsoleUIA(Terminal): STABILIZE_DELAY = 0.03 + #: Bound review in consoles by default to maintain feature parity + #: with legacy + reviewBounded = True _TextInfo = consoleUIATextInfo _isTyping = False _lastCharTime = 0 diff --git a/source/globalCommands.py b/source/globalCommands.py index c311d2cb6ea..793fa440b62 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -972,6 +972,17 @@ def script_review_activate(self,gesture): script_review_activate.__doc__=_("Performs the default action on the current navigator object (example: presses it if it is a button).") script_review_activate.category=SCRCAT_OBJECTNAVIGATION + def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): + """ + Nondestructively moves a textInfo and returns a (newInfo, res) tuple. + Res is 0 if the move would be out of bounds. + """ + newInfo = info.copy() + res = newInfo.move(unit, direction, endPoint=endPoint) + if newInfo.obj.reviewBounded and newInfo.isOutOfBounds(): + return (info, 0) + return (newInfo, res) + def script_review_top(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_FIRST) api.setReviewPosition(info) @@ -987,7 +998,7 @@ def script_review_previousLine(self,gesture): if info._expandCollapseBeforeReview: info.expand(textInfos.UNIT_LINE) info.collapse() - res=info.move(textInfos.UNIT_LINE,-1) + info, res = self._moveWithBoundsChecking(info, textInfos.UNIT_LINE,-1) if res==0: # Translators: a message reported when review cursor is at the top line of the current navigator object. ui.reviewMessage(_("Top")) @@ -1019,7 +1030,7 @@ def script_review_nextLine(self,gesture): if info._expandCollapseBeforeReview: info.expand(textInfos.UNIT_LINE) info.collapse() - res=info.move(textInfos.UNIT_LINE,1) + info, res = self._moveWithBoundsChecking(info, textInfos.UNIT_LINE,1) if res==0: # Translators: a message reported when review cursor is at the bottom line of the current navigator object. ui.reviewMessage(_("Bottom")) @@ -1047,7 +1058,7 @@ def script_review_previousWord(self,gesture): if info._expandCollapseBeforeReview: info.expand(textInfos.UNIT_WORD) info.collapse() - res=info.move(textInfos.UNIT_WORD,-1) + info, res = self._moveWithBoundsChecking(info, textInfos.UNIT_WORD,-1) if res==0: # Translators: a message reported when review cursor is at the top line of the current navigator object. ui.reviewMessage(_("Top")) @@ -1078,7 +1089,7 @@ def script_review_nextWord(self,gesture): if info._expandCollapseBeforeReview: info.expand(textInfos.UNIT_WORD) info.collapse() - res=info.move(textInfos.UNIT_WORD,1) + info, res = self._moveWithBoundsChecking(info, textInfos.UNIT_WORD,1) if res==0: # Translators: a message reported when review cursor is at the bottom line of the current navigator object. ui.reviewMessage(_("Bottom")) From deffcea30611c3ae8d3b1e732340b3a25e49f072 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 01:11:30 -0400 Subject: [PATCH 05/26] Implement review of top and bottom lines when review is bounded. --- source/NVDAObjects/UIA/__init__.py | 14 ++++++++++++++ source/globalCommands.py | 8 ++++++-- source/textInfos/__init__.py | 4 +++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index d6e739dd94b..838b18ab5a6 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -297,6 +297,20 @@ def __init__(self,obj,position,_rangeObj=None): elif position==textInfos.POSITION_LAST: self._rangeObj=self.obj.UIATextPattern.documentRange self.collapse(True) + elif position==textInfos.POSITION_FIRSTVISIBLE: + try: + visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + self._rangeObj = visiRanges.GetElement(0) + except COMError: + # Error: FIRST_VISIBLE position not supported by the UIA text pattern. + raise RuntimeError + elif position==textInfos.POSITION_LASTVISIBLE: + try: + visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + self._rangeObj = visiRanges.GetElement(visiRanges.length - 1) + except COMError: + # Error: LAST_VISIBLE position not supported by the UIA text pattern. + raise RuntimeError elif position==textInfos.POSITION_ALL or position==self.obj: self._rangeObj=self.obj.UIATextPattern.documentRange elif isinstance(position,UIA) or isinstance(position,UIAHandler.IUIAutomationElement): diff --git a/source/globalCommands.py b/source/globalCommands.py index 793fa440b62..a1924a82aae 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -984,7 +984,9 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): return (newInfo, res) def script_review_top(self,gesture): - info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_FIRST) + obj = api.getReviewPosition().obj + pos = textInfos.POSITION_FIRSTVISIBLE if obj.reviewBounded else textInfos.POSITION_FIRST + info = obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) ui.reviewMessage(_("Top")) @@ -1044,7 +1046,9 @@ def script_review_nextLine(self,gesture): script_review_nextLine.category=SCRCAT_TEXTREVIEW def script_review_bottom(self,gesture): - info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) + obj = api.getReviewPosition().obj + pos = textInfos.POSITION_LASTVISIBLE if obj.reviewBounded else textInfos.POSITION_LAST + info = obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) ui.reviewMessage(_("Bottom")) diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 9b9d98b1209..8b2ca73a130 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -155,6 +155,8 @@ def __repr__(self): #Position constants POSITION_FIRST="first" POSITION_LAST="last" +POSITION_FIRSTVISIBLE="firstVisible" +POSITION_LASTVISIBLE="lastVisible" POSITION_CARET="caret" POSITION_SELECTION="selection" POSITION_ALL="all" @@ -259,7 +261,7 @@ class TextInfo(baseObject.AutoPropertyObject): L{Points} must be supported as a position. To support routing to a screen point from a given position, L{pointAtStart} or L{boundingRects} must be implemented. In order to support text formatting or control information, L{getTextWithFields} should be overridden. - To support review bounds configuration, implement the L{isOutOfBounds} method. + To support review bounds configuration, implement the L{isOutOfBounds} method and L{POSITION_FIRSTVISIBLE} and L{POSITION_LASTVISIBLE} positions. @ivar bookmark: A unique identifier that can be used to make another textInfo object at this position. @type bookmark: L{Bookmark} """ From 51fa56a94863111805153415d98cad5974b5b0b9 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 02:22:34 -0400 Subject: [PATCH 06/26] Add new gesture to the input gestures dialog, update user guide. --- source/NVDAObjects/__init__.py | 13 ++++++++----- user_docs/en/userGuide.t2t | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index 299ccca9c35..e2bd9de4753 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1272,7 +1272,9 @@ def getSelectedItemsCount(self,maxCount=2): reviewBounded = False @script(gesture="kb:NVDA+o", - #Translators: A gesture description. + # Hardcoded to avoid circular import + category=_(u"Text review"), + # Translators: A gesture description. description=_(u"Toggles whether review is constrained to the currently visible text")) def script_toggleReviewBounds(self, gesture): try: @@ -1281,7 +1283,8 @@ def script_toggleReviewBounds(self, gesture): self.reviewBounded = not self.reviewBounded if self.reviewBounded: ui.message( - #Translators: Reported when review is constrained to this object's visible text. + # Translators: Reported when review is constrained to this + # object's visible text. _(u"Bounded review") ) if outOfBounds: @@ -1289,11 +1292,11 @@ def script_toggleReviewBounds(self, gesture): self.makeTextInfo(textInfos.POSITION_CARET), isCaret=True) else: ui.message( - #Translators: Reported when review is unconstrained, so all of this object's text can be read. + # Translators: Reported when review is unconstrained, so all + # of this object's text can be read. _(u"Unbounded review") ) except NotImplementedError: ui.message( - #Translators: Reported when review bound configuration isn't supported for this object. + # Translators: Reported when review bound configuration isn't supported for this object. _(u"Not supported here")) - \ No newline at end of file diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 42c0e6e94c5..b1b3a72fd17 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -402,6 +402,7 @@ The following commands are available for reviewing text: | Select then Copy to review cursor | NVDA+f10 | NVDA+f10 | none | On the first press, text is selected from the position previously set start marker up to and including the review cursor's current position. After pressing this key a second time, the text will be copied to the Windows clipboard | | Report text formatting | NVDA+f | NVDA+f | none | Reports the formatting of the text where the review cursor is currently situated. Pressing twice shows the information in browse mode | | Report current symbol replacement | None | None | none | Speaks the symbol where the review cursor is positioned. Pressed twice, shows the symbol and the text used to speak it in browse mode. | +| Toggle review bounds | NVDA+o | NVDA+o | none | Toggles whether the review cursor is constrained to the currently visible text. This is particularly useful in the Windows Console for reviewing text that has scrolled off the screen. | %kc:endInclude Note: numpad keys require numlock key to be turned off to work properly. From a1ad305f5d96b609f3b012807814519b9bf6daed Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 05:21:49 -0400 Subject: [PATCH 07/26] Implement review bounds configuration for offsetsTextInfo. --- source/textInfos/offsets.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/textInfos/offsets.py b/source/textInfos/offsets.py index c62a83f11b1..217646b2e7a 100755 --- a/source/textInfos/offsets.py +++ b/source/textInfos/offsets.py @@ -401,6 +401,10 @@ def __init__(self,obj,position): self._startOffset=self._endOffset=0 elif position==textInfos.POSITION_LAST: self._startOffset=self._endOffset=max(self._getStoryLength()-1,0) + elif position==textInfos.POSITION_FIRSTVISIBLE: + self._startOffset=self._endOffset=self._getFirstVisibleOffset() + elif position==textInfos.POSITION_LASTVISIBLE: + self._startOffset=self._endOffset=self._getLastVisibleOffset() elif position==textInfos.POSITION_CARET: self._startOffset=self._endOffset=self._getCaretOffset() elif position==textInfos.POSITION_SELECTION: @@ -457,6 +461,14 @@ def collapse(self,end=False): def expand(self,unit): self._startOffset,self._endOffset=self._getUnitOffsets(unit,self._startOffset) + def isOutOfBounds(self): + try: + return self._startOffset < self._getFirstVisibleOffset( + ) or self._endOffset > self._getLastVisibleOffset() + except AttributeError, LookupError: + # Some implementations have incomplete support. + raise NotImplementedError + def copy(self): return self.__class__(self.obj,self) From d7a00e887dd213950a7cbba1bd71f41dc4b5f002 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 06:38:05 -0400 Subject: [PATCH 08/26] Apply suggestions from code review Co-Authored-By: Leonard de Ruijter --- source/NVDAObjects/UIA/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 838b18ab5a6..4d0b27d657b 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -297,9 +297,10 @@ def __init__(self,obj,position,_rangeObj=None): elif position==textInfos.POSITION_LAST: self._rangeObj=self.obj.UIATextPattern.documentRange self.collapse(True) - elif position==textInfos.POSITION_FIRSTVISIBLE: + elif position in (textInfos.POSITION_FIRSTVISIBLE, textInfos.POSITION_LASTVISIBLE): try: visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + element = 0 if position == textInfos.POSITION_FIRSTVISIBLE else visiRanges.length - 1: self._rangeObj = visiRanges.GetElement(0) except COMError: # Error: FIRST_VISIBLE position not supported by the UIA text pattern. From 1a937d2240a77d75dadfb71ab4b783aa16f6f2dc Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 06:52:22 -0400 Subject: [PATCH 09/26] Review actions. --- source/NVDAObjects/UIA/__init__.py | 13 +++---------- source/NVDAObjects/__init__.py | 11 +++++++---- source/displayModel.py | 5 +++++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 4d0b27d657b..484911defa2 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -300,18 +300,11 @@ def __init__(self,obj,position,_rangeObj=None): elif position in (textInfos.POSITION_FIRSTVISIBLE, textInfos.POSITION_LASTVISIBLE): try: visiRanges = self.obj.UIATextPattern.GetVisibleRanges() - element = 0 if position == textInfos.POSITION_FIRSTVISIBLE else visiRanges.length - 1: + element = 0 if position == textInfos.POSITION_FIRSTVISIBLE else visiRanges.length - 1 self._rangeObj = visiRanges.GetElement(0) except COMError: # Error: FIRST_VISIBLE position not supported by the UIA text pattern. - raise RuntimeError - elif position==textInfos.POSITION_LASTVISIBLE: - try: - visiRanges = self.obj.UIATextPattern.GetVisibleRanges() - self._rangeObj = visiRanges.GetElement(visiRanges.length - 1) - except COMError: - # Error: LAST_VISIBLE position not supported by the UIA text pattern. - raise RuntimeError + raise NotImplementedError elif position==textInfos.POSITION_ALL or position==self.obj: self._rangeObj=self.obj.UIATextPattern.documentRange elif isinstance(position,UIA) or isinstance(position,UIAHandler.IUIAutomationElement): @@ -731,7 +724,7 @@ def isOutOfBounds(self): UIAHandler.TextPatternRangeEndpoint_End) >= 0 else: # Review bounds not available. - return False + return super(UIATextInfo, self).isOutOfBounds() def setEndPoint(self,other,which): if which.startswith('start'): diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index e2bd9de4753..8d25e76e971 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1269,6 +1269,8 @@ def getSelectedItemsCount(self,maxCount=2): """ return 0 + #: Whether review is constrained to the currently visible text. + #: @type: bool reviewBounded = False @script(gesture="kb:NVDA+o", @@ -1280,6 +1282,11 @@ def script_toggleReviewBounds(self, gesture): try: rp = api.getReviewPosition() outOfBounds = rp.isOutOfBounds() + except NotImplementedError: + ui.message( + # Translators: Reported when review bound configuration isn't supported for this object. + _(u"Not supported here")) + else: self.reviewBounded = not self.reviewBounded if self.reviewBounded: ui.message( @@ -1296,7 +1303,3 @@ def script_toggleReviewBounds(self, gesture): # of this object's text can be read. _(u"Unbounded review") ) - except NotImplementedError: - ui.message( - # Translators: Reported when review bound configuration isn't supported for this object. - _(u"Not supported here")) diff --git a/source/displayModel.py b/source/displayModel.py index 01bd7e5682f..5f18403417b 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -590,3 +590,8 @@ def _setSelectionOffsets(self,start,end): if start!=end: raise NotImplementedError("Expanded selections not supported") self._setCaretOffset(start) + + def isOutOfBounds(self): + """DisplayModelTextInfo instances only show screen contents, so + unbounding is impossible.""" + raise NotImplementedError From 536cccee89ee3de46fa7b76f1e3b8c6ea95e8aa5 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 07:06:50 -0400 Subject: [PATCH 10/26] isOutOfBounds -> isOffscreen --- source/NVDAObjects/UIA/__init__.py | 2 +- source/NVDAObjects/__init__.py | 2 +- source/displayModel.py | 2 +- source/globalCommands.py | 2 +- source/textInfos/__init__.py | 4 ++-- source/textInfos/offsets.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 484911defa2..9a8a205dc11 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -710,7 +710,7 @@ def compareEndPoints(self,other,which): target=UIAHandler.TextPatternRangeEndpoint_End return self._rangeObj.CompareEndpoints(src,other._rangeObj,target) - def isOutOfBounds(self): + def isOffscreen(self): visiRanges = self.obj.UIATextPattern.GetVisibleRanges() visiLength = visiRanges.length if visiLength > 0: diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index 8d25e76e971..29d7dce298d 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1281,7 +1281,7 @@ def getSelectedItemsCount(self,maxCount=2): def script_toggleReviewBounds(self, gesture): try: rp = api.getReviewPosition() - outOfBounds = rp.isOutOfBounds() + outOfBounds = rp.isOffscreen() except NotImplementedError: ui.message( # Translators: Reported when review bound configuration isn't supported for this object. diff --git a/source/displayModel.py b/source/displayModel.py index 5f18403417b..9848e38f722 100644 --- a/source/displayModel.py +++ b/source/displayModel.py @@ -591,7 +591,7 @@ def _setSelectionOffsets(self,start,end): raise NotImplementedError("Expanded selections not supported") self._setCaretOffset(start) - def isOutOfBounds(self): + def isOffscreen(self): """DisplayModelTextInfo instances only show screen contents, so unbounding is impossible.""" raise NotImplementedError diff --git a/source/globalCommands.py b/source/globalCommands.py index a1924a82aae..dd6afd630af 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -979,7 +979,7 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): """ newInfo = info.copy() res = newInfo.move(unit, direction, endPoint=endPoint) - if newInfo.obj.reviewBounded and newInfo.isOutOfBounds(): + if newInfo.obj.reviewBounded and newInfo.isOffscreen(): return (info, 0) return (newInfo, res) diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 8b2ca73a130..9a6b995a229 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -261,7 +261,7 @@ class TextInfo(baseObject.AutoPropertyObject): L{Points} must be supported as a position. To support routing to a screen point from a given position, L{pointAtStart} or L{boundingRects} must be implemented. In order to support text formatting or control information, L{getTextWithFields} should be overridden. - To support review bounds configuration, implement the L{isOutOfBounds} method and L{POSITION_FIRSTVISIBLE} and L{POSITION_LASTVISIBLE} positions. + To support review bounds configuration, implement the L{isOffscreen} method and L{POSITION_FIRSTVISIBLE} and L{POSITION_LASTVISIBLE} positions. @ivar bookmark: A unique identifier that can be used to make another textInfo object at this position. @type bookmark: L{Bookmark} """ @@ -553,7 +553,7 @@ def getMathMl(self, field): """ raise NotImplementedError - def isOutOfBounds(self): + def isOffscreen(self): """ Returns True if this textInfo is positioned outside its object's visible text. diff --git a/source/textInfos/offsets.py b/source/textInfos/offsets.py index 217646b2e7a..7b1009420fb 100755 --- a/source/textInfos/offsets.py +++ b/source/textInfos/offsets.py @@ -461,7 +461,7 @@ def collapse(self,end=False): def expand(self,unit): self._startOffset,self._endOffset=self._getUnitOffsets(unit,self._startOffset) - def isOutOfBounds(self): + def isOffscreen(self): try: return self._startOffset < self._getFirstVisibleOffset( ) or self._endOffset > self._getLastVisibleOffset() From d84570a8112e6cc9e51b1c7fb5bcd5f288368bcc Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 13 Jun 2019 19:51:35 -0400 Subject: [PATCH 11/26] Move SCRCAT constants to a new scriptCategories module. --- developerGuide.t2t | 4 +-- source/NVDAObjects/__init__.py | 4 +-- source/globalCommands.py | 47 ++------------------------ source/inputCore.py | 11 +----- source/scriptCategories.py | 61 ++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 source/scriptCategories.py diff --git a/developerGuide.t2t b/developerGuide.t2t index b88134aea58..66c46137f76 100644 --- a/developerGuide.t2t +++ b/developerGuide.t2t @@ -403,7 +403,7 @@ For example: --- start --- @script( description=_("Speaks the date and time"), - category=inputCore.SCRCAT_MISC, + category=scriptCategories.SCRCAT_MISC, gestures=["kb:NVDA+shift+t", "kb:NVDA+alt+r"] ) def script_sayDateTime(self, gesture): @@ -421,7 +421,7 @@ The following keyword arguments can be used when applying the script decorator: - category: The category of the script in order for it to be grouped with other similar scripts. For example, a script in a global plugin which adds browse mode quick navigation keys may be categorized under the "Browse mode" category. The category can be set for individual scripts, but you can also set the "scriptCategory" attribute on the plugin class, which will be used for scripts which do not specify a category. - There are constants for common categories prefixed with SCRCAT_ in the inputCore and globalCommands modules, which can also be specified. + There are constants for common categories prefixed with SCRCAT_ in the scriptCategories module, which can also be specified. The script will be listed under the specified category in the Input Gestures dialog. If no category is specified, the script will be categorized under "Miscellaneous". - gesture: A string containing a single gesture associated with this script, e.g. "kb:NVDA+shift+r". diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index 29d7dce298d..ceaa10e66e8 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -29,6 +29,7 @@ import braille import globalPluginHandler import brailleInput +import scriptCategories class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo): """A default TextInfo which is used to enable text review of information about widgets that don't support text content. @@ -1274,8 +1275,7 @@ def getSelectedItemsCount(self,maxCount=2): reviewBounded = False @script(gesture="kb:NVDA+o", - # Hardcoded to avoid circular import - category=_(u"Text review"), + category=scriptCategories.SCRCAT_TEXTREVIEW, # Translators: A gesture description. description=_(u"Toggles whether review is constrained to the currently visible text")) def script_toggleReviewBounds(self, gesture): diff --git a/source/globalCommands.py b/source/globalCommands.py index dd6afd630af..b489ff48e62 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -32,7 +32,7 @@ import treeInterceptorHandler import browseMode import scriptHandler -from scriptHandler import script +from scriptCategories import * import ui import braille import brailleInput @@ -44,49 +44,6 @@ import winVersion from base64 import b16encode -#: Script category for text review commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_TEXTREVIEW = _("Text review") -#: Script category for Object navigation commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_OBJECTNAVIGATION = _("Object navigation") -#: Script category for system caret commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_SYSTEMCARET = _("System caret") -#: Script category for mouse commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_MOUSE = _("Mouse") -#: Script category for speech commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_SPEECH = _("Speech") -#: Script category for configuration dialogs commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_CONFIG = _("Configuration") -#: Script category for configuration profile activation and management commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_CONFIG_PROFILES = _("Configuration profiles") -#: Script category for Braille commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_BRAILLE = _("Braille") -#: Script category for tools commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_TOOLS = pgettext('script category', 'Tools') -#: Script category for touch commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_TOUCH = _("Touch screen") -#: Script category for focus commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_FOCUS = _("System focus") -#: Script category for system status commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_SYSTEM = _("System status") -#: Script category for input commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_INPUT = _("Input") -#: Script category for document formatting commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_DOCUMENTFORMATTING = _("Document formatting") - class GlobalCommands(ScriptableObject): """Commands that are available at all times, regardless of the current focus. """ @@ -1221,7 +1178,7 @@ def _getCurrentLanguageForTextInfo(self, info): curLanguage = speech.getCurrentLanguage() return curLanguage - @script( + @scriptHandler.script( # Translators: Input help mode message for Review Current Symbol command. description=_("Reports the symbol where the review cursor is positioned. Pressed twice, shows the symbol and the text used to speak it in browse mode"), category=SCRCAT_TEXTREVIEW, diff --git a/source/inputCore.py b/source/inputCore.py index 4b1efe01d12..a903a25df93 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -27,21 +27,12 @@ from fileUtils import FaultTolerantFile import watchdog from logHandler import log +from scriptCategories import SCRCAT_BROWSEMODE, SCRCAT_KBEMU, SCRCAT_MISC import globalVars import languageHandler import controlTypes import keyLabels -#: Script category for emulated keyboard keys. -# Translators: The name of a category of NVDA commands. -SCRCAT_KBEMU = _("Emulated system keyboard keys") -#: Script category for miscellaneous commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_MISC = _("Miscellaneous") -#: Script category for Browse Mode commands. -# Translators: The name of a category of NVDA commands. -SCRCAT_BROWSEMODE = _("Browse mode") - class NoInputGestureAction(LookupError): """Informs that there is no action to execute for a gesture. """ diff --git a/source/scriptCategories.py b/source/scriptCategories.py new file mode 100644 index 00000000000..b25de6c242f --- /dev/null +++ b/source/scriptCategories.py @@ -0,0 +1,61 @@ +# scriptCategories.py +# A part of NonVisual Desktop Access (NVDA) +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. +# Copyright (C) 2019 Bill Dengler +""" +Script category constants. +@author: Bill Dengler +""" + +#: Script category for Braille commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BRAILLE = _("Braille") +#: Script category for Browse Mode commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_BROWSEMODE = _("Browse mode") +#: Script category for configuration dialogs commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_CONFIG = _("Configuration") +#: Script category for configuration profile activation and management commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_CONFIG_PROFILES = _("Configuration profiles") +#: Script category for document formatting commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_DOCUMENTFORMATTING = _("Document formatting") +#: Script category for focus commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_FOCUS = _("System focus") +#: Script category for input commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_INPUT = _("Input") +#: Script category for emulated keyboard keys. +# Translators: The name of a category of NVDA commands. +SCRCAT_KBEMU = _("Emulated system keyboard keys") +#: Script category for miscellaneous commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_MISC = _("Miscellaneous") +#: Script category for mouse commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_MOUSE = _("Mouse") +#: Script category for Object navigation commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_OBJECTNAVIGATION = _("Object navigation") +#: Script category for speech commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SPEECH = _("Speech") +#: Script category for system status commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEM = _("System status") +#: Script category for system caret commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_SYSTEMCARET = _("System caret") +#: Script category for text review commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TEXTREVIEW = _("Text review") +#: Script category for tools commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOOLS = pgettext('script category', 'Tools') +#: Script category for touch commands. +# Translators: The name of a category of NVDA commands. +SCRCAT_TOUCH = _("Touch screen") From 454097211491c2bde866a9324889319199e28c4b Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Mon, 17 Jun 2019 20:27:24 -0400 Subject: [PATCH 12/26] Review actions. --- source/NVDAObjects/UIA/__init__.py | 11 +++++++---- source/NVDAObjects/__init__.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 9a8a205dc11..a6f06ae30c8 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -301,9 +301,9 @@ def __init__(self,obj,position,_rangeObj=None): try: visiRanges = self.obj.UIATextPattern.GetVisibleRanges() element = 0 if position == textInfos.POSITION_FIRSTVISIBLE else visiRanges.length - 1 - self._rangeObj = visiRanges.GetElement(0) + self._rangeObj = visiRanges.GetElement(element) except COMError: - # Error: FIRST_VISIBLE position not supported by the UIA text pattern. + # Error: FIRST_VISIBLE/LAST_VISIBLE position not supported by the UIA text pattern. raise NotImplementedError elif position==textInfos.POSITION_ALL or position==self.obj: self._rangeObj=self.obj.UIATextPattern.documentRange @@ -711,7 +711,10 @@ def compareEndPoints(self,other,which): return self._rangeObj.CompareEndpoints(src,other._rangeObj,target) def isOffscreen(self): - visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + try: + visiRanges = self.obj.UIATextPattern.GetVisibleRanges() + except COMError: + raise NotImplementedError visiLength = visiRanges.length if visiLength > 0: firstVisiRange = visiRanges.GetElement(0) @@ -724,7 +727,7 @@ def isOffscreen(self): UIAHandler.TextPatternRangeEndpoint_End) >= 0 else: # Review bounds not available. - return super(UIATextInfo, self).isOutOfBounds() + return super(UIATextInfo, self).isOffscreen() def setEndPoint(self,other,which): if which.startswith('start'): diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index ceaa10e66e8..a929b3c9230 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1279,8 +1279,8 @@ def getSelectedItemsCount(self,maxCount=2): # Translators: A gesture description. description=_(u"Toggles whether review is constrained to the currently visible text")) def script_toggleReviewBounds(self, gesture): + rp = api.getReviewPosition() try: - rp = api.getReviewPosition() outOfBounds = rp.isOffscreen() except NotImplementedError: ui.message( @@ -1296,7 +1296,7 @@ def script_toggleReviewBounds(self, gesture): ) if outOfBounds: api.setReviewPosition( - self.makeTextInfo(textInfos.POSITION_CARET), isCaret=True) + self.makeTextInfo(textInfos.POSITION_CARET)) else: ui.message( # Translators: Reported when review is unconstrained, so all From 768cf6b55b6f02e2adf5ba3de3305c5d66168da3 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Mon, 17 Jun 2019 20:36:24 -0400 Subject: [PATCH 13/26] Clarify doc comment. --- source/globalCommands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index b489ff48e62..c2325d06b45 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -932,7 +932,7 @@ def script_review_activate(self,gesture): def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): """ Nondestructively moves a textInfo and returns a (newInfo, res) tuple. - Res is 0 if the move would be out of bounds. + Res is 0 and the original C{textInfo} is returned if the move would be out of bounds. """ newInfo = info.copy() res = newInfo.move(unit, direction, endPoint=endPoint) From 4cc443ff9f26a6ffb50e89121ca205a40be3cf97 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 19 Jun 2019 20:42:26 -0400 Subject: [PATCH 14/26] Add unique IDs for NVDA objects. --- source/NVDAObjects/IAccessible/__init__.py | 1 + source/NVDAObjects/UIA/__init__.py | 1 + source/NVDAObjects/window/__init__.py | 1 + source/documentBase.py | 3 +++ 4 files changed, 6 insertions(+) diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 401df9a942c..45dd36a0dfc 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -632,6 +632,7 @@ def __init__(self,windowHandle=None,IAccessibleObject=None,IAccessibleChildID=No self.event_objectID=event_objectID self.event_childID=event_childID super(IAccessible,self).__init__(windowHandle=windowHandle) + self.uniqueID = (self.windowHandle, self.IA2UniqueID) try: self.IAccessibleActionObject=IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleAction) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index a6f06ae30c8..24cd1c2e2e5 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -955,6 +955,7 @@ def __init__(self,windowHandle=None,UIAElement=None,initialUIACachedPropertyIDs= if not windowHandle: raise InvalidNVDAObject("no windowHandle") super(UIA,self).__init__(windowHandle=windowHandle) + self.uniqueID = self.UIAElement.getRuntimeId() self.initialUIACachedPropertyIDs=initialUIACachedPropertyIDs if initialUIACachedPropertyIDs: diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index 6f7ee3c6db9..05472fd01fd 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -167,6 +167,7 @@ def __init__(self,windowHandle=None): raise ValueError("invalid or not specified window handle") self.windowHandle=windowHandle super(Window,self).__init__() + self.uniqueID = self.windowHandle def _isEqual(self,other): return super(Window,self)._isEqual(other) and other.windowHandle==self.windowHandle diff --git a/source/documentBase.py b/source/documentBase.py index 51aee3d3b24..ee6cdbab347 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -17,6 +17,9 @@ class TextContainerObject(AutoPropertyObject): E.g. NVDAObjects, BrowseModeDocument TreeInterceptors. """ + #: A unique ID to represent this object. + uniqueID = None + def _get_TextInfo(self): raise NotImplementedError From 291b00e30eb54a08cb0a13aad154049e1730d591 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 19 Jun 2019 21:43:55 -0400 Subject: [PATCH 15/26] Persist review bounds state when an object is regenerated. --- source/NVDAObjects/UIA/winConsoleUIA.py | 11 ++++++++--- source/NVDAObjects/__init__.py | 4 ---- source/documentBase.py | 11 +++++++++-- source/globalVars.py | 5 ++++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 00eea19a350..d15c62c5733 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -6,6 +6,7 @@ import config import ctypes +import globalVars import NVDAHelper import speech import time @@ -188,9 +189,6 @@ class WinConsoleUIA(Terminal): #: Only process text changes every 30 ms, in case the console is getting #: a lot of text. STABILIZE_DELAY = 0.03 - #: Bound review in consoles by default to maintain feature parity - #: with legacy - reviewBounded = True _TextInfo = consoleUIATextInfo #: A queue of typed characters, to be dispatched on C{textChange}. #: This queue allows NVDA to suppress typed passwords when needed. @@ -273,5 +271,12 @@ def _findNonBlankIndices(self, lines): def findExtraOverlayClasses(obj, clsList): if obj.UIAElement.cachedAutomationId == "Text Area": clsList.append(WinConsoleUIA) + # Bound review in consoles by default to maintain feature parity + # with legacy support. + # There may be a better way to do this: I tried overriding __init__ + # on the console class itself, but the console's version never + # got called. + if obj.uniqueID not in globalVars.reviewBoundsStates: + obj.reviewBounded = True elif obj.UIAElement.cachedAutomationId == "Console Window": clsList.append(consoleUIAWindow) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index a929b3c9230..a80d9860b5b 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1270,10 +1270,6 @@ def getSelectedItemsCount(self,maxCount=2): """ return 0 - #: Whether review is constrained to the currently visible text. - #: @type: bool - reviewBounded = False - @script(gesture="kb:NVDA+o", category=scriptCategories.SCRCAT_TEXTREVIEW, # Translators: A gesture description. diff --git a/source/documentBase.py b/source/documentBase.py index ee6cdbab347..7f2b825a2ce 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -10,6 +10,7 @@ import speech import ui import controlTypes +import globalVars class TextContainerObject(AutoPropertyObject): """ @@ -32,6 +33,14 @@ def _get_selection(self): def _set_selection(self,info): info.updateSelection() + def _get_reviewBounded(self): + return globalVars.reviewBoundsStates.get(self.uniqueID, False) + + def _set_reviewBounded(self, state): + if not self.uniqueID: + raise NotImplementedError + globalVars.reviewBoundsStates[self.uniqueID] = state + class DocumentWithTableNavigation(TextContainerObject,ScriptableObject): """ A document that supports standard table navigiation comments (E.g. control+alt+arrows to move between table cells). @@ -208,5 +217,3 @@ def script_toggleIncludeLayoutTables(self,gesture): "kb:control+alt+rightArrow": "nextColumn", "kb:control+alt+leftArrow": "previousColumn", } - - diff --git a/source/globalVars.py b/source/globalVars.py index 5a93fec7ba6..715a2461c9c 100644 --- a/source/globalVars.py +++ b/source/globalVars.py @@ -1,6 +1,6 @@ #globalVars.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2006-2007 NVDA Contributors +#Copyright (C) 2006-2019 NVDA Contributors, Bill Dengler #This file is covered by the GNU General Public License. #See the file COPYING for more details. """global variables module @@ -18,6 +18,8 @@ @type navigatorObject: L{NVDAObjects.NVDAObject} @var navigatorTracksFocus: if true, the navigator object will follow the focus as it changes @type navigatorTracksFocus: boolean +@var reviewBoundsStates: maps object unique IDs to their review bounds states, needed for persistence when an object is regenerated. +@type reviewBoundsStates: dict """ startTime=0 @@ -32,6 +34,7 @@ navigatorObject=None reviewPosition=None reviewPositionObj=None +reviewBoundsStates = dict() lastProgressValue=0 appArgs=None appArgsExtra=None From c94229b05041379d7ea28aa34fd897082db4ccd5 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 19 Jun 2019 22:50:15 -0400 Subject: [PATCH 16/26] Make isOffscreen a property. --- source/NVDAObjects/UIA/__init__.py | 2 +- source/NVDAObjects/__init__.py | 2 +- source/globalCommands.py | 2 +- source/textInfos/__init__.py | 4 ++-- source/textInfos/offsets.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 24cd1c2e2e5..bd1991b6a01 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -710,7 +710,7 @@ def compareEndPoints(self,other,which): target=UIAHandler.TextPatternRangeEndpoint_End return self._rangeObj.CompareEndpoints(src,other._rangeObj,target) - def isOffscreen(self): + def _get_isOffscreen(self): try: visiRanges = self.obj.UIATextPattern.GetVisibleRanges() except COMError: diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index a80d9860b5b..61c194a0d47 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1277,7 +1277,7 @@ def getSelectedItemsCount(self,maxCount=2): def script_toggleReviewBounds(self, gesture): rp = api.getReviewPosition() try: - outOfBounds = rp.isOffscreen() + outOfBounds = rp.isOffscreen except NotImplementedError: ui.message( # Translators: Reported when review bound configuration isn't supported for this object. diff --git a/source/globalCommands.py b/source/globalCommands.py index c2325d06b45..b44448e9d2a 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -936,7 +936,7 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): """ newInfo = info.copy() res = newInfo.move(unit, direction, endPoint=endPoint) - if newInfo.obj.reviewBounded and newInfo.isOffscreen(): + if newInfo.obj.reviewBounded and newInfo.isOffscreen: return (info, 0) return (newInfo, res) diff --git a/source/textInfos/__init__.py b/source/textInfos/__init__.py index 9a6b995a229..3b02087fd58 100755 --- a/source/textInfos/__init__.py +++ b/source/textInfos/__init__.py @@ -261,7 +261,7 @@ class TextInfo(baseObject.AutoPropertyObject): L{Points} must be supported as a position. To support routing to a screen point from a given position, L{pointAtStart} or L{boundingRects} must be implemented. In order to support text formatting or control information, L{getTextWithFields} should be overridden. - To support review bounds configuration, implement the L{isOffscreen} method and L{POSITION_FIRSTVISIBLE} and L{POSITION_LASTVISIBLE} positions. + To support review bounds configuration, implement the L{isOffscreen} property and L{POSITION_FIRSTVISIBLE} and L{POSITION_LASTVISIBLE} positions. @ivar bookmark: A unique identifier that can be used to make another textInfo object at this position. @type bookmark: L{Bookmark} """ @@ -553,7 +553,7 @@ def getMathMl(self, field): """ raise NotImplementedError - def isOffscreen(self): + def _get_isOffscreen(self): """ Returns True if this textInfo is positioned outside its object's visible text. diff --git a/source/textInfos/offsets.py b/source/textInfos/offsets.py index 7b1009420fb..2fed4d5fa7d 100755 --- a/source/textInfos/offsets.py +++ b/source/textInfos/offsets.py @@ -461,7 +461,7 @@ def collapse(self,end=False): def expand(self,unit): self._startOffset,self._endOffset=self._getUnitOffsets(unit,self._startOffset) - def isOffscreen(self): + def _get_isOffscreen(self): try: return self._startOffset < self._getFirstVisibleOffset( ) or self._endOffset > self._getLastVisibleOffset() From b2924fa149a09c7c214f944e7522f07cb4e601fd Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 20 Jun 2019 21:30:55 -0400 Subject: [PATCH 17/26] Fixes. --- source/NVDAObjects/UIA/winConsoleUIA.py | 12 +++++------- source/documentBase.py | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index d15c62c5733..c4211881447 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -197,6 +197,11 @@ class WinConsoleUIA(Terminal): #: Used to determine if typed character/word buffers should be flushed. _hasNewLines = False + def initOverlayClass(self): + # Bound consoles by default to maintain feature parity with legacy. + if self.uniqueID not in globalVars.reviewBoundsStates: + self.reviewBounded = True + def _reportNewText(self, line): # Additional typed character filtering beyond that in LiveText if len(line.strip()) < max(len(speech.curWordChars) + 1, 3): @@ -271,12 +276,5 @@ def _findNonBlankIndices(self, lines): def findExtraOverlayClasses(obj, clsList): if obj.UIAElement.cachedAutomationId == "Text Area": clsList.append(WinConsoleUIA) - # Bound review in consoles by default to maintain feature parity - # with legacy support. - # There may be a better way to do this: I tried overriding __init__ - # on the console class itself, but the console's version never - # got called. - if obj.uniqueID not in globalVars.reviewBoundsStates: - obj.reviewBounded = True elif obj.UIAElement.cachedAutomationId == "Console Window": clsList.append(consoleUIAWindow) diff --git a/source/documentBase.py b/source/documentBase.py index 7f2b825a2ce..b1bf66a82d5 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -12,7 +12,7 @@ import controlTypes import globalVars -class TextContainerObject(AutoPropertyObject): +class TextContainerObject(ScriptableObject): """ An object that contains text which can be accessed via a call to a makeTextInfo method. E.g. NVDAObjects, BrowseModeDocument TreeInterceptors. From 2c24358ed5b46b73415470c91965a83592649e11 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 20 Jun 2019 21:39:36 -0400 Subject: [PATCH 18/26] Better default review bounds configuration for objects. --- source/NVDAObjects/UIA/winConsoleUIA.py | 7 ++----- source/documentBase.py | 5 ++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index c4211881447..4d3a269ad3a 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -196,11 +196,8 @@ class WinConsoleUIA(Terminal): #: Whether the console got new text lines in its last update. #: Used to determine if typed character/word buffers should be flushed. _hasNewLines = False - - def initOverlayClass(self): - # Bound consoles by default to maintain feature parity with legacy. - if self.uniqueID not in globalVars.reviewBoundsStates: - self.reviewBounded = True + #: Bound consoles by default to maintain feature parity with legacy. + _defaultReviewBounds = True def _reportNewText(self, line): # Additional typed character filtering beyond that in LiveText diff --git a/source/documentBase.py b/source/documentBase.py index b1bf66a82d5..5658affdb14 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -33,8 +33,11 @@ def _get_selection(self): def _set_selection(self,info): info.updateSelection() + #: The default state for an object's review bounds. + _defaultReviewBounds = False + def _get_reviewBounded(self): - return globalVars.reviewBoundsStates.get(self.uniqueID, False) + return globalVars.reviewBoundsStates.get(self.uniqueID, self._defaultReviewBounds) def _set_reviewBounded(self, state): if not self.uniqueID: From 959a6c71f7a91e70a104162e0c1d03611c2a5fc6 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 20 Jun 2019 21:40:13 -0400 Subject: [PATCH 19/26] Remove unneeded import. --- source/NVDAObjects/UIA/winConsoleUIA.py | 1 - 1 file changed, 1 deletion(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 4d3a269ad3a..fb2b358a2cd 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -6,7 +6,6 @@ import config import ctypes -import globalVars import NVDAHelper import speech import time From 87d67e7097ed76ca426785ca738473e37a654552 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 17 Jul 2019 01:56:00 +0800 Subject: [PATCH 20/26] Revert "Add unique IDs for NVDA objects." This reverts commit 4cc443ff9f26a6ffb50e89121ca205a40be3cf97. --- source/NVDAObjects/IAccessible/__init__.py | 1 - source/NVDAObjects/UIA/__init__.py | 1 - source/NVDAObjects/window/__init__.py | 1 - source/documentBase.py | 3 --- 4 files changed, 6 deletions(-) diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index d02d193e5a9..6da88c0cdb8 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -637,7 +637,6 @@ def __init__(self,windowHandle=None,IAccessibleObject=None,IAccessibleChildID=No self.event_objectID=event_objectID self.event_childID=event_childID super(IAccessible,self).__init__(windowHandle=windowHandle) - self.uniqueID = (self.windowHandle, self.IA2UniqueID) try: self.IAccessibleActionObject=IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleAction) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index bd1991b6a01..a2911ebb077 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -955,7 +955,6 @@ def __init__(self,windowHandle=None,UIAElement=None,initialUIACachedPropertyIDs= if not windowHandle: raise InvalidNVDAObject("no windowHandle") super(UIA,self).__init__(windowHandle=windowHandle) - self.uniqueID = self.UIAElement.getRuntimeId() self.initialUIACachedPropertyIDs=initialUIACachedPropertyIDs if initialUIACachedPropertyIDs: diff --git a/source/NVDAObjects/window/__init__.py b/source/NVDAObjects/window/__init__.py index 05472fd01fd..6f7ee3c6db9 100644 --- a/source/NVDAObjects/window/__init__.py +++ b/source/NVDAObjects/window/__init__.py @@ -167,7 +167,6 @@ def __init__(self,windowHandle=None): raise ValueError("invalid or not specified window handle") self.windowHandle=windowHandle super(Window,self).__init__() - self.uniqueID = self.windowHandle def _isEqual(self,other): return super(Window,self)._isEqual(other) and other.windowHandle==self.windowHandle diff --git a/source/documentBase.py b/source/documentBase.py index 5658affdb14..b680e1eafa4 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -18,9 +18,6 @@ class TextContainerObject(ScriptableObject): E.g. NVDAObjects, BrowseModeDocument TreeInterceptors. """ - #: A unique ID to represent this object. - uniqueID = None - def _get_TextInfo(self): raise NotImplementedError From d41476b43dd686c63c22e3c174f36aa6dbb7da5e Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 17 Jul 2019 01:57:16 +0800 Subject: [PATCH 21/26] Revert "Persist review bounds state when an object is regenerated." This reverts commit 291b00e30eb54a08cb0a13aad154049e1730d591. --- source/NVDAObjects/UIA/winConsoleUIA.py | 3 +++ source/NVDAObjects/__init__.py | 4 ++++ source/documentBase.py | 13 ++----------- source/globalVars.py | 5 +---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index 6d72737332a..c84515ceea6 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -209,6 +209,9 @@ class WinConsoleUIA(Terminal): #: Only process text changes every 30 ms, in case the console is getting #: a lot of text. STABILIZE_DELAY = 0.03 + #: Bound review in consoles by default to maintain feature parity + #: with legacy + reviewBounded = True _TextInfo = consoleUIATextInfo #: A queue of typed characters, to be dispatched on C{textChange}. #: This queue allows NVDA to suppress typed passwords when needed. diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index 61c194a0d47..8a25d40dc52 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1270,6 +1270,10 @@ def getSelectedItemsCount(self,maxCount=2): """ return 0 + #: Whether review is constrained to the currently visible text. + #: @type: bool + reviewBounded = False + @script(gesture="kb:NVDA+o", category=scriptCategories.SCRCAT_TEXTREVIEW, # Translators: A gesture description. diff --git a/source/documentBase.py b/source/documentBase.py index b680e1eafa4..6d3b7543516 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -10,7 +10,6 @@ import speech import ui import controlTypes -import globalVars class TextContainerObject(ScriptableObject): """ @@ -30,16 +29,6 @@ def _get_selection(self): def _set_selection(self,info): info.updateSelection() - #: The default state for an object's review bounds. - _defaultReviewBounds = False - - def _get_reviewBounded(self): - return globalVars.reviewBoundsStates.get(self.uniqueID, self._defaultReviewBounds) - - def _set_reviewBounded(self, state): - if not self.uniqueID: - raise NotImplementedError - globalVars.reviewBoundsStates[self.uniqueID] = state class DocumentWithTableNavigation(TextContainerObject,ScriptableObject): """ @@ -217,3 +206,5 @@ def script_toggleIncludeLayoutTables(self,gesture): "kb:control+alt+rightArrow": "nextColumn", "kb:control+alt+leftArrow": "previousColumn", } + + diff --git a/source/globalVars.py b/source/globalVars.py index 715a2461c9c..5a93fec7ba6 100644 --- a/source/globalVars.py +++ b/source/globalVars.py @@ -1,6 +1,6 @@ #globalVars.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2006-2019 NVDA Contributors, Bill Dengler +#Copyright (C) 2006-2007 NVDA Contributors #This file is covered by the GNU General Public License. #See the file COPYING for more details. """global variables module @@ -18,8 +18,6 @@ @type navigatorObject: L{NVDAObjects.NVDAObject} @var navigatorTracksFocus: if true, the navigator object will follow the focus as it changes @type navigatorTracksFocus: boolean -@var reviewBoundsStates: maps object unique IDs to their review bounds states, needed for persistence when an object is regenerated. -@type reviewBoundsStates: dict """ startTime=0 @@ -34,7 +32,6 @@ navigatorObject=None reviewPosition=None reviewPositionObj=None -reviewBoundsStates = dict() lastProgressValue=0 appArgs=None appArgsExtra=None From 8c50383503365e4371c47c7bc0b3ba56c06e6702 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 17 Jul 2019 02:02:56 +0800 Subject: [PATCH 22/26] Revert "Better default review bounds configuration for objects." This reverts commit 2c24358ed5b46b73415470c91965a83592649e11. --- source/NVDAObjects/UIA/winConsoleUIA.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index c84515ceea6..b98bd78103a 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -209,9 +209,6 @@ class WinConsoleUIA(Terminal): #: Only process text changes every 30 ms, in case the console is getting #: a lot of text. STABILIZE_DELAY = 0.03 - #: Bound review in consoles by default to maintain feature parity - #: with legacy - reviewBounded = True _TextInfo = consoleUIATextInfo #: A queue of typed characters, to be dispatched on C{textChange}. #: This queue allows NVDA to suppress typed passwords when needed. @@ -219,8 +216,6 @@ class WinConsoleUIA(Terminal): #: Whether the console got new text lines in its last update. #: Used to determine if typed character/word buffers should be flushed. _hasNewLines = False - #: Bound consoles by default to maintain feature parity with legacy. - _defaultReviewBounds = True #: the caret in consoles can take a while to move on Windows 10 1903 and later. _caretMovementTimeoutMultiplier = 1.5 From cf11cdd4d98f1dc4a1cbf53afc7783ebc65b3dc9 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 17 Jul 2019 02:48:10 +0800 Subject: [PATCH 23/26] Make review bounds state global and bound review by default where supported. --- source/NVDAObjects/__init__.py | 35 --------------------------- source/documentBase.py | 12 +++++++++- source/globalCommands.py | 43 ++++++++++++++++++++++++++++++---- source/globalVars.py | 2 ++ 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index 8a25d40dc52..12b02e04b5b 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -12,7 +12,6 @@ import re import weakref from logHandler import log -from scriptHandler import script import review import eventHandler from displayModel import DisplayModelTextInfo @@ -1269,37 +1268,3 @@ def getSelectedItemsCount(self,maxCount=2): For performance, this method will only count up to the given maxCount number, and if there is one more above that, then sys.maxint is returned stating that many items are selected. """ return 0 - - #: Whether review is constrained to the currently visible text. - #: @type: bool - reviewBounded = False - - @script(gesture="kb:NVDA+o", - category=scriptCategories.SCRCAT_TEXTREVIEW, - # Translators: A gesture description. - description=_(u"Toggles whether review is constrained to the currently visible text")) - def script_toggleReviewBounds(self, gesture): - rp = api.getReviewPosition() - try: - outOfBounds = rp.isOffscreen - except NotImplementedError: - ui.message( - # Translators: Reported when review bound configuration isn't supported for this object. - _(u"Not supported here")) - else: - self.reviewBounded = not self.reviewBounded - if self.reviewBounded: - ui.message( - # Translators: Reported when review is constrained to this - # object's visible text. - _(u"Bounded review") - ) - if outOfBounds: - api.setReviewPosition( - self.makeTextInfo(textInfos.POSITION_CARET)) - else: - ui.message( - # Translators: Reported when review is unconstrained, so all - # of this object's text can be read. - _(u"Unbounded review") - ) diff --git a/source/documentBase.py b/source/documentBase.py index 6d3b7543516..84bc0836ee7 100644 --- a/source/documentBase.py +++ b/source/documentBase.py @@ -21,7 +21,17 @@ def _get_TextInfo(self): raise NotImplementedError def makeTextInfo(self,position): - return self.TextInfo(self,position) + try: + return self.TextInfo(self,position) + except NotImplementedError as e: + if position == textInfos.POSITION_FIRSTVISIBLE: + # Fall back to POSITION_FIRST + return self.makeTextInfo(textInfos.POSITION_FIRST) + elif position == textInfos.POSITION_LASTVISIBLE: + # Fall back to POSITION_LAST + return self.makeTextInfo(textInfos.POSITION_LAST) + else: + raise e def _get_selection(self): return self.makeTextInfo(textInfos.POSITION_SELECTION) diff --git a/source/globalCommands.py b/source/globalCommands.py index 3f7ce94fea2..9a4764d14f3 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -936,13 +936,16 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): """ newInfo = info.copy() res = newInfo.move(unit, direction, endPoint=endPoint) - if newInfo.obj.reviewBounded and newInfo.isOffscreen: - return (info, 0) + try: + if globalVars.reviewBounded and newInfo.isOffscreen: + return (info, 0) + except NotImplementedError: + pass return (newInfo, res) def script_review_top(self,gesture): obj = api.getReviewPosition().obj - pos = textInfos.POSITION_FIRSTVISIBLE if obj.reviewBounded else textInfos.POSITION_FIRST + pos = textInfos.POSITION_FIRSTVISIBLE if globalVars.reviewBounded else textInfos.POSITION_FIRST info = obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) @@ -1004,7 +1007,7 @@ def script_review_nextLine(self,gesture): def script_review_bottom(self,gesture): obj = api.getReviewPosition().obj - pos = textInfos.POSITION_LASTVISIBLE if obj.reviewBounded else textInfos.POSITION_LAST + pos = textInfos.POSITION_LASTVISIBLE if globalVars.reviewBounded else textInfos.POSITION_LAST info = obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) @@ -2258,6 +2261,37 @@ def script_recognizeWithUwpOcr(self, gesture): # Translators: Describes a command. script_recognizeWithUwpOcr.__doc__ = _("Recognizes the content of the current navigator object with Windows 10 OCR") + def script_toggleReviewBounds(self, gesture): + rp = api.getReviewPosition() + outOfBounds = None + try: + outOfBounds = rp.isOffscreen + except NotImplementedError: + ui.message( + # Translators: Reported when review bound configuration isn't supported for this object. + _(u"Not supported here")) + else: + globalVars.reviewBounded = not globalVars.reviewBounded + if globalVars.reviewBounded: + ui.message( + # Translators: Reported when review is constrained to this + # object's visible text. + _(u"Bounded review") + ) + if outOfBounds: + api.setReviewPosition( + api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_CARET)) + else: + ui.message( + # Translators: Reported when review is unconstrained, so all + # of this object's text can be read. + _(u"Unbounded review") + ) + + # Translators: A gesture description. + script_toggleReviewBounds.__doc__ = _(u"Toggles whether review is constrained to the currently visible text") + script_toggleReviewBounds.category=SCRCAT_TEXTREVIEW + __gestures = { # Basic "kb:NVDA+n": "showGui", @@ -2369,6 +2403,7 @@ def script_recognizeWithUwpOcr(self, gesture): "kb:NVDA+numpad1": "reviewMode_previous", "kb(laptop):NVDA+pageDown": "reviewMode_previous", "ts(object):2finger_flickDown": "reviewMode_previous", + "kb:NVDA+o": "toggleReviewBounds", # Mouse "kb:numpadDivide": "leftMouseClick", diff --git a/source/globalVars.py b/source/globalVars.py index 5a93fec7ba6..dd7846941c7 100644 --- a/source/globalVars.py +++ b/source/globalVars.py @@ -18,6 +18,7 @@ @type navigatorObject: L{NVDAObjects.NVDAObject} @var navigatorTracksFocus: if true, the navigator object will follow the focus as it changes @type navigatorTracksFocus: boolean +@var reviewBounded: Whether the review cursor is bounded to the currently visible text. """ startTime=0 @@ -32,6 +33,7 @@ navigatorObject=None reviewPosition=None reviewPositionObj=None +reviewBounded = True lastProgressValue=0 appArgs=None appArgsExtra=None From 11f0d59d30596fa08d4ac254f62a08c3992e30d4 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Wed, 17 Jul 2019 18:39:35 +0800 Subject: [PATCH 24/26] Disable bounds checking if we're currently offscreen to avoid getting stuck out of bounds. --- source/globalCommands.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/globalCommands.py b/source/globalCommands.py index 9a4764d14f3..73f04727394 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -934,6 +934,12 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): Nondestructively moves a textInfo and returns a (newInfo, res) tuple. Res is 0 and the original C{textInfo} is returned if the move would be out of bounds. """ + try: + if info.isOffscreen: + res = info.move(unit, direction, endPoint=endPoint) + return (info, res) + except NotImplementedError: + pass newInfo = info.copy() res = newInfo.move(unit, direction, endPoint=endPoint) try: From bd33c921844c78f5a035fb41491f02468f0516ea Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 18 Jul 2019 01:02:54 +0800 Subject: [PATCH 25/26] In UIA consoles, diff the entire buffer instead of just the visible ranges. This is likely a little slower, but is more stable. --- source/NVDAObjects/UIA/winConsoleUIA.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index b98bd78103a..df01c3cd93f 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -272,9 +272,12 @@ def script_flush_queuedChars(self, gesture): def _getTextLines(self): # Filter out extraneous empty lines from UIA - ptr = self.UIATextPattern.GetVisibleRanges() - res = [ptr.GetElement(i).GetText(-1) for i in range(ptr.length)] - return res + return ( + self.makeTextInfo(textInfos.POSITION_ALL) + ._rangeObj.getText(-1) + .rstrip() + .split("\r\n") + ) def _calculateNewText(self, newLines, oldLines): self._hasNewLines = ( From f3ff4a2654be72f1c76ce82ef59e83e779418ec8 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Thu, 18 Jul 2019 01:12:14 +0800 Subject: [PATCH 26/26] Fix top/bottom review scripts. --- source/globalCommands.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/source/globalCommands.py b/source/globalCommands.py index 73f04727394..46fd5a231c7 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -950,9 +950,14 @@ def _moveWithBoundsChecking(self, info, unit, direction, endPoint=None): return (newInfo, res) def script_review_top(self,gesture): - obj = api.getReviewPosition().obj - pos = textInfos.POSITION_FIRSTVISIBLE if globalVars.reviewBounded else textInfos.POSITION_FIRST - info = obj.makeTextInfo(pos) + rp = api.getReviewPosition() + pos = textInfos.POSITION_FIRST + try: + if globalVars.reviewBounded and not rp.isOffscreen: + pos = textInfos.POSITION_FIRSTVISIBLE + except NotImplementedError: + pass + info = rp.obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) ui.reviewMessage(_("Top")) @@ -1012,9 +1017,14 @@ def script_review_nextLine(self,gesture): script_review_nextLine.category=SCRCAT_TEXTREVIEW def script_review_bottom(self,gesture): - obj = api.getReviewPosition().obj - pos = textInfos.POSITION_LASTVISIBLE if globalVars.reviewBounded else textInfos.POSITION_LAST - info = obj.makeTextInfo(pos) + rp = api.getReviewPosition() + pos = textInfos.POSITION_LAST + try: + if globalVars.reviewBounded and not rp.isOffscreen: + pos = textInfos.POSITION_LASTVISIBLE + except NotImplementedError: + pass + info = rp.obj.makeTextInfo(pos) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) ui.reviewMessage(_("Bottom"))