Skip to content
Permalink
Browse files

Allow disabling automatic focus of focusable elements in browse mode (#…

…9511)

* Fast browse mode - system focus doesn't follow browse mode focus

* Fix bug when checkboxes and radio button new state is not announced when checked or unchecked

* Renaming configuration setting name to camel case

* Refactoring definitions of passthrough methods

* Refactoring sync focus function

* Always call .scrollIntoView()

* Fixing fast browse mode in Outlook emails

* Fixing Tab behavior

* Fixing bug - some edit boxes in IE impossible to enter

* Refactoring

* Refactoring - better tracking of async gainFocus events

* Fixing exception in event pump

* Rename browse modes

* Set fast browse mode to be default

* Updated bogfixes section

* Add more keystrokes with focus sync

* Renaming legacy browse mode back to traditional browse mode since we won't remove it in the nearby future

* Update user documentation

* Change toggle messages

* Use .uniqueID attribute

* Revert back to IA2UniqueID

* Refactoring

* Refactoring

* Refactoring

* Refactoring

* Adding .uniqueID attribute to NVDAObject implementations

* Adding passthrough gestures with numpadEnter

* Lazy evaluation of uniqueID

* Refactoring

* Adding debug information - to be removed later

* Implement uniqueID on NVDAObject

* Use processID in the uniqueID "of windows, as windowHandles might eventually get reused

* Remove everything reffering to async logic, except for the uniqueID properties

* Make the code somewhat more compact

* Fix issue with checkboxes

* Remove references to uniqueId

* Remove debug logging

* Improve pass through gestures, added some more

* Renames and restructuring code

* Fix some unneeded changes

* Only use lastFocusableObj when applicable

* Update user guide

* Move the auto focus focusable elements checkbox from browseMode settings to Advanced settings.

* Update what's new.
  • Loading branch information...
leonardder authored and michaelDCurran committed May 15, 2019
1 parent c20a503 commit 7ffc65fef57889bb1a8a47ae40d6d06e35861397
@@ -198,7 +198,7 @@ def report(self,readUnit=None):
speech.speakTextInfo(info, reason=controlTypes.REASON_FOCUS)

def activate(self):
self.textInfo.obj._activatePosition(self.textInfo)
self.textInfo.obj._activatePosition(info=self.textInfo)

def moveTo(self):
info=self.textInfo.copy()
@@ -466,7 +466,7 @@ def _activateNVDAObject(self, obj):
except NotImplementedError:
log.debugWarning("doAction not implemented")

def _activatePosition(self,obj=None):
def _activatePosition(self, obj=None):
if not obj:
obj=self.currentNVDAObject
if not obj:
@@ -489,10 +489,30 @@ def _activatePosition(self,obj=None):
self._activateNVDAObject(obj)

def script_activatePosition(self,gesture):
self._activatePosition()
if config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
self._activatePosition()
else:
self._focusLastFocusableObject(activatePosition=True)
# Translators: the description for the activatePosition script on browseMode documents.
script_activatePosition.__doc__ = _("Activates the current object in the document")

def _focusLastFocusableObject(self, activatePosition=False):
obj = self._lastFocusableObj
if not obj:
return
if obj!=self.rootNVDAObject and self._shouldSetFocusToObj(obj) and obj!= api.getFocusObject():
obj.setFocus()
speech.speakObject(obj,controlTypes.REASON_ONLYCACHE)
if activatePosition:
self._activatePosition(obj=obj)

def script_passThrough(self,gesture):
if not config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
self._focusLastFocusableObject()
gesture.send()
# Translators: the description for the passThrough script on browseMode documents.
script_passThrough.__doc__ = _("Passes gesture through to the application")

def script_disablePassThrough(self, gesture):
if not self.passThrough or self.disableAutoPassThrough:
return gesture.send()
@@ -512,6 +532,17 @@ def script_disablePassThrough(self, gesture):
"kb:space": "activatePosition",
"kb:NVDA+shift+space":"toggleSingleLetterNav",
"kb:escape": "disablePassThrough",
"kb:control+enter": "passThrough",
"kb:control+numpadEnter": "passThrough",
"kb:shift+enter": "passThrough",
"kb:shift+numpadEnter": "passThrough",
"kb:control+shift+enter": "passThrough",
"kb:control+shift+numpadEnter": "passThrough",
"kb:alt+enter": "passThrough",
"kb:alt+numpadEnter": "passThrough",
"kb:applications": "passThrough",
"kb:shift+applications": "passThrough",
"kb:shift+f10": "passThrough",
}

# Add quick navigation scripts.
@@ -1128,6 +1159,7 @@ def __init__(self,obj):
self._lastProgrammaticScrollTime = None
self.documentConstantIdentifier = self.documentConstantIdentifier
self._lastFocusObj = None
self._lastFocusableObj = None
self._hadFirstGainFocus = False
self._enteringFromOutside = True
# We need to cache this because it will be unavailable once the document dies.
@@ -1209,13 +1241,12 @@ def _activateLongDesc(self,controlField):
"""
raise NotImplementedError

def _activatePosition(self, info=None):
obj=None
def _activatePosition(self, obj=None, info=None):
if info:
obj=info.NVDAObjectAtStart
if not obj:
return
super(BrowseModeDocumentTreeInterceptor,self)._activatePosition(obj)
super(BrowseModeDocumentTreeInterceptor,self)._activatePosition(obj=obj)

def _set_selection(self, info, reason=controlTypes.REASON_CARET):
super(BrowseModeDocumentTreeInterceptor, self)._set_selection(info)
@@ -1231,6 +1262,7 @@ def _set_selection(self, info, reason=controlTypes.REASON_CARET):
if reason == controlTypes.REASON_FOCUS:
self._lastCaretMoveWasFocus = True
focusObj = api.getFocusObject()
self._lastFocusableObj = None
if focusObj==self.rootNVDAObject:
return
else:
@@ -1240,10 +1272,14 @@ def _set_selection(self, info, reason=controlTypes.REASON_CARET):
if not obj:
log.debugWarning("Invalid NVDAObjectAtStart")
return
followBrowseModeFocus= config.conf["virtualBuffers"]["autoFocusFocusableElements"]
if obj==self.rootNVDAObject:
return
if focusObj and not eventHandler.isPendingEvents("gainFocus") and focusObj!=self.rootNVDAObject and focusObj != api.getFocusObject() and self._shouldSetFocusToObj(focusObj):
focusObj.setFocus()
if followBrowseModeFocus:
if focusObj and not eventHandler.isPendingEvents("gainFocus") and focusObj!=self.rootNVDAObject and focusObj != api.getFocusObject() and self._shouldSetFocusToObj(focusObj):
focusObj.setFocus()
else:
self._lastFocusableObj = focusObj
obj.scrollIntoView()
if self.programmaticScrollMayFireEvent:
self._lastProgrammaticScrollTime = time.time()
@@ -1299,6 +1335,8 @@ def event_caretMovementFailed(self, obj, nextHandler, gesture=None):

currentExpandedControl=None #: an NVDAObject representing the control that has just been expanded with the collapseOrExpandControl script.
def script_collapseOrExpandControl(self, gesture):
if not config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
self._focusLastFocusableObject()
oldFocus = api.getFocusObject()
oldFocusStates = oldFocus.states
gesture.send()
@@ -1481,6 +1519,13 @@ def event_gainFocus(self, obj, nextHandler):
# This focus change was caused by a virtual caret movement, so don't speak the focused node to avoid double speaking.
# However, we still want to update the speech property cache so that property changes will be spoken properly.
speech.speakObject(obj,controlTypes.REASON_ONLYCACHE)
if (
not config.conf["virtualBuffers"]["autoFocusFocusableElements"]
and self._lastFocusableObj
and obj == self._lastFocusableObj
and obj is not self._lastFocusableObj
):
speech.speakObject(self._lastFocusableObj,controlTypes.REASON_CHANGE)
else:
self._replayFocusEnteredEvents()
return nextHandler()
@@ -139,6 +139,7 @@
autoSayAllOnPageLoad = boolean(default=true)
trapNonCommandGestures = boolean(default=true)
enableOnPageLoad = boolean(default=true)
autoFocusFocusableElements = boolean(default=True)
[touch]
touchTyping = boolean(default=False)
@@ -1569,6 +1569,20 @@ def script_toggleFocusMovesNavigatorObject(self,gesture):
script_toggleFocusMovesNavigatorObject.__doc__=_("Toggles on and off the movement of the navigator object due to focus changes")
script_toggleFocusMovesNavigatorObject.category=SCRCAT_OBJECTNAVIGATION

def script_toggleAutoFocusFocusableElements(self,gesture):
if config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
# Translators: presented when toggled.
state = _("Automatically set system focus to focusable elements off")
config.conf["virtualBuffers"]["autoFocusFocusableElements"]=False
else:
# Translators: presented when toggled.
state = _("Automatically set system focus to focusable elements on")
config.conf["virtualBuffers"]["autoFocusFocusableElements"]=True
ui.message(state)
# Translators: Input help mode message for toggle auto focus focusable elements command.
script_toggleAutoFocusFocusableElements.__doc__=_("Toggles on and off automatic movement of the system focus due to browse mode commands")
script_toggleAutoFocusFocusableElements.category=inputCore.SCRCAT_BROWSEMODE

#added by Rui Batista<ruiandrebatista@gmail.com> to implement a battery status script
def script_say_battery_status(self,gesture):
UNKNOWN_BATTERY_STATUS = 0xFF
@@ -2386,6 +2400,7 @@ def script_recognizeWithUwpOcr(self, gesture):
"kb:NVDA+5": "toggleReportDynamicContentChanges",
"kb:NVDA+6": "toggleCaretMovesReviewCursor",
"kb:NVDA+7": "toggleFocusMovesNavigatorObject",
"kb:NVDA+8": "toggleAutoFocusFocusableElements",
"kb:NVDA+control+t": "braille_toggleTether",

# Synth settings ring
@@ -2037,6 +2037,22 @@ def __init__(self, parent):
self.UIAInMSWordCheckBox.SetValue(config.conf["UIA"]["useInMSWordWhenAvailable"])
self.UIAInMSWordCheckBox.defaultValue = self._getDefaultValue(["UIA", "useInMSWordWhenAvailable"])

# Translators: This is the label for a group of advanced options in the
# Advanced settings panel
label = _("Browse mode")
browseModeGroup = guiHelper.BoxSizerHelper(
parent=self,
sizer=wx.StaticBoxSizer(parent=self, label=label, orient=wx.VERTICAL)
)
sHelper.addItem(browseModeGroup)

# Translators: This is the label for a checkbox in the
# Advanced settings panel.
autoFocusFocusableElementsText = _("Automatically set system &focus to focusable elements")
self.autoFocusFocusableElementsCheckBox=browseModeGroup.addItem(wx.CheckBox(self,wx.ID_ANY,label=autoFocusFocusableElementsText))
self.autoFocusFocusableElementsCheckBox.SetValue(config.conf["virtualBuffers"]["autoFocusFocusableElements"])
self.autoFocusFocusableElementsCheckBox.defaultValue=self._getDefaultValue(["virtualBuffers","autoFocusFocusableElements"])

# Translators: This is the label for a group of advanced options in the
# Advanced settings panel
label = _("Editable Text")
@@ -2105,6 +2121,7 @@ def haveConfigDefaultsBeenRestored(self):
self._defaultsRestored and
self.scratchpadCheckBox.IsChecked() == self.scratchpadCheckBox.defaultValue and
self.UIAInMSWordCheckBox.IsChecked() == self.UIAInMSWordCheckBox.defaultValue and
self.autoFocusFocusableElementsCheckBox.IsChecked() == self.autoFocusFocusableElementsCheckBox.defaultValue and
self.caretMoveTimeoutSpinControl.GetValue() == self.caretMoveTimeoutSpinControl.defaultValue and
set(self.logCategoriesList.CheckedItems) == set(self.logCategoriesList.defaultCheckedItems) and
True # reduce noise in diff when the list is extended.
@@ -2113,6 +2130,7 @@ def haveConfigDefaultsBeenRestored(self):
def restoreToDefaults(self):
self.scratchpadCheckBox.SetValue(self.scratchpadCheckBox.defaultValue)
self.UIAInMSWordCheckBox.SetValue(self.UIAInMSWordCheckBox.defaultValue)
self.autoFocusFocusableElementsCheckBox.SetValue(self.autoFocusFocusableElementsCheckBox.defaultValue)
self.caretMoveTimeoutSpinControl.SetValue(self.caretMoveTimeoutSpinControl.defaultValue)
self.logCategoriesList.CheckedItems = self.logCategoriesList.defaultCheckedItems
self._defaultsRestored = True
@@ -2121,6 +2139,7 @@ def onSave(self):
log.debug("Saving advanced config")
config.conf["development"]["enableScratchpadDir"]=self.scratchpadCheckBox.IsChecked()
config.conf["UIA"]["useInMSWordWhenAvailable"]=self.UIAInMSWordCheckBox.IsChecked()
config.conf["virtualBuffers"]["autoFocusFocusableElements"] = self.autoFocusFocusableElementsCheckBox.IsChecked()
config.conf["editableText"]["caretMoveTimeoutMs"]=self.caretMoveTimeoutSpinControl.GetValue()
for index,key in enumerate(self.logCategories):
config.conf['debugLog'][key]=self.logCategoriesList.IsChecked(index)
@@ -382,7 +382,7 @@ def _get_clipboardText(self):
return "\r\n".join(blocks)

def activate(self):
self.obj._activatePosition(self)
self.obj._activatePosition(info=self)

def getMathMl(self, field):
docHandle = int(field["controlIdentifier_docHandle"])
@@ -18,6 +18,10 @@ What's New in NVDA
- The gesture must be configured in the "Input gestures" dialog.
- Eclipse: Added support for autocompletion in code editor. (#5667)
- Additionally, Javadoc information can be read from the editor when it is present by using NVDA+d.
- Added an experimental option to the Advanced Settings panel that allows you to stop the system focus from following the browse mode cursor (Automatically set system focus to focusable elements). (#2039) Although this may not be suitable to turn off for all websites, this may fix:
- Rubber band effect: NVDA sporadically undoes the last browse mode keystroke by jumping to the previous location.
- Edit boxes steal system focus when arrowing down through them on some websites.
- Browse mode keystrokes are slow to respond.


== Changes ==
@@ -1670,6 +1670,16 @@ This includes in Microsoft Word itself, and also the Microsoft Outlook message v
However, There may be some information which is either not exposed, or exposed incorrectly in some versions of Microsoft Office, which means this UI automation support cannot always be relied upon.
We still do not recommend that the majority of users turn this on by default, though we do welcome users of Office 2016/365 to test this feature and provide feedback.

==== Automatically set system focus to focusable elements in Browse Mode ====[BrowseModeSettingsAutoFocusFocusableElements]
Key: NVDA+8

Enabled by default, this option allows you to choose if the system focus should automatically be set to elements that can take the system focus (links, form fields, etc.) when navigating content with the browse mode caret.
If enabled, this represents default behavior of NVDA as of version 2019.1 and before.
Disabling this option will not automatically focus focusable elements when they are selected with the browse mode caret.
This might result in faster browsing experience and better responsiveness in browse mode.
The focus will yet be updated to the particular element when interacting with it (e.g. pressing a button, checking a check box).
This functionality is experimental as of NVDA 2019.2.

==== Caret move timeout (in MS) ====[AdvancedSettingsCaretMoveTimeout]
This option allows you to configure the number of milliseconds NVDA will wait for the caret (insertion point) to move in editable text controls.
If you find that NVDA seems to be incorrectly tracking the caret E.g. it seems to be always one character behind or is repeating lines, then you may wish to try increasing this value.

0 comments on commit 7ffc65f

Please sign in to comment.
You can’t perform that action at this time.