Skip to content

Commit 6fbc40b

Browse files
authored
Merge b7ff24c into b4db7dc
2 parents b4db7dc + b7ff24c commit 6fbc40b

29 files changed

Lines changed: 1447 additions & 66 deletions

source/NVDAObjects/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import appModuleHandler
2626
import treeInterceptorHandler
2727
import braille
28+
import vision
2829
import globalPluginHandler
2930
import brailleInput
3031
import locationHelper
@@ -991,14 +992,15 @@ def event_mouseMove(self,x,y):
991992
else:
992993
speechWasCanceled=False
993994
self._mouseEntered=True
995+
vision.handler.handleMouseMove(self, x, y)
994996
try:
995997
info=self.makeTextInfo(locationHelper.Point(x,y))
996998
except NotImplementedError:
997999
info=NVDAObjectTextInfo(self,textInfos.POSITION_FIRST)
9981000
except LookupError:
9991001
return
10001002
if config.conf["reviewCursor"]["followMouse"]:
1001-
api.setReviewPosition(info)
1003+
api.setReviewPosition(info, isCaret=True)
10021004
info.expand(info.unit_mouseChunk)
10031005
oldInfo=getattr(self,'_lastMouseTextInfoObject',None)
10041006
self._lastMouseTextInfoObject=info
@@ -1018,6 +1020,7 @@ def event_stateChange(self):
10181020
if self is api.getFocusObject():
10191021
speech.speakObjectProperties(self,states=True, reason=controlTypes.REASON_CHANGE)
10201022
braille.handler.handleUpdate(self)
1023+
vision.handler.handleUpdate(self, property="states")
10211024

10221025
def event_focusEntered(self):
10231026
if self.role in (controlTypes.ROLE_MENUBAR,controlTypes.ROLE_POPUPMENU,controlTypes.ROLE_MENUITEM):
@@ -1033,6 +1036,7 @@ def event_gainFocus(self):
10331036
self.reportFocus()
10341037
braille.handler.handleGainFocus(self)
10351038
brailleInput.handler.handleGainFocus(self)
1039+
vision.handler.handleGainFocus(self)
10361040

10371041
def event_loseFocus(self):
10381042
# Forget the word currently being typed as focus is moving to a new control.
@@ -1044,6 +1048,7 @@ def event_foreground(self):
10441048
L{event_focusEntered} or L{event_gainFocus} will be called for this object, so this method should not speak/braille the object, etc.
10451049
"""
10461050
speech.cancelSpeech()
1051+
vision.handler.handleForeground(self)
10471052

10481053
def event_becomeNavigatorObject(self, isFocus=False):
10491054
"""Called when this object becomes the navigator object.
@@ -1052,29 +1057,35 @@ def event_becomeNavigatorObject(self, isFocus=False):
10521057
"""
10531058
# When the navigator object follows the focus and braille is auto tethered to review,
10541059
# we should not update braille with the new review position as a tether to focus is due.
1055-
if braille.handler.shouldAutoTether and isFocus:
1056-
return
1057-
braille.handler.handleReviewMove(shouldAutoTether=not isFocus)
1060+
if not (braille.handler.shouldAutoTether and isFocus):
1061+
braille.handler.handleReviewMove(shouldAutoTether=not isFocus)
1062+
vision.handler.handleReviewMove(
1063+
context=vision.constants.Context.FOCUS if isFocus else vision.constants.Context.NAVIGATOR
1064+
)
10581065

10591066
def event_valueChange(self):
10601067
if self is api.getFocusObject():
10611068
speech.speakObjectProperties(self, value=True, reason=controlTypes.REASON_CHANGE)
10621069
braille.handler.handleUpdate(self)
1070+
vision.handler.handleUpdate(self, property="value")
10631071

10641072
def event_nameChange(self):
10651073
if self is api.getFocusObject():
10661074
speech.speakObjectProperties(self, name=True, reason=controlTypes.REASON_CHANGE)
10671075
braille.handler.handleUpdate(self)
1076+
vision.handler.handleUpdate(self, property="name")
10681077

10691078
def event_descriptionChange(self):
10701079
if self is api.getFocusObject():
10711080
speech.speakObjectProperties(self, description=True, reason=controlTypes.REASON_CHANGE)
10721081
braille.handler.handleUpdate(self)
1082+
vision.handler.handleUpdate(self, property="description")
10731083

10741084
def event_caret(self):
10751085
if self is api.getFocusObject() and not eventHandler.isPendingEvents("gainFocus"):
10761086
braille.handler.handleCaretMove(self)
10771087
brailleInput.handler.handleCaretMove(self)
1088+
vision.handler.handleCaretMove(self)
10781089
review.handleCaretMove(self)
10791090

10801091
def _get_flatReviewPosition(self):

source/NVDAObjects/window/excel.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import browseMode
3737
import inputCore
3838
import ctypes
39+
import vision
3940

4041
excel2010VersionMajor=14
4142

@@ -609,14 +610,14 @@ def getCellAddress(cell, external=False,format=xlA1):
609610
text=_("{start} through {end}").format(start=textList[0], end=textList[1])
610611
return text
611612

612-
def _getDropdown(self):
613+
def _getDropdown(self, selection=None):
613614
w=winUser.getAncestor(self.windowHandle,winUser.GA_ROOT)
614615
if not w:
615616
log.debugWarning("Could not get ancestor window (GA_ROOT)")
616617
return
617618
obj=Window(windowHandle=w,chooseBestAPI=False)
618619
if not obj:
619-
log.debugWarning("Could not instnaciate NVDAObject for ancestor window")
620+
log.debugWarning("Could not instanciate NVDAObject for ancestor window")
620621
return
621622
threadID=obj.windowThreadID
622623
while not eventHandler.isPendingEvents("gainFocus"):
@@ -626,6 +627,10 @@ def _getDropdown(self):
626627
return
627628
if obj.windowClassName=='EXCEL:':
628629
break
630+
if selection:
631+
# If we are getting a dropdown for a selection,
632+
# we want the selection to be presented as the direct ancestor of the dropdown.
633+
obj.parent = selection
629634
return obj
630635

631636
def _getSelection(self):
@@ -665,16 +670,19 @@ class Excel7Window(ExcelBase):
665670
def _get_excelWindowObject(self):
666671
return self.excelWindowObjectFromWindow(self.windowHandle)
667672

668-
def event_gainFocus(self):
673+
def _get_focusRedirect(self):
669674
selection=self._getSelection()
670-
dropdown=self._getDropdown()
675+
dropdown = self._getDropdown(selection=selection)
671676
if dropdown:
672-
if selection:
673-
dropdown.parent=selection
674-
eventHandler.executeEvent('gainFocus',dropdown)
675-
return
677+
return dropdown
676678
if selection:
677-
eventHandler.executeEvent('gainFocus',selection)
679+
return selection
680+
681+
def event_caret(self):
682+
# This object never gains focus, so normally, caret updates would be ignored.
683+
# However, we need to tell the vision handler that a caret move has occured on this object,
684+
# in order for a magnifier or highlighter to be positioned correctly.
685+
vision.handler.handleCaretMove(self)
678686

679687
class ExcelWorksheet(ExcelBase):
680688

source/api.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
import controlTypes
2222
import eventHandler
2323
import braille
24+
import vision
2425
import watchdog
2526
import appModuleHandler
27+
import cursorManager
28+
from typing import Any
2629

2730
#User functions
2831

@@ -177,22 +180,36 @@ def getReviewPosition():
177180
globalVars.reviewPosition,globalVars.reviewPositionObj=review.getPositionForCurrentMode(obj)
178181
return globalVars.reviewPosition
179182

180-
def setReviewPosition(reviewPosition,clearNavigatorObject=True,isCaret=False):
183+
184+
def setReviewPosition(
185+
reviewPosition,
186+
clearNavigatorObject=True,
187+
isCaret=False,
188+
isMouse=False
189+
):
181190
"""Sets a TextInfo instance as the review position.
182191
@param clearNavigatorObject: if true, It sets the current navigator object to C{None}.
183192
In that case, the next time the navigator object is asked for it fetches it from the review position.
184193
@type clearNavigatorObject: bool
185194
@param isCaret: Whether the review position is changed due to caret following.
186195
@type isCaret: bool
196+
@param isMouse: Whether the review position is changed due to mouse following.
197+
@type isMouse: bool
187198
"""
188199
globalVars.reviewPosition=reviewPosition.copy()
189200
globalVars.reviewPositionObj=reviewPosition.obj
190201
if clearNavigatorObject: globalVars.navigatorObject=None
191202
# When the review cursor follows the caret and braille is auto tethered to review,
192203
# we should not update braille with the new review position as a tether to focus is due.
193-
if braille.handler.shouldAutoTether and isCaret:
194-
return
195-
braille.handler.handleReviewMove(shouldAutoTether=not isCaret)
204+
if not (braille.handler.shouldAutoTether and isCaret):
205+
braille.handler.handleReviewMove(shouldAutoTether=not isCaret)
206+
if isCaret:
207+
visionContext = vision.constants.Context.CARET
208+
elif isMouse:
209+
visionContext = vision.constants.Context.MOUSE
210+
else:
211+
visionContext = vision.constants.Context.REVIEW
212+
vision.handler.handleReviewMove(context=visionContext)
196213

197214
def getNavigatorObject():
198215
"""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.
@@ -346,6 +363,34 @@ def filterFileName(name):
346363
name=name.replace(c,'_')
347364
return name
348365

366+
367+
def isNVDAObject(obj: Any) -> bool:
368+
"""Returns whether the supplied object is a L{NVDAObjects.NVDAObject}"""
369+
return isinstance(obj, NVDAObjects.NVDAObject)
370+
371+
372+
def isCursorManager(obj: Any) -> bool:
373+
"""Returns whether the supplied object is a L{cursorManager.CursorManager}"""
374+
return isinstance(obj, cursorManager.CursorManager)
375+
376+
377+
def isTreeInterceptor(obj: Any) -> bool:
378+
"""Returns whether the supplied object is a L{treeInterceptorHandler.TreeInterceptor}"""
379+
return isinstance(obj, treeInterceptorHandler.TreeInterceptor)
380+
381+
382+
def isObjectInActiveTreeInterceptor(obj: NVDAObjects.NVDAObject) -> bool:
383+
"""Returns whether the supplied L{NVDAObjects.NVDAObject} is
384+
in an active L{treeInterceptorHandler.TreeInterceptor},
385+
i.e. a tree interceptor that is not in pass through mode.
386+
"""
387+
return bool(
388+
isinstance(obj, NVDAObjects.NVDAObject)
389+
and obj.treeInterceptor
390+
and not obj.treeInterceptor.passThrough
391+
)
392+
393+
349394
def getCaretObject():
350395
"""Gets the object which contains the caret.
351396
This is normally the focus object.

source/browseMode.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import config
2828
import textInfos
2929
import braille
30+
import vision
3031
import speech
3132
import sayAllHandler
3233
import treeInterceptorHandler
@@ -1503,6 +1504,9 @@ def event_gainFocus(self, obj, nextHandler):
15031504
speech.speakTextInfo(focusInfo,reason=controlTypes.REASON_FOCUS)
15041505
# However, we still want to update the speech property cache so that property changes will be spoken properly.
15051506
speech.speakObject(obj,controlTypes.REASON_ONLYCACHE)
1507+
# As we do not call nextHandler which would trigger the vision framework to handle gain focus,
1508+
# we need to call it manually here.
1509+
vision.handler.handleGainFocus(obj)
15061510
else:
15071511
# Although we are going to speak the object rather than textInfo content, we still need to silently speak the textInfo content so that the textInfo speech cache is updated correctly.
15081512
# Not doing this would cause later browseMode speaking to either not speak controlFields it had entered, or speak controlField exits after having already exited.
@@ -1518,9 +1522,13 @@ def event_gainFocus(self, obj, nextHandler):
15181522
# This focus change was caused by a virtual caret movement, so don't speak the focused node to avoid double speaking.
15191523
# However, we still want to update the speech property cache so that property changes will be spoken properly.
15201524
speech.speakObject(obj,controlTypes.REASON_ONLYCACHE)
1521-
if (
1522-
not config.conf["virtualBuffers"]["autoFocusFocusableElements"]
1523-
and self._lastFocusableObj
1525+
if config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
1526+
# As we do not call nextHandler which would trigger the vision framework to handle gain focus,
1527+
# we need to call it manually here.
1528+
# Note: this is usually called after the caret movement.
1529+
vision.handler.handleGainFocus(obj)
1530+
elif (
1531+
self._lastFocusableObj
15241532
and obj == self._lastFocusableObj
15251533
and obj is not self._lastFocusableObj
15261534
):

source/colors.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ def fromString(cls,s):
6464
return RGB(r,g,b)
6565
raise ValueError("invalid RGB string: %s"%s)
6666

67+
def toCOLORREF(self) -> COLORREF:
68+
"""Returns a COLORREF ctypes instance
69+
"""
70+
return COLORREF(self.red & 0xff | ((self.green & 0xff) << 8) | ((self.blue & 0xff) << 16))
71+
72+
def toGDIPlusARGB(self, alpha: int = 255) -> int:
73+
"""Creates a GDI+ compatible ARGB color, using the specified alpha for the alpha component.
74+
@param alpha: The alpha part of the ARGB color,
75+
0 is fully transparent and 255 is fully opaque.
76+
Defaults to 255 (opaque).
77+
@type alpha: int
78+
"""
79+
return (alpha << 24) | (self.red << 16) | (self.green << 8) | self.blue
80+
6781
@property
6882
def name(self):
6983
foundName=RGBToNamesCache.get(self,None)

source/compoundDocuments.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import api
1818
import config
1919
import review
20+
import vision
2021
from logHandler import log
2122
from locationHelper import RectLTWH
2223

@@ -464,6 +465,7 @@ def event_treeInterceptor_gainFocus(self):
464465
def event_caret(self, obj, nextHandler):
465466
self.detectPossibleSelectionChange()
466467
braille.handler.handleCaretMove(self)
468+
vision.handler.handleCaretMove(self)
467469
caret = self.makeTextInfo(textInfos.POSITION_CARET)
468470
review.handleCaretMove(caret)
469471

source/config/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,15 @@ def getSystemConfigPath():
135135
pass
136136
return None
137137

138-
SCRATCH_PAD_ONLY_DIRS = ('appModules','brailleDisplayDrivers','globalPlugins','synthDrivers')
138+
139+
SCRATCH_PAD_ONLY_DIRS = (
140+
'appModules',
141+
'brailleDisplayDrivers',
142+
'globalPlugins',
143+
'synthDrivers',
144+
'visionEnhancementProviders',
145+
)
146+
139147

140148
def getScratchpadDir(ensureExists=False):
141149
""" Returns the path where custom appModules, globalPlugins and drivers can be placed while being developed."""

source/config/configSpec.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@
7171
[[__many__]]
7272
port = string(default="")
7373
74+
# Vision enhancement provider settings
75+
[vision]
76+
providers = string_list(=default=list())
77+
78+
# Vision enhancement provider settings
79+
[[__many__]]
80+
7481
# Presentation settings
7582
[presentation]
7683
reportKeyboardShortcuts = boolean(default=true)
@@ -212,6 +219,7 @@
212219
gui = boolean(default=false)
213220
louis = boolean(default=false)
214221
timeSinceInput = boolean(default=false)
222+
vision = boolean(default=false)
215223
216224
[uwpOcr]
217225
language = string(default="")

0 commit comments

Comments
 (0)