Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add the ability to automatically tether to focus or review #7489

Merged
merged 20 commits into from Jan 15, 2018
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/NVDAObjects/IAccessible/mscandui.py
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion source/NVDAObjects/UIA/__init__.py
Expand Up @@ -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))
6 changes: 4 additions & 2 deletions source/NVDAObjects/__init__.py
Expand Up @@ -958,10 +958,12 @@ 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()
braille.handler.handleReviewMove(shouldAutoTether=not isFocus)

def event_valueChange(self):
if self is api.getFocusObject():
Expand Down
4 changes: 2 additions & 2 deletions source/NVDAObjects/window/winConsole.py
Expand Up @@ -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()
Expand All @@ -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:
Expand Down
13 changes: 9 additions & 4 deletions source/api.py
Expand Up @@ -177,13 +177,18 @@ 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()
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.
Expand Down Expand Up @@ -227,7 +232,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.
Expand Down
2 changes: 1 addition & 1 deletion source/appModules/explorer.py
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion source/appModules/lockapp.py
Expand Up @@ -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"]:
Expand Down
3 changes: 2 additions & 1 deletion source/appModules/skype.py
Expand Up @@ -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):
Expand Down
74 changes: 51 additions & 23 deletions source/braille.py
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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)
elif self._tether != self.TETHER_FOCUS:
return
self._doNewObject(itertools.chain(getFocusContextRegions(obj, oldFocusRegions=self.mainBuffer.regions), getFocusRegions(obj)))

Expand Down Expand Up @@ -1714,17 +1738,19 @@ 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:
if shouldAutoTether:
self.setTether(self.TETHER_FOCUS, auto=True)
elif 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
else:
# 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."""
Expand Down Expand Up @@ -1784,7 +1810,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
Expand All @@ -1800,12 +1826,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:
if not shouldAutoTether and self._tether != self.TETHER_REVIEW:
return
reviewPos = api.getReviewPosition()
if shouldAutoTether:
self.setTether(self.TETHER_REVIEW, auto=True)
region = self.mainBuffer.regions[-1] if self.mainBuffer.regions else None
if region and region.obj == reviewPos.obj:
self._doCursorMove(region)
Expand Down Expand Up @@ -1893,9 +1921,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."""
Expand Down
1 change: 1 addition & 0 deletions source/config/configSpec.py
Expand Up @@ -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")
Expand Down
15 changes: 12 additions & 3 deletions source/eventHandler.py
Expand Up @@ -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
Expand All @@ -16,6 +16,7 @@
import globalPluginHandler
import config
import winUser
import extensionPoints

#Some dicts to store event counts by name and or obj
_pendingEventCountsByName={}
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please log a clear message here (perhaps warning) so that we catch these issues pretty quick and fix them.


def gen(self, eventName, obj):
funcName = "event_%s" % eventName
Expand Down Expand Up @@ -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()):
Expand Down