diff --git a/source/NVDAObjects/IAccessible/mscandui.py b/source/NVDAObjects/IAccessible/mscandui.py index 865b43aa057..9bc92cddcd2 100755 --- a/source/NVDAObjects/IAccessible/mscandui.py +++ b/source/NVDAObjects/IAccessible/mscandui.py @@ -160,7 +160,7 @@ def event_show(self): reportSelectedCandidate(item) return elif config.conf["reviewCursor"]["followFocus"]: - api.setNavigatorObject(candidateList) + api.setNavigatorObject(candidateList, isFocus=True) elif role==controlTypes.ROLE_MENUBUTTON: item=candidateList.firstChild.next.next item=MSCandUI21_candidateMenuItem(IAccessibleObject=item.IAccessibleObject,IAccessibleChildID=item.IAccessibleChildID) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 11b69c08df3..7d124db7941 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -1513,7 +1513,7 @@ def event_UIA_elementSelected(self): focusControllerFor=api.getFocusObject().controllerFor if len(focusControllerFor)>0 and focusControllerFor[0].appModule is self.appModule and self.name: speech.cancelSpeech() - api.setNavigatorObject(self) + api.setNavigatorObject(self, isFocus=True) self.reportFocus() # Display results as flash messages. braille.handler.message(braille.getBrailleTextForProperties(name=self.name, role=self.role, positionInfo=self.positionInfo)) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index b857970c23b..603c61934cf 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -958,10 +958,16 @@ def event_foreground(self): """ speech.cancelSpeech() - def event_becomeNavigatorObject(self): + def event_becomeNavigatorObject(self, isFocus=False): """Called when this object becomes the navigator object. + @param isFocus: true if the navigator object was set due to a focus change. + @type isFocus: bool """ - braille.handler.handleReviewMove() + # When the navigator object follows the focus and braille is auto tethered to review, + # we should not update braille with the new review position as a tether to focus is due. + if braille.handler.shouldAutoTether and isFocus: + return + braille.handler.handleReviewMove(shouldAutoTether=not isFocus) def event_valueChange(self): if self is api.getFocusObject(): diff --git a/source/NVDAObjects/window/winConsole.py b/source/NVDAObjects/window/winConsole.py index d36ba7e57a1..65df9117c1e 100644 --- a/source/NVDAObjects/window/winConsole.py +++ b/source/NVDAObjects/window/winConsole.py @@ -19,7 +19,7 @@ def _get_TextInfo(self): return winConsoleHandler.WinConsoleTextInfo return super(WinConsole,self).TextInfo - def event_becomeNavigatorObject(self): + def event_becomeNavigatorObject(self, isFocus=False): if winConsoleHandler.consoleObject is not self: if winConsoleHandler.consoleObject: winConsoleHandler.disconnectConsole() @@ -28,7 +28,7 @@ def event_becomeNavigatorObject(self): # The user is returning to the focus object with object navigation. # The focused console should always be monitored if possible. self.startMonitoring() - super(WinConsole,self).event_becomeNavigatorObject() + super(WinConsole,self).event_becomeNavigatorObject(isFocus=isFocus) def event_gainFocus(self): if winConsoleHandler.consoleObject is not self: diff --git a/source/api.py b/source/api.py index 2745a434fa9..ca10b1009cb 100644 --- a/source/api.py +++ b/source/api.py @@ -177,13 +177,22 @@ def getReviewPosition(): globalVars.reviewPosition,globalVars.reviewPositionObj=review.getPositionForCurrentMode(obj) return globalVars.reviewPosition -def setReviewPosition(reviewPosition,clearNavigatorObject=True): - """Sets a TextInfo instance as the review position. if clearNavigatorObject is true, It sets the current navigator object to None so that the next time the navigator object is asked for it fetches it from the review position. +def setReviewPosition(reviewPosition,clearNavigatorObject=True,isCaret=False): + """Sets a TextInfo instance as the review position. + @param clearNavigatorObject: if true, It sets the current navigator object to C{None}. + In that case, the next time the navigator object is asked for it fetches it from the review position. + @type clearNavigatorObject: bool + @param isCaret: Whether the review position is changed due to caret following. + @type isCaret: bool """ globalVars.reviewPosition=reviewPosition.copy() globalVars.reviewPositionObj=reviewPosition.obj if clearNavigatorObject: globalVars.navigatorObject=None - braille.handler.handleReviewMove() + # When the review cursor follows the caret and braille is auto tethered to review, + # we should not update braille with the new review position as a tether to focus is due. + if braille.handler.shouldAutoTether and isCaret: + return + braille.handler.handleReviewMove(shouldAutoTether=not isCaret) def getNavigatorObject(): """Gets the current navigator object. Navigator objects can be used to navigate around the operating system (with the number pad) with out moving the focus. If the navigator object is not set, it fetches it from the review position. @@ -227,7 +236,7 @@ def setNavigatorObject(obj,isFocus=False): if isFocus: globalVars.reviewPosition=obj.treeInterceptor.makeTextInfo(textInfos.POSITION_CARET) globalVars.reviewPositionObj=globalVars.reviewPosition - eventHandler.executeEvent("becomeNavigatorObject",obj) + eventHandler.executeEvent("becomeNavigatorObject",obj,isFocus=isFocus) def isTypingProtected(): """Checks to see if key echo should be suppressed because the focus is currently on an object that has its protected state set. diff --git a/source/appModules/explorer.py b/source/appModules/explorer.py index e1c4ffd8898..24f6afe2947 100644 --- a/source/appModules/explorer.py +++ b/source/appModules/explorer.py @@ -41,7 +41,7 @@ class SuggestionListItem(UIA): def event_UIA_elementSelected(self): speech.cancelSpeech() - api.setNavigatorObject(self) + api.setNavigatorObject(self, isFocus=True) self.reportFocus() super(SuggestionListItem,self).event_UIA_elementSelected() diff --git a/source/appModules/lockapp.py b/source/appModules/lockapp.py index 092e504e8d1..4d66e930b61 100644 --- a/source/appModules/lockapp.py +++ b/source/appModules/lockapp.py @@ -90,7 +90,7 @@ def event_appModule_gainFocus(self): # Move the review cursor so others can't access its previous position. self._oldReviewPos = api.getReviewPosition() self._oldReviewObj = self._oldReviewPos.obj - api.setNavigatorObject(eventHandler.lastQueuedFocusObject) + api.setNavigatorObject(eventHandler.lastQueuedFocusObject, isFocus=True) def event_appModule_loseFocus(self): if not config.conf["reviewCursor"]["followFocus"]: diff --git a/source/appModules/skype.py b/source/appModules/skype.py index 698532a84e3..f0790d863c1 100644 --- a/source/appModules/skype.py +++ b/source/appModules/skype.py @@ -160,7 +160,8 @@ def reviewRecentMessage(self, index): ui.message(_("No message yet")) return message = self.getChild(count - index) - api.setNavigatorObject(message) + # Reviewing a message should not auto tether + api.setNavigatorObject(message, isFocus=True) self.reportMessage(message.name) class Notification(NVDAObjects.behaviors.Notification): diff --git a/source/braille.py b/source/braille.py index a948f0e8d62..0d2ef905837 100644 --- a/source/braille.py +++ b/source/braille.py @@ -1473,8 +1473,18 @@ def formatCellsForLog(cells): for cell in cells]) class BrailleHandler(baseObject.AutoPropertyObject): + TETHER_AUTO = "auto" TETHER_FOCUS = "focus" TETHER_REVIEW = "review" + tetherValues=[ + # Translators: The label for a braille setting indicating that braille should be + # tethered to focus or review cursor automatically. + (TETHER_AUTO,_("automatically")), + # Translators: The label for a braille setting indicating that braille should be tethered to focus. + (TETHER_FOCUS,_("to focus")), + # Translators: The label for a braille setting indicating that braille should be tethered to the review cursor. + (TETHER_REVIEW,_("to review")) + ] def __init__(self): self.display = None @@ -1492,6 +1502,7 @@ def __init__(self): self._cells = [] self._cursorBlinkTimer = None config.configProfileSwitched.register(self.handleConfigProfileSwitch) + self._tether = config.conf["braille"]["tetherTo"] def terminate(self): if self._messageCallLater: @@ -1506,18 +1517,29 @@ def terminate(self): self.display = None _BgThread.stop() + def getTether(self): + return self._tether + def _get_tether(self): - return config.conf["braille"]["tetherTo"] + """@deprecated: Use L{getTether instead.""" + return self.getTether() - def _set_tether(self, tether): - if tether == config.conf["braille"]["tetherTo"]: + def setTether(self, tether, auto=False): + if auto and not self.shouldAutoTether: + return + if not auto: + config.conf["braille"]["tetherTo"] = tether + if tether == self._tether: return - config.conf["braille"]["tetherTo"] = tether + self._tether = tether self.mainBuffer.clear() - if tether == self.TETHER_REVIEW: - self.handleReviewMove() - else: - self.handleGainFocus(api.getFocusObject()) + + def _set_tether(self, tether): + """@deprecated: Use L{setTether instead.""" + self.setTether(tether, auto=False) + + def _get_shouldAutoTether(self): + return self.enabled and config.conf["braille"]["autoTether"] def setDisplayByName(self, name, isFallback=False): if not name: @@ -1682,10 +1704,12 @@ def _dismissMessage(self): self._messageCallLater = None self.update() - def handleGainFocus(self, obj): + def handleGainFocus(self, obj, shouldAutoTether=True): if not self.enabled: return - if self.tether != self.TETHER_FOCUS: + if shouldAutoTether: + self.setTether(self.TETHER_FOCUS, auto=True) + if self._tether != self.TETHER_FOCUS: return self._doNewObject(itertools.chain(getFocusContextRegions(obj, oldFocusRegions=self.mainBuffer.regions), getFocusRegions(obj))) @@ -1714,17 +1738,20 @@ def _doNewObject(self, regions): elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage: self._dismissMessage() - def handleCaretMove(self, obj): + def handleCaretMove(self, obj, shouldAutoTether=True): if not self.enabled: return - if self.tether != self.TETHER_FOCUS: - return - if not self.mainBuffer.regions: - return - region = self.mainBuffer.regions[-1] - if region.obj is not obj: + prevTether = self._tether + if shouldAutoTether: + self.setTether(self.TETHER_FOCUS, auto=True) + if self._tether != self.TETHER_FOCUS: return - region.pendingCaretUpdate=True + region = self.mainBuffer.regions[-1] if self.mainBuffer.regions else None + if region and region.obj==obj: + region.pendingCaretUpdate=True + elif prevTether == self.TETHER_REVIEW: + # The caret moved in a different object than the review position. + self._doNewObject(getFocusRegions(obj, review=False)) def handlePendingCaretUpdate(self): """Checks to see if the final text region needs its caret updated and if so calls _doCursorMove for the region.""" @@ -1784,7 +1811,7 @@ def handleUpdate(self, obj): # There are some objects that require special update behavior even if they have no region. # This only applies when tethered to focus, because tethering to review shows only one object at a time, # which always has a braille region associated with it. - if self.tether != self.TETHER_FOCUS: + if self._tether != self.TETHER_FOCUS: return # Late import to avoid circular import. from NVDAObjects import NVDAObject @@ -1800,12 +1827,14 @@ def handleUpdate(self, obj): elif self.buffer is self.messageBuffer and keyboardHandler.keyCounter>self._keyCountForLastMessage: self._dismissMessage() - def handleReviewMove(self): + def handleReviewMove(self, shouldAutoTether=True): if not self.enabled: return - if self.tether != self.TETHER_REVIEW: - return reviewPos = api.getReviewPosition() + if shouldAutoTether: + self.setTether(self.TETHER_REVIEW, auto=True) + if self._tether != self.TETHER_REVIEW: + return region = self.mainBuffer.regions[-1] if self.mainBuffer.regions else None if region and region.obj == reviewPos.obj: self._doCursorMove(region) @@ -1893,9 +1922,9 @@ def initialize(): # Braille is disabled or focus/review hasn't yet been initialised. return if handler.tether == handler.TETHER_FOCUS: - handler.handleGainFocus(api.getFocusObject()) + handler.handleGainFocus(api.getFocusObject(), shouldAutoTether=False) else: - handler.handleReviewMove() + handler.handleReviewMove(shouldAutoTether=False) def pumpAll(): """Runs tasks at the end of each core cycle. For now just caret updates.""" diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 9a23add3cfc..63e78964c76 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -61,6 +61,7 @@ noMessageTimeout = boolean(default=false) messageTimeout = integer(default=4,min=0,max=20) tetherTo = string(default="focus") + autoTether = boolean(default=true) readByParagraph = boolean(default=false) wordWrap = boolean(default=true) focusContextPresentation = option("changedContext", "fill", "scroll", default="changedContext") diff --git a/source/eventHandler.py b/source/eventHandler.py index 2ce99b9f06a..af95d062704 100755 --- a/source/eventHandler.py +++ b/source/eventHandler.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) 2007-2014 NV Access Limited +#Copyright (C) 2007-2017 NV Access Limited, Babbage B.V. import threading import queueHandler @@ -16,6 +16,7 @@ import globalPluginHandler import config import winUser +import extensionPoints #Some dicts to store event counts by name and or obj _pendingEventCountsByName={} @@ -95,7 +96,15 @@ def __init__(self, eventName, obj, kwargs): def next(self): func, args = next(self._gen) - return func(*args, **self.kwargs) + try: + return func(*args, **self.kwargs) + except TypeError: + log.warning("Could not execute function {func} defined in {module} module due to unsupported kwargs: {kwargs}".format( + func=func.__name__, + module=func.__module__ or "unknown", + kwargs=self.kwargs + ), exc_info=True) + return extensionPoints.callWithSupportedKwargs(func, *args, **self.kwargs) def gen(self, eventName, obj): funcName = "event_%s" % eventName @@ -170,7 +179,7 @@ def doPreGainFocus(obj,sleepMode=False): if obj.treeInterceptor and obj.treeInterceptor.isReady and hasattr(obj.treeInterceptor,"event_treeInterceptor_gainFocus"): obj.treeInterceptor.event_treeInterceptor_gainFocus() return True - + def doPreDocumentLoadComplete(obj): focusObject=api.getFocusObject() if (not obj.treeInterceptor or not obj.treeInterceptor.isAlive or obj.treeInterceptor.shouldPrepare) and (obj==focusObject or obj in api.getFocusAncestors()): diff --git a/source/globalCommands.py b/source/globalCommands.py index 2a05c0c4163..05404de7068 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -724,13 +724,14 @@ def script_moveNavigatorObjectToMouse(self,gesture): def script_reviewMode_next(self,gesture): label=review.nextMode() if label: - ui.message(label) + ui.reviewMessage(label) pos=api.getReviewPosition().copy() pos.expand(textInfos.UNIT_LINE) + braille.handler.setTether(braille.handler.TETHER_REVIEW, auto=True) speech.speakTextInfo(pos) else: # Translators: reported when there are no other available review modes for this object - ui.message(_("No next review mode")) + ui.reviewMessage(_("No next review mode")) # Translators: Script help message for next review mode command. script_reviewMode_next.__doc__=_("Switches to the next review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") script_reviewMode_next.category=SCRCAT_TEXTREVIEW @@ -738,13 +739,14 @@ def script_reviewMode_next(self,gesture): def script_reviewMode_previous(self,gesture): label=review.nextMode(prev=True) if label: - ui.message(label) + ui.reviewMessage(label) pos=api.getReviewPosition().copy() pos.expand(textInfos.UNIT_LINE) + braille.handler.setTether(braille.handler.TETHER_REVIEW, auto=True) speech.speakTextInfo(pos) else: # Translators: reported when there are no other available review modes for this object - ui.message(_("No previous review mode")) + ui.reviewMessage(_("No previous review mode")) # Translators: Script help message for previous review mode command. script_reviewMode_previous.__doc__=_("Switches to the previous review mode (e.g. object, document or screen) and positions the review position at the point of the navigator object") script_reviewMode_previous.category=SCRCAT_TEXTREVIEW @@ -768,7 +770,7 @@ def script_navigatorObject_current(self,gesture): if not isinstance(curObject,NVDAObject): # Translators: Reported when the user tries to perform a command related to the navigator object # but there is no current navigator object. - speech.speakMessage(_("No navigator object")) + ui.reviewMessage(_("No navigator object")) return if scriptHandler.getLastScriptRepeatCount()>=1: if curObject.TextInfo!=NVDAObjectTextInfo: @@ -970,7 +972,7 @@ def script_review_top(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_FIRST) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) - speech.speakMessage(_("Top")) + ui.reviewMessage(_("Top")) speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to top line command. script_review_top.__doc__=_("Moves the review cursor to the top line of the current navigator object and speaks it") @@ -996,6 +998,8 @@ def script_review_previousLine(self,gesture): def script_review_currentLine(self,gesture): info=api.getReviewPosition().copy() info.expand(textInfos.UNIT_LINE) + # Explicitly tether here + braille.handler.setTether(braille.handler.TETHER_REVIEW, auto=True) scriptCount=scriptHandler.getLastScriptRepeatCount() if scriptCount==0: speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) @@ -1004,7 +1008,7 @@ def script_review_currentLine(self,gesture): # Translators: Input help mode message for read current line under review cursor command. script_review_currentLine.__doc__=_("Reports the line of the current navigator object where the review cursor is situated. If this key is pressed twice, the current line will be spelled. Pressing three times will spell the line using character descriptions.") script_review_currentLine.category=SCRCAT_TEXTREVIEW - + def script_review_nextLine(self,gesture): info=api.getReviewPosition().copy() info.expand(textInfos.UNIT_LINE) @@ -1026,7 +1030,7 @@ def script_review_bottom(self,gesture): info=api.getReviewPosition().obj.makeTextInfo(textInfos.POSITION_LAST) api.setReviewPosition(info) info.expand(textInfos.UNIT_LINE) - speech.speakMessage(_("Bottom")) + ui.reviewMessage(_("Bottom")) speech.speakTextInfo(info,unit=textInfos.UNIT_LINE,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to bottom line command. script_review_bottom.__doc__=_("Moves the review cursor to the bottom line of the current navigator object and speaks it") @@ -1051,6 +1055,8 @@ def script_review_previousWord(self,gesture): def script_review_currentWord(self,gesture): info=api.getReviewPosition().copy() info.expand(textInfos.UNIT_WORD) + # Explicitly tether here + braille.handler.setTether(braille.handler.TETHER_REVIEW, auto=True) scriptCount=scriptHandler.getLastScriptRepeatCount() if scriptCount==0: speech.speakTextInfo(info,reason=controlTypes.REASON_CARET,unit=textInfos.UNIT_WORD) @@ -1082,7 +1088,7 @@ def script_review_startOfLine(self,gesture): info.collapse() api.setReviewPosition(info) info.expand(textInfos.UNIT_CHARACTER) - speech.speakMessage(_("Left")) + ui.reviewMessage(_("Left")) speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to start of current line command. script_review_startOfLine.__doc__=_("Moves the review cursor to the first character of the line where it is situated in the current navigator object and speaks it") @@ -1112,6 +1118,8 @@ def script_review_previousCharacter(self,gesture): def script_review_currentCharacter(self,gesture): info=api.getReviewPosition().copy() info.expand(textInfos.UNIT_CHARACTER) + # Explicitly tether here + braille.handler.setTether(braille.handler.TETHER_REVIEW, auto=True) scriptCount=scriptHandler.getLastScriptRepeatCount() if scriptCount==0: speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) @@ -1156,7 +1164,7 @@ def script_review_endOfLine(self,gesture): info.move(textInfos.UNIT_CHARACTER,-1) api.setReviewPosition(info) info.expand(textInfos.UNIT_CHARACTER) - speech.speakMessage(_("Right")) + ui.reviewMessage(_("Right")) speech.speakTextInfo(info,unit=textInfos.UNIT_CHARACTER,reason=controlTypes.REASON_CARET) # Translators: Input help mode message for move review cursor to end of current line command. script_review_endOfLine.__doc__=_("Moves the review cursor to the last character of the line where it is situated in the current navigator object and speaks it") @@ -1333,7 +1341,7 @@ def script_reportCurrentFocus(self,gesture): else: speech.speakSpelling(focusObject.name) else: - speech.speakMessage(_("No focus")) + ui.message(_("No focus")) # Translators: Input help mode message for report current focus command. script_reportCurrentFocus.__doc__ = _("Reports the object with focus. If pressed twice, spells the information") script_reportCurrentFocus.category=SCRCAT_FOCUS @@ -1689,16 +1697,33 @@ def script_toggleSpeechViewer(self,gesture): script_toggleSpeechViewer.category=SCRCAT_TOOLS def script_braille_toggleTether(self, gesture): - if braille.handler.tether == braille.handler.TETHER_FOCUS: - braille.handler.tether = braille.handler.TETHER_REVIEW - # Translators: One of the options for tethering braille (see the comment on "braille tethered to" message for more information). - tetherMsg = _("review") - else: - braille.handler.tether = braille.handler.TETHER_FOCUS - # Translators: One of the options for tethering braille (see the comment on "braille tethered to" message for more information). - tetherMsg = _("focus") - # Translators: Reports which position braille is tethered to (braille can be tethered to either focus or review position). - ui.message(_("Braille tethered to %s") % tetherMsg) + values = [x[0] for x in braille.handler.tetherValues] + labels = [x[1] for x in braille.handler.tetherValues] + try: + index = values.index( + braille.handler.TETHER_AUTO if config.conf["braille"]["autoTether"] else config.conf["braille"]["tetherTo"] + ) + except: + index=0 + newIndex = (index+1) % len(values) + newTetherChoice = values[newIndex] + if newTetherChoice==braille.handler.TETHER_AUTO: + config.conf["braille"]["autoTether"] = True + config.conf["braille"]["tetherTo"] = braille.handler.TETHER_FOCUS + else: + config.conf["braille"]["autoTether"] = False + braille.handler.setTether(newTetherChoice, auto=False) + if newTetherChoice==braille.handler.TETHER_REVIEW: + braille.handler.handleReviewMove(shouldAutoTether=False) + else: + focus = api.getFocusObject() + if focus.treeInterceptor and not focus.treeInterceptor.passThrough: + braille.handler.handleGainFocus(focus.treeInterceptor,shouldAutoTether=False) + else: + braille.handler.handleGainFocus(focus,shouldAutoTether=False) + # Translators: Reports which position braille is tethered to + # (braille can be tethered automatically or to either focus or review position). + ui.message(_("Braille tethered %s") % labels[newIndex]) # Translators: Input help mode message for toggle braille tether to command (tethered means connected to or follows). script_braille_toggleTether.__doc__ = _("Toggle tethering of braille between the focus and the review position") script_braille_toggleTether.category=SCRCAT_BRAILLE @@ -1742,7 +1767,7 @@ def script_braille_cycleCursorShape(self, gesture): ui.message(_("Braille cursor is turned off")) return shapes = [s[0] for s in braille.CURSOR_SHAPES] - if braille.handler.tether == braille.handler.TETHER_FOCUS: + if braille.handler.getTether() == braille.handler.TETHER_FOCUS: cursorShape = "cursorShapeFocus" else: cursorShape = "cursorShapeReview" @@ -1903,18 +1928,26 @@ def script_braille_dots(self, gesture): script_braille_dots.category=SCRCAT_BRAILLE def script_braille_toFocus(self, gesture): - if braille.handler.tether == braille.handler.TETHER_REVIEW: + braille.handler.setTether(braille.handler.TETHER_FOCUS, auto=True) + if braille.handler.getTether() == braille.handler.TETHER_REVIEW: self.script_navigatorObject_toFocus(gesture) else: - if not braille.handler.mainBuffer.regions: - return - region = braille.handler.mainBuffer.regions[-1] - braille.handler.mainBuffer.focus(region) - if region.brailleCursorPos is not None: - braille.handler.mainBuffer.scrollTo(region, region.brailleCursorPos) - elif region.brailleSelectionStart is not None: - braille.handler.mainBuffer.scrollTo(region, region.brailleSelectionStart) - braille.handler.mainBuffer.updateDisplay() + obj = api.getFocusObject() + region = braille.handler.mainBuffer.regions[-1] if braille.handler.mainBuffer.regions else None + if region and region.obj==obj: + braille.handler.mainBuffer.focus(region) + if region.brailleCursorPos is not None: + braille.handler.mainBuffer.scrollTo(region, region.brailleCursorPos) + elif region.brailleSelectionStart is not None: + braille.handler.mainBuffer.scrollTo(region, region.brailleSelectionStart) + braille.handler.mainBuffer.updateDisplay() + else: + # We just tethered to focus from review, + # Handle this case as we just focused the object + if obj.treeInterceptor and not obj.treeInterceptor.passThrough: + braille.handler.handleGainFocus(obj.treeInterceptor,shouldAutoTether=False) + else: + braille.handler.handleGainFocus(obj,shouldAutoTether=False) # Translators: Input help mode message for a braille command. script_braille_toFocus.__doc__= _("Moves the braille display to the current focus") script_braille_toFocus.category=SCRCAT_BRAILLE diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 7eab2efefb2..63b36842e26 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -1654,13 +1654,12 @@ def makeSettings(self, settingsSizer): self.messageTimeoutEdit.Disable() # Translators: The label for a setting in braille settings to set whether braille should be tethered to focus or review cursor. - tetherListText = _("B&raille tethered to:") + tetherListText = _("Tether B&raille:") # Translators: The value for a setting in the braille settings, to set whether braille should be tethered to focus or review cursor. - self.tetherValues=[("focus",_("focus")),("review",_("review"))] - tetherChoices = [x[1] for x in self.tetherValues] + tetherChoices = [x[1] for x in braille.handler.tetherValues] self.tetherList = sHelper.addLabeledControl(tetherListText, wx.Choice, choices=tetherChoices) - tetherConfig=braille.handler.tether - selection = (x for x,y in enumerate(self.tetherValues) if y[0]==tetherConfig).next() + tetherChoice=braille.handler.TETHER_AUTO if config.conf["braille"]["autoTether"] else config.conf["braille"]["tetherTo"] + selection = (x for x,y in enumerate(braille.handler.tetherValues) if y[0]==tetherChoice).next() try: self.tetherList.SetSelection(selection) except: @@ -1709,7 +1708,13 @@ def onOk(self, evt): config.conf["braille"]["cursorShapeReview"] = self.cursorShapes[self.cursorShapeReviewList.GetSelection()] config.conf["braille"]["noMessageTimeout"] = self.noMessageTimeoutCheckBox.GetValue() config.conf["braille"]["messageTimeout"] = self.messageTimeoutEdit.GetValue() - braille.handler.tether = self.tetherValues[self.tetherList.GetSelection()][0] + tetherChoice = braille.handler.tetherValues[self.tetherList.GetSelection()][0] + if tetherChoice==braille.handler.TETHER_AUTO: + config.conf["braille"]["autoTether"] = True + config.conf["braille"]["tetherTo"] = braille.handler.TETHER_FOCUS + else: + config.conf["braille"]["autoTether"] = False + braille.handler.setTether(tetherChoice, auto=False) config.conf["braille"]["readByParagraph"] = self.readByParagraphCheckBox.Value config.conf["braille"]["wordWrap"] = self.wordWrapCheckBox.Value config.conf["braille"]["focusContextPresentation"] = self.focusContextPresentationValues[self.focusContextPresentationList.GetSelection()] diff --git a/source/review.py b/source/review.py index 2325d3c93ce..66adb7716df 100644 --- a/source/review.py +++ b/source/review.py @@ -167,4 +167,4 @@ def handleCaretMove(pos): info=obj.makeTextInfo(textInfos.POSITION_CARET) except (NotImplementedError,RuntimeError): return - api.setReviewPosition(info) + api.setReviewPosition(info, isCaret=True) diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py index b90ffbaea5a..8870e7f88e5 100644 --- a/source/sayAllHandler.py +++ b/source/sayAllHandler.py @@ -81,7 +81,7 @@ def readObjectsHelper_generator(obj): lastReceivedIndex=receivedIndex lastReceivedObj=objIndexMap.get(lastReceivedIndex) if lastReceivedObj is not None: - api.setNavigatorObject(lastReceivedObj) + api.setNavigatorObject(lastReceivedObj, isFocus=lastSayAllMode==CURSOR_CARET) #Clear old objects from the map for i in objIndexMap.keys(): if i<=lastReceivedIndex: @@ -151,7 +151,7 @@ def readTextHelper_generator(cursor): if cursor==CURSOR_CARET: updater.updateCaret() if cursor!=CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]: - api.setReviewPosition(updater) + api.setReviewPosition(updater, isCaret=cursor==CURSOR_CARET) elif not keepReading and lastReceivedIndex==lastSentIndex: # All text has been sent to the synth. # Turn the page and start again if the object supports it. diff --git a/source/treeInterceptorHandler.py b/source/treeInterceptorHandler.py index 307bdb6b8bd..199fb4c0b8c 100644 --- a/source/treeInterceptorHandler.py +++ b/source/treeInterceptorHandler.py @@ -116,7 +116,7 @@ def _set_passThrough(self, state): if review.getCurrentMode()=='document': # if focus is in this treeInterceptor and review mode is document, turning on passThrough should force object review review.setCurrentMode('object') - api.setNavigatorObject(focusObj) + api.setNavigatorObject(focusObj, isFocus=True) braille.handler.handleGainFocus(api.getFocusObject()) else: obj=api.getNavigatorObject() diff --git a/source/ui.py b/source/ui.py index 1bc626edd68..12c697da63f 100644 --- a/source/ui.py +++ b/source/ui.py @@ -1,6 +1,6 @@ #ui.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2008-2016 NV Access Limited, Dinesh Kaushal, Davy Kager +#Copyright (C) 2008-2017 NV Access Limited, Dinesh Kaushal, Davy Kager, Babbage B.V. #This file is covered by the GNU General Public License. #See the file COPYING for more details. @@ -68,10 +68,10 @@ def message(text): def reviewMessage(text): """Present a message from review or object navigation to the user. - The message will always be presented in speech, and also in braille if it is tethered to review. + The message will always be presented in speech, and also in braille if it is tethered to review or when auto tethering is on. @param text: The text of the message. @type text: str """ speech.speakMessage(text) - if braille.handler.tether == braille.handler.TETHER_REVIEW: + if braille.handler.shouldAutoTether or braille.handler.getTether() == braille.handler.TETHER_REVIEW: braille.handler.message(text) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index b7713621361..a888a839c2c 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -328,8 +328,10 @@ The object currently being reviewed is called the navigator object. Once you navigate to an object, you can review its content using the [text review commands #ReviewingText] while in [Object review mode #ObjectReview]. By default, the navigator object moves along with the System focus, though this behavior can be toggled on and off. -Note that braille follows the [focus #SystemFocus] and [caret #SystemCaret] by default, rather than object navigation and text review. -If you want it to follow object navigation and text review instead, you need to [configure braille to be tethered to #BrailleTether] review. +Note that braille follows both the [focus #SystemFocus] and [caret #SystemCaret] as well as object navigation and text review by default. +If you want it to follow the focus and caret only, you need to [configure braille to be tethered to #BrailleTether] focus. +In this case, braille will not follow object navigation and text review. +If you want braille to follow object navigation and text review instead, you need to [configure braille to be tethered to #BrailleTether] review. To navigate by object, use the following commands: @@ -1132,10 +1134,12 @@ Specifying 0 disables displaying of these messages completely. This option allows NVDA messages to be displayed on the braille display indefinitely. %kc:setting -==== Braille Tethered to ====[BrailleTether] +==== Tether Braille ====[BrailleTether] Key: NVDA+control+t -This option allows you to choose whether the braille display will follow the system focus, or whether it follows the navigator object / review cursor. +This option allows you to choose whether the braille display will follow the system focus, the navigator object / review cursor, or both. +When "automatically" is selected, NVDA will follow the system focus and caret by default. +In this case, when the navigator object or the review cursor position is changed by means of explicit user interaction, NVDA will tether to review temporarily, until the focus or the caret changes. ==== Read by Paragraph ==== If enabled, braille will be displayed by paragraphs instead of lines.