Package NVDAObjects :: Package UIA
[hide private]
[frames] | no frames]

Source Code for Package NVDAObjects.UIA

  1  from ctypes.wintypes import POINT 
  2  from comtypes import COMError 
  3  import weakref 
  4  import UIAHandler 
  5  import globalVars 
  6  import eventHandler 
  7  import controlTypes 
  8  import config 
  9  import speech 
 10  import api 
 11  import textInfos 
 12  from logHandler import log 
 13  from NVDAObjects.window import Window 
 14  from NVDAObjects import NVDAObjectTextInfo, InvalidNVDAObject 
 15  from NVDAObjects.behaviors import ProgressBar, EditableTextWithoutAutoSelectDetection 
16 17 -class UIATextInfo(textInfos.TextInfo):
18 19 NVDAUnitsToUIAUnits={ 20 "character":UIAHandler.TextUnit_Character, 21 "word":UIAHandler.TextUnit_Word, 22 "line":UIAHandler.TextUnit_Line, 23 "paragraph":UIAHandler.TextUnit_Paragraph, 24 "readingChunk":UIAHandler.TextUnit_Line, 25 } 26
27 - def _getFormatFieldAtRange(self,range,formatConfig):
28 formatField=textInfos.FormatField() 29 if formatConfig["reportFontName"]: 30 try: 31 fontNameValue=range.GetAttributeValue(UIAHandler.UIA_FontNameAttributeId) 32 except COMError: 33 fontNameValue=UIAHandler.handler.reservedNotSupportedValue 34 if fontNameValue!=UIAHandler.handler.reservedNotSupportedValue: 35 formatField["font-name"]=fontNameValue 36 if formatConfig["reportFontSize"]: 37 try: 38 fontSizeValue=range.GetAttributeValue(UIAHandler.UIA_FontSizeAttributeId) 39 except COMError: 40 fontSizeValue=UIAHandler.handler.reservedNotSupportedValue 41 if fontSizeValue!=UIAHandler.handler.reservedNotSupportedValue: 42 formatField['font-size']="%g pt"%float(fontSizeValue) 43 return formatField
44
45 - def __init__(self,obj,position):
46 super(UIATextInfo,self).__init__(obj,position) 47 if isinstance(position,UIAHandler.IUIAutomationTextRange): 48 self._rangeObj=position 49 elif position in (textInfos.POSITION_CARET,textInfos.POSITION_SELECTION): 50 sel=self.obj.UIATextPattern.GetSelection() 51 if sel.length>0: 52 self._rangeObj=sel.getElement(0).clone() 53 else: 54 raise NotImplementedError("UIAutomationTextRangeArray is empty") 55 if position==textInfos.POSITION_CARET: 56 self.collapse() 57 elif isinstance(position,UIATextInfo): #bookmark 58 self._rangeObj=position._rangeObj 59 elif position==textInfos.POSITION_FIRST: 60 self._rangeObj=self.obj.UIATextPattern.documentRange 61 self.collapse() 62 elif position==textInfos.POSITION_LAST: 63 self._rangeObj=self.obj.UIATextPattern.documentRange 64 self.collapse(True) 65 elif position==textInfos.POSITION_ALL: 66 self._rangeObj=self.obj.UIATextPattern.documentRange 67 else: 68 raise ValueError("Unknown position %s"%position)
69
70 - def __eq__(self,other):
71 if self is other: return True 72 if self.__class__ is not other.__class__: return False 73 return bool(self._rangeObj.compare(other._rangeObj))
74
75 - def _get_bookmark(self):
76 return self.copy()
77
78 - def getTextWithFields(self,formatConfig=None):
79 if not formatConfig: 80 formatConfig=config.conf["documentFormatting"] 81 rangeObj=self._rangeObj.Clone() 82 rangeObj.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,rangeObj,UIAHandler.TextPatternRangeEndpoint_Start) 83 rangeObj.ExpandToEnclosingUnit(UIAHandler.TextUnit_Character) 84 formatField=self._getFormatFieldAtRange(rangeObj,formatConfig) 85 field=textInfos.FieldCommand("formatChange",formatField) 86 return [field,self.text]
87
88 - def _get_text(self):
89 return self._rangeObj.GetText(-1)
90
91 - def expand(self,unit):
92 UIAUnit=self.NVDAUnitsToUIAUnits[unit] 93 self._rangeObj.ExpandToEnclosingUnit(UIAUnit)
94
95 - def move(self,unit,direction,endPoint=None):
96 UIAUnit=self.NVDAUnitsToUIAUnits[unit] 97 if endPoint=="start": 98 res=self._rangeObj.MoveEndpointByUnit(UIAHandler.TextPatternRangeEndpoint_Start,UIAUnit,direction) 99 elif endPoint=="end": 100 res=self._rangeObj.MoveEndpointByUnit(UIAHandler.TextPatternRangeEndpoint_End,UIAUnit,direction) 101 else: 102 res=self._rangeObj.Move(UIAUnit,direction) 103 #Some Implementations of Move and moveEndpointByUnit return a positive number even if the direction is negative 104 if direction<0 and res>0: 105 res=0-res 106 return res
107
108 - def copy(self):
109 return self.__class__(self.obj,self._rangeObj.clone())
110
111 - def collapse(self,end=False):
112 if end: 113 self._rangeObj.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_Start,self._rangeObj,UIAHandler.TextPatternRangeEndpoint_End) 114 else: 115 self._rangeObj.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,self._rangeObj,UIAHandler.TextPatternRangeEndpoint_Start)
116
117 - def compareEndPoints(self,other,which):
118 if which.startswith('start'): 119 src=UIAHandler.TextPatternRangeEndpoint_Start 120 else: 121 src=UIAHandler.TextPatternRangeEndpoint_End 122 if which.endswith('Start'): 123 target=UIAHandler.TextPatternRangeEndpoint_Start 124 else: 125 target=UIAHandler.TextPatternRangeEndpoint_End 126 return self._rangeObj.CompareEndpoints(src,other._rangeObj,target)
127
128 - def setEndPoint(self,other,which):
129 if which.startswith('start'): 130 src=UIAHandler.TextPatternRangeEndpoint_Start 131 else: 132 src=UIAHandler.TextPatternRangeEndpoint_End 133 if which.endswith('Start'): 134 target=UIAHandler.TextPatternRangeEndpoint_Start 135 else: 136 target=UIAHandler.TextPatternRangeEndpoint_End 137 self._rangeObj.MoveEndpointByRange(src,other._rangeObj,target)
138
139 - def updateSelection(self):
140 self._rangeObj.Select()
141 142 updateCaret = updateSelection
143
144 -class UIA(Window):
145 146 liveNVDAObjectTable=weakref.WeakValueDictionary() 147
148 - def findOverlayClasses(self,clsList):
149 if self.TextInfo==UIATextInfo: 150 clsList.append(EditableTextWithoutAutoSelectDetection) 151 152 UIAControlType=self.UIAElement.cachedControlType 153 UIAClassName=self.UIAElement.cachedClassName 154 if UIAControlType==UIAHandler.UIA_ProgressBarControlTypeId: 155 clsList.append(ProgressBar) 156 if UIAClassName=="ControlPanelLink": 157 clsList.append(ControlPanelLink) 158 if UIAClassName=="UIColumnHeader": 159 clsList.append(UIColumnHeader) 160 elif UIAClassName=="UIItem": 161 clsList.append(UIItem) 162 elif UIAClassName=="SensitiveSlider": 163 clsList.append(SensitiveSlider) 164 if UIAControlType==UIAHandler.UIA_TreeItemControlTypeId: 165 clsList.append(TreeviewItem) 166 elif UIAControlType==UIAHandler.UIA_ComboBoxControlTypeId: 167 try: 168 if not self.UIAElement.getCurrentPropertyValue(UIAHandler.UIA_IsValuePatternAvailablePropertyId): 169 clsList.append(ComboBoxWithoutValuePattern) 170 except COMError: 171 pass 172 elif UIAControlType==UIAHandler.UIA_ListItemControlTypeId: 173 clsList.append(ListItem) 174 clsList.append(UIA) 175 176 if self.UIAIsWindowElement: 177 super(UIA,self).findOverlayClasses(clsList)
178 179 @classmethod
180 - def kwargsFromSuper(cls,kwargs,relation=None):
181 UIAElement=None 182 windowHandle=kwargs.get('windowHandle') 183 if isinstance(relation,tuple): 184 UIAElement=UIAHandler.handler.clientObject.ElementFromPointBuildCache(POINT(relation[0],relation[1]),UIAHandler.handler.baseCacheRequest) 185 elif relation=="focus": 186 try: 187 UIAElement=UIAHandler.handler.clientObject.getFocusedElementBuildCache(UIAHandler.handler.baseCacheRequest) 188 # This object may be in a different window, so we need to recalculate the window handle. 189 kwargs['windowHandle']=None 190 except COMError: 191 log.debugWarning("getFocusedElement failed", exc_info=True) 192 else: 193 UIAElement=UIAHandler.handler.clientObject.ElementFromHandleBuildCache(windowHandle,UIAHandler.handler.baseCacheRequest) 194 if not UIAElement: 195 return False 196 kwargs['UIAElement']=UIAElement 197 return True
198
199 - def __new__(cls,relation=None,windowHandle=None,UIAElement=None):
200 try: 201 runtimeId=UIAElement.getRuntimeId() 202 except COMError: 203 log.debugWarning("Could not get UIA element runtime Id",exc_info=True) 204 runtimeId=None 205 206 obj=cls.liveNVDAObjectTable.get(runtimeId,None) if runtimeId else None 207 if not obj: 208 obj=super(UIA,cls).__new__(cls) 209 if not obj: 210 return None 211 obj.UIARuntimeId=runtimeId 212 obj.UIAElement=UIAElement 213 return obj
214
215 - def __init__(self,windowHandle=None,UIAElement=None):
216 if getattr(self,"_doneInit",False): 217 # This instance was retrieved from the cache by __new__ and has already been constructed. 218 return 219 self._doneInit=True 220 221 if not UIAElement: 222 raise ValueError("needs a UIA element") 223 224 UIACachedWindowHandle=UIAElement.cachedNativeWindowHandle 225 self.UIAIsWindowElement=bool(UIACachedWindowHandle) 226 if UIACachedWindowHandle: 227 windowHandle=UIACachedWindowHandle 228 if not windowHandle: 229 windowHandle=UIAHandler.handler.getNearestWindowHandle(UIAElement) 230 if not windowHandle: 231 raise InvalidNVDAObject("no windowHandle") 232 super(UIA,self).__init__(windowHandle=windowHandle) 233 234 if UIAElement.getCachedPropertyValue(UIAHandler.UIA_IsTextPatternAvailablePropertyId): 235 self.TextInfo=UIATextInfo 236 self.value="" 237 238 # UIARuntimeId is set by __new__. 239 if self.UIARuntimeId: 240 # This must be the last thing in the constructor, 241 # as this object should only be cached if construction succeeds. 242 self.liveNVDAObjectTable[self.UIARuntimeId]=self
243
244 - def _isEqual(self,other):
245 if not isinstance(other,UIA): 246 return False 247 try: 248 return UIAHandler.handler.clientObject.CompareElements(self.UIAElement,other.UIAElement) 249 except: 250 return False
251
252 - def _get_UIAInvokePattern(self):
253 if not hasattr(self,'_UIAInvokePattern'): 254 punk=self.UIAElement.GetCurrentPattern(UIAHandler.UIA_InvokePatternId) 255 if punk: 256 self._UIAInvokePattern=punk.QueryInterface(UIAHandler.IUIAutomationInvokePattern) 257 else: 258 self._UIAInvokePattern=None 259 return self._UIAInvokePattern
260
261 - def _get_UIATextPattern(self):
262 if not hasattr(self,'_UIATextPattern'): 263 punk=self.UIAElement.GetCurrentPattern(UIAHandler.UIA_TextPatternId) 264 if punk: 265 self._UIATextPattern=punk.QueryInterface(UIAHandler.IUIAutomationTextPattern) 266 else: 267 self._UIATextPattern=None 268 return self._UIATextPattern
269
270 - def setFocus(self):
271 self.UIAElement.setFocus()
272
273 - def _get_devInfo(self):
274 info=super(UIA,self).devInfo 275 info.append("UIAElement: %r"%self.UIAElement) 276 try: 277 ret=self.UIAElement.currentAutomationID 278 except Exception as e: 279 ret="Exception: %s"%e 280 info.append("UIA automationID: %s"%ret) 281 try: 282 ret=self.UIAElement.currentFrameworkID 283 except Exception as e: 284 ret="Exception: %s"%e 285 info.append("UIA frameworkID: %s"%ret) 286 try: 287 ret=str(self.UIAElement.getRuntimeID()) 288 except Exception as e: 289 ret="Exception: %s"%e 290 info.append("UIA runtimeID: %s"%ret) 291 try: 292 ret=self.UIAElement.cachedProviderDescription 293 except Exception as e: 294 ret="Exception: %s"%e 295 info.append("UIA providerDescription: %s"%ret) 296 try: 297 ret=self.UIAElement.currentClassName 298 except Exception as e: 299 ret="Exception: %s"%e 300 info.append("UIA className: %s"%ret) 301 return info
302
303 - def _get_name(self):
304 try: 305 return self.UIAElement.currentName 306 except COMError: 307 return ""
308
309 - def _get_role(self):
310 role=UIAHandler.UIAControlTypesToNVDARoles.get(self.UIAElement.cachedControlType,controlTypes.ROLE_UNKNOWN) 311 if role in (controlTypes.ROLE_UNKNOWN,controlTypes.ROLE_PANE,controlTypes.ROLE_WINDOW) and self.windowHandle: 312 superRole=super(UIA,self).role 313 if superRole!=controlTypes.ROLE_WINDOW: 314 return superRole 315 return role
316
317 - def _get_description(self):
318 try: 319 return self.UIAElement.currentHelpText 320 except COMError: 321 return ""
322
323 - def _get_keyboardShortcut(self):
324 try: 325 return self.UIAElement.currentAccessKey 326 except COMError: 327 return None
328
329 - def _get_states(self):
330 states=set() 331 try: 332 hasKeyboardFocus=self.UIAElement.currentHasKeyboardFocus 333 except COMError: 334 hasKeyboardFocus=False 335 if hasKeyboardFocus: 336 states.add(controlTypes.STATE_FOCUSED) 337 if self.UIAElement.cachedIsKeyboardFocusable: 338 states.add(controlTypes.STATE_FOCUSABLE) 339 if self.UIAElement.cachedIsPassword: 340 states.add(controlTypes.STATE_PROTECTED) 341 # Don't fetch the role unless we must, but never fetch it more than once. 342 role=None 343 if self.UIAElement.getCachedPropertyValue(UIAHandler.UIA_IsSelectionItemPatternAvailablePropertyId): 344 role=self.role 345 states.add(controlTypes.STATE_CHECKABLE if role==controlTypes.ROLE_RADIOBUTTON else controlTypes.STATE_SELECTABLE) 346 if self.UIAElement.getCurrentPropertyValue(UIAHandler.UIA_SelectionItemIsSelectedPropertyId): 347 states.add(controlTypes.STATE_CHECKED if role==controlTypes.ROLE_RADIOBUTTON else controlTypes.STATE_SELECTED) 348 try: 349 s=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_ExpandCollapseExpandCollapseStatePropertyId,True) 350 except COMError: 351 s=UIAHandler.handler.reservedNotSupportedValue 352 if s!=UIAHandler.handler.reservedNotSupportedValue: 353 if s==UIAHandler.ExpandCollapseState_Collapsed: 354 states.add(controlTypes.STATE_COLLAPSED) 355 elif s==UIAHandler.ExpandCollapseState_Expanded: 356 states.add(controlTypes.STATE_EXPANDED) 357 try: 358 s=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_ToggleToggleStatePropertyId,True) 359 except COMError: 360 s=UIAHandler.handler.reservedNotSupportedValue 361 if s!=UIAHandler.handler.reservedNotSupportedValue: 362 if not role: 363 role=self.role 364 if role==controlTypes.ROLE_BUTTON: 365 if s==UIAHandler.ToggleState_On: 366 states.add(controlTypes.STATE_PRESSED) 367 else: 368 states.add(controlTypes.STATE_CHECKABLE) 369 if s==UIAHandler.ToggleState_On: 370 states.add(controlTypes.STATE_CHECKED) 371 return states
372
373 - def correctAPIForRelation(self, obj, relation=None):
374 if obj and self.windowHandle != obj.windowHandle and not obj.UIAElement.cachedNativeWindowHandle: 375 # The target element is not the root element for the window, so don't change API class; i.e. always use UIA. 376 return obj 377 return super(UIA, self).correctAPIForRelation(obj, relation)
378
379 - def _get_parent(self):
380 try: 381 parentElement=UIAHandler.handler.baseTreeWalker.GetParentElementBuildCache(self.UIAElement,UIAHandler.handler.baseCacheRequest) 382 except COMError: 383 parentElement=None 384 if not parentElement: 385 return super(UIA,self).parent 386 return self.correctAPIForRelation(UIA(UIAElement=parentElement),relation="parent")
387
388 - def _get_previous(self):
389 try: 390 previousElement=UIAHandler.handler.baseTreeWalker.GetPreviousSiblingElementBuildCache(self.UIAElement,UIAHandler.handler.baseCacheRequest) 391 except COMError: 392 log.debugWarning("Tree walker failed", exc_info=True) 393 return None 394 if not previousElement: 395 return None 396 return self.correctAPIForRelation(UIA(UIAElement=previousElement))
397
398 - def _get_next(self):
399 try: 400 nextElement=UIAHandler.handler.baseTreeWalker.GetNextSiblingElementBuildCache(self.UIAElement,UIAHandler.handler.baseCacheRequest) 401 except COMError: 402 log.debugWarning("Tree walker failed", exc_info=True) 403 return None 404 if not nextElement: 405 return None 406 return self.correctAPIForRelation(UIA(UIAElement=nextElement))
407
408 - def _get_firstChild(self):
409 try: 410 firstChildElement=UIAHandler.handler.baseTreeWalker.GetFirstChildElementBuildCache(self.UIAElement,UIAHandler.handler.baseCacheRequest) 411 except COMError: 412 log.debugWarning("Tree walker failed", exc_info=True) 413 return None 414 if not firstChildElement: 415 return None 416 return self.correctAPIForRelation(UIA(UIAElement=firstChildElement))
417
418 - def _get_lastChild(self):
419 try: 420 lastChildElement=UIAHandler.handler.baseTreeWalker.GetLastChildElementBuildCache(self.UIAElement,UIAHandler.handler.baseCacheRequest) 421 except COMError: 422 log.debugWarning("Tree walker failed", exc_info=True) 423 return None 424 if not lastChildElement: 425 return None 426 return self.correctAPIForRelation(UIA(UIAElement=lastChildElement))
427
428 - def _get_rowNumber(self):
429 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_GridItemRowPropertyId,True) 430 if val!=UIAHandler.handler.reservedNotSupportedValue: 431 return val+1 432 raise NotImplementedError
433
434 - def _get_columnNumber(self):
435 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_GridItemColumnPropertyId,True) 436 if val!=UIAHandler.handler.reservedNotSupportedValue: 437 return val+1 438 raise NotImplementedError
439
440 - def _get_rowCount(self):
441 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_GridRowCountPropertyId,True) 442 if val!=UIAHandler.handler.reservedNotSupportedValue: 443 return val 444 raise NotImplementedError
445
446 - def _get_columnCount(self):
447 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_GridColumnCountPropertyId,True) 448 if val!=UIAHandler.handler.reservedNotSupportedValue: 449 return val 450 raise NotImplementedError
451
452 - def _get_processID(self):
453 return self.UIAElement.cachedProcessId
454
455 - def _get_location(self):
456 try: 457 r=self.UIAElement.currentBoundingRectangle 458 except COMError: 459 return None 460 left=r.left 461 top=r.top 462 width=r.right-left 463 height=r.bottom-top 464 return left,top,width,height
465
466 - def _get_value(self):
467 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_RangeValueValuePropertyId,True) 468 if val!=UIAHandler.handler.reservedNotSupportedValue: 469 minVal=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_RangeValueMinimumPropertyId,False) 470 maxVal=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_RangeValueMaximumPropertyId,False) 471 val=((val-minVal)/maxVal)*100.0 472 return "%g"%val 473 val=self.UIAElement.getCurrentPropertyValueEx(UIAHandler.UIA_ValueValuePropertyId,True) 474 if val!=UIAHandler.handler.reservedNotSupportedValue: 475 return val
476
477 - def _get_actionCount(self):
478 if self.UIAInvokePattern: 479 return 1 480 return 0
481
482 - def getActionName(self,index=None):
483 if not index: 484 index=self.defaultActionIndex 485 if index==0 and self.UIAInvokePattern: 486 return _("invoke") 487 raise NotImplementedError
488
489 - def doAction(self,index=None):
490 if not index: 491 index=self.defaultActionIndex 492 if index==0 and self.UIAInvokePattern: 493 self.UIAInvokePattern.Invoke() 494 return 495 raise NotImplementedError
496
497 - def _get_hasFocus(self):
498 try: 499 return self.UIAElement.currentHasKeyboardFocus 500 except COMError: 501 return False
502
503 -class TreeviewItem(UIA):
504
505 - def _get_value(self):
506 return ""
507
508 - def _get__level(self):
509 level=0 510 obj=self 511 while obj: 512 level+=1 513 parent=obj.parent=obj.parent 514 if not parent or parent==obj or parent.role!=controlTypes.ROLE_TREEVIEWITEM: 515 return level 516 obj=parent 517 return level
518
519 - def _get_positionInfo(self):
520 return {'level':self._level}
521
522 -class UIColumnHeader(UIA):
523
524 - def _get_description(self):
525 description=super(UIColumnHeader,self).description 526 try: 527 itemStatus=self.UIAElement.currentItemStatus 528 except COMError: 529 itemStatus="" 530 return " ".join([x for x in (description,itemStatus) if x and not x.isspace()])
531
532 -class UIItem(UIA):
533 """UIA list items in an Items View repeate the name as the value""" 534
535 - def _get_value(self):
536 return ""
537
538 -class SensitiveSlider(UIA):
539 """A slider that tends to give focus to its thumb control""" 540
541 - def event_focusEntered(self):
542 self.reportFocus()
543
544 - def event_valueChange(self):
545 focusParent=api.getFocusObject().parent 546 if self==focusParent: 547 speech.speakObjectProperties(self,value=True,reason=speech.REASON_CHANGE) 548 else: 549 super(SensitiveSlider,self).event_valueChange()
550 562
563 -class ComboBoxWithoutValuePattern(UIA):
564 """A combo box without the Value pattern. 565 UIA combo boxes don't necessarily support the Value pattern unless they take arbitrary text values. 566 However, NVDA expects combo boxes to have a value and to fire valueChange events. 567 The value is obtained by retrieving the selected item's name. 568 The valueChange event is fired on this object by L{ListItem.event_stateChange}. 569 """ 570
571 - def _get_UIASelectionPattern(self):
572 punk = self.UIAElement.GetCurrentPattern(UIAHandler.UIA_SelectionPatternId) 573 if punk: 574 self.UIASelectionPattern = punk.QueryInterface(UIAHandler.IUIAutomationSelectionPattern) 575 else: 576 self.UIASelectionPattern = None 577 return self.UIASelectionPattern
578
579 - def _get_value(self):
580 try: 581 return self.UIASelectionPattern.GetCurrentSelection().GetElement(0).CurrentName 582 except COMError: 583 return None
584
585 -class ListItem(UIA):
586
587 - def event_stateChange(self):
588 if not self.hasFocus: 589 parent = self.parent 590 if parent and isinstance(parent, ComboBoxWithoutValuePattern): 591 # This is an item in a combo box without the Value pattern. 592 # This item has been selected, so notify the combo box that its value has changed. 593 parent.event_valueChange() 594 super(ListItem, self).event_stateChange()
595