Package AccessEngine :: Package AEAccAdapters :: Package ATSPI :: Module DefaultEventHandler
[hide private]
[frames] | no frames]

Source Code for Module AccessEngine.AEAccAdapters.ATSPI.DefaultEventHandler

  1  ''' 
  2  Defines default L{AEAccAdapter.AEAccAdapter}s for the  
  3  L{AEAccInterfaces.IEventHandler} interface on  
  4  C{pyatspi.Accessibility.Accessible} objects. 
  5   
  6  @var EVENT_HANDLERS: Maps event names to method names to be called to  
  7    handle them 
  8  @type EVENT_HANDLERS: dictionary 
  9  @var AE_MAP: Mapping from L{AEEvent}s to raw events that must be registered 
 10    to generate them 
 11  @type AE_MAP: dictionary 
 12   
 13  @author: Pete Brunet 
 14  @author: Peter Parente 
 15  @author: Brett Clippingdale 
 16  @organization: IBM Corporation 
 17  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
 18  @license: The BSD License 
 19   
 20  @author: Frank Zenker 
 21  @organization: IT Science Center Ruegen gGmbH, Germany 
 22  @copyright: Copyright (c) 2007, 2008 ITSC Ruegen 
 23  @license: The BSD License 
 24   
 25  All rights reserved. This program and the accompanying materials are made 
 26  available under the terms of the BSD license which accompanies 
 27  this distribution, and is available at 
 28  U{http://www.opensource.org/licenses/bsd-license.php} 
 29  ''' 
 30  from AccessEngine.AEPor import AEPor 
 31  from AccessEngine.AEEvent import * 
 32  from AccessEngine.AEAccAdapter import AEAccAdapter 
 33  from AccessEngine.AEAccInterfaces import * 
 34  import pyatspi 
 35  from AccessEngine import AEConstants 
 36   
 37  # opt: AE to raw event mapping, only registers for raw events when desired 
 38  AE_MAP = {ViewChange : ['window:activate', 'window:deactivate',  
 39                          'window:create', 'window:destroy'], 
 40            FocusChange : ['focus', 'object:state-changed:focused'], 
 41            CaretChange : ['object:text-caret-moved', 
 42                           'object:text-changed'], 
 43            ChildrenChange : ['object:children-changed'], 
 44            PropertyChange : ['object:property-change'], 
 45            SelectorChange : ['object:text-selection-changed', 
 46                              'object:selection-changed', 
 47                              'object:active-descendant-changed'], 
 48            # opt: only register for state events that might be worth reporting  
 49            # to a script for the time being, lots of state change processing 
 50            # can really slow down the screen reader 
 51            StateChange : ['object:state-changed:expanded',  
 52                           'object:state-changed:checked', 
 53                           'object:state-changed:enabled', 
 54                           'object:state-changed:sensitive', 
 55                           'object:state-changed:animated', 
 56                           'object:state-changed:busy', 
 57                           'object:state-changed:visible', 
 58                           'object:state-changed:showing'], 
 59            TableChange : ['object:row-inserted', 
 60                           'object:row-deleted', 
 61                           'object:row-reordered', 
 62                           'object:column-inserted', 
 63                           'object:column-deleted', 
 64                           'object:column-reordered'], 
 65            ScreenChange : ['object:bounds-changed', 
 66                            'object:text-bounds-changed', 
 67                            'object:visible-data-changed'], 
 68            MouseChange : ['mouse:abs', 'mouse:button'], 
 69            PrivateChange : ['keyboard:press'] 
 70            } 
 71   
 72  EVENT_HANDLERS = { 
 73    'keyboard:press' : '_handleKeyPressEvent', 
 74    'window:activate': '_handleViewChange', 
 75    'focus' : '_handleFocusEvent', 
 76    'object:state-changed:focused' : '_handleFocusEvent', 
 77    'object:text-changed:insert' : '_handleTextEvent', 
 78    'object:text-changed:delete' : '_handleTextEvent', 
 79    'object:text-caret-moved' : '_handleCaretEvent', 
 80    'object:text-selection-changed' : '_handleTextSelectionEvent', 
 81    'object:selection-changed' : '_handleSelectionChangedEvent', 
 82    'object:active-descendant-changed': '_handleDescendantEvent', 
 83    'object:property-change:accessible-name': '_handlePropertyEvent', 
 84    'object:property-change:accessible-role': '_handlePropertyEvent', 
 85    'object:property-change:accessible-description': '_handlePropertyEvent', 
 86    'object:property-change:accessible-value': '_handlePropertyEvent', 
 87    'object:property-change:accessible-table-caption': '_handlePropertyEvent', 
 88    'object:property-change:accessible-table-summary' : '_handlePropertyEvent', 
 89    'object:property-change:accessible-table-column-description' :  
 90    '_handlePropertyEvent', 
 91    'object:property-change:accessible-table-row-description' :  
 92    '_handlePropertyEvent', 
 93    'object:children-changed:add' : '_handleChildrenEvent', 
 94    'object:children-changed:remove' : '_handleChildrenEvent', 
 95    'object:row-inserted' : '_handleTableEvent', 
 96    'object:row-deleted' : '_handleTableEvent', 
 97    'object:row-reordered' : '_handleTableEvent', 
 98    'object:column-inserted' : '_handleTableEvent', 
 99    'object:column-deleted' : '_handleTableEvent', 
100    'object:column-reordered' : '_handleTableEvent', 
101    'object:visible-data-changed' : '_handleScreenEvent', 
102    'object:text-bounds-changed' : '_handleScreenEvent', 
103    'object:object-bounds-changed' : '_handleScreenEvent' 
104    # PP: not handling accessible parent as it seems to cause problems 
105    #'object:property-change:accessible-parent' : '_handleHierarchyEvent', 
106  } 
107   
108 -class DefaultEventHandlerAdapter(AEAccAdapter):
109 ''' 110 Adapts all events from AT-SPI accessibles to the interfaces defined in 111 L{provides}. No condition for adaption is given implying that this adapter is 112 used as a default by L{AEAccAdapter} when no better adapter is available. 113 114 This class is meant to be subclassed by more specific event handlers. Only the 115 protected handler methods (those starting with _handle) need to be overridden 116 as the public method L{getAEEvents} will call the most child implementation of 117 the appropriate event handling method. 118 119 @cvar last_focus: Last accessible to receive focus 120 @type last_focus: pyatspi.Accessibility.Accessible 121 ''' 122 provides = [IEventHandler] 123 singleton = True 124 last_focus = None 125
126 - def getRawEvents(self, kind):
127 ''' 128 Gets a list of raw AT-SPI event names that map to the given kind of 129 L{AEEvent}. 130 131 @param kind: Indicates the type of L{AEEvent} some part of the system wants 132 to be able to process 133 @type kind: L{AEEvent} class 134 @return: List of AT-SPI event names 135 @rtype: list of string 136 @raise KeyError: When no mapping exists for the given event 137 ''' 138 return AE_MAP[kind]
139 140
141 - def getAEViewEvents(self, event, collector, vm):
142 ''' 143 Determines if the active view has changed and what events need to be 144 fired in response. The possible cases include a normal view change 145 (app1 window to app2 window), a floating widget change (app1 window to 146 app1 floater), or a overlay change (app1 window to app2 floater). Resets 147 the last focus when the third case occurs so that the first application 148 can announce its restored focus when the floater goes away. For instance, 149 if the metacity task switcher appears and the user immediately closes it, 150 he lands right back in the window where he started. In this case, the 151 new focus is the last focus, but we still want to announce it. 152 153 @param event: Raw event 154 @type event: C{pyatspi.event.Event} 155 @param collector: Callable object that collects L{AEEvent}s to process. 156 Accepts N parameters of type L{AEEvent}. 157 @type collector: callable 158 @param vm: Reference to the view manager that has information about the 159 current view and needs to store a reference to a new view 160 @type vm: L{AEViewManager} 161 ''' 162 try: 163 # make sure we can get the event type and role 164 et = event.type.major 165 role = event.source.getRole() 166 except Exception: 167 # bad event 168 return 169 170 # get if this is a window 171 is_win = role == pyatspi.ROLE_WINDOW 172 if et == 'activate' and (vm.setRawView(event.source) or 173 not vm.getRawActive()): 174 # only a view change if the event source wasn't already the active view 175 vm.setRawActive(True) 176 collector(ViewChange(vm.getRawView(), AEConstants.EVENT_VIEW_GAINED)) 177 elif vm.getRawView() is not None and et == 'deactivate': 178 por = AEPor(event.source) 179 if por == vm.getRawView(): 180 # only unset the flag if another activate hasn't been received 181 vm.setRawActive(False) 182 collector(ViewChange(por, AEConstants.EVENT_VIEW_LOST)) 183 elif not vm.getRawActive() and is_win: 184 # some other application created a floating window 185 if et == 'create' and vm.setRawView(event.source): 186 collector(ViewChange(vm.getRawView(), AEConstants.EVENT_VIEW_GAINED)) 187 # forget about the last focus so that a focus event can be fired when 188 # the previous application is activated 189 DefaultEventHandlerAdapter.last_focus = None 190 elif et == 'destroy': 191 collector(ViewChange(AEPor(event.source), AEConstants.EVENT_VIEW_LOST))
192 193
194 - def getAEEvents(self, event, collector):
195 ''' 196 Determines what L{AEEvent}s should be posted to L{AEEventManager} for later 197 execution by a L{AETier} based on the provided raw C{pyatspi.event.Event}. 198 Makes an initial decision about whether the event occurred in a focused 199 control or an unfocused control. 200 201 @param event: Raw event 202 @type event: C{pyatspi.event.Event} 203 @param collector: Callable object that collects L{AEEvent}s to process. 204 Accepts N parameters of type L{AEEvent}. 205 @type collector: callable 206 ''' 207 acc = self.subject 208 if acc is None: 209 try: 210 # handle the case where there is no subject, e.g. a device event 211 name = EVENT_HANDLERS[event.type.name] 212 method = getattr(self, name) 213 except (KeyError, AttributeError): 214 return 215 # always consider key events focused 216 focused = True 217 else: 218 try: 219 ss = acc.getState() 220 role = acc.getRole() 221 if (ss.contains(pyatspi.STATE_DEFUNCT) or 222 role == pyatspi.ROLE_REDUNDANT_OBJECT): 223 # abort immediately if the accessible has state defunct or if it has 224 # a redundant object role 225 return 226 227 # make a first guess as to whether the object is focused or not 228 # NOTE: fixed focus layer bug where events from unfocused controls 229 # came through as being from the focus; testing against last known 230 # focused control now, not just states except in case where no focus 231 # event has been received yet 232 lf = DefaultEventHandlerAdapter.last_focus 233 focused = (lf == event.source or 234 ss.contains(pyatspi.STATE_ACTIVE) or 235 (ss.contains(pyatspi.STATE_FOCUSED) and lf is None)) 236 except AttributeError: 237 # abort immediately if the CORBA object is dead 238 return 239 240 try: 241 # try to hash against an exact method 242 name = EVENT_HANDLERS[event.type.name] 243 method = getattr(self, name) 244 except (KeyError, AttributeError): 245 # if that fails, try to check the major event type 246 if event.type.klass == 'mouse': 247 method = self._handleMouseEvent 248 elif event.type.major == 'state-changed': 249 method = self._handleStateEvent 250 else: 251 return 252 253 if method == self._handleFocusEvent: 254 if self._filterFocusEvent(event): 255 # filter unwanted focus events 256 return 257 258 # call the method that handles this event 259 # keyword arguments will be passed as-is to AEEvent constructors 260 events = method(event, focused=focused) 261 262 if events: 263 if method == self._handleFocusEvent: 264 # synthesize focus lost events 265 if lf is not None: 266 lost_events = self._handleUnfocusEvent(lf) 267 if lost_events: collector(*lost_events) 268 DefaultEventHandlerAdapter.last_focus = event.source 269 # send the generated events 270 collector(*events)
271 272
273 - def _handleUnfocusEvent(self, source, **kwargs):
274 ''' 275 Creates an L{AEEvent.FocusChange} indicating that the accessible being 276 adapted has lost the focus. 277 278 @param source: Source of the last raw focus change event 279 @type source: C{pyatspi.Accessibility.Accessible} 280 @param kwargs: Parameters to be passed to any created L{AEEvent} 281 @type kwargs: dictionary 282 @return: L{AEEvent.FocusChange} 283 @rtype: tuple of L{AEEvent} 284 ''' 285 por = AEPor(source, None, 0) 286 return (FocusChange(por, False, **kwargs),)
287
288 - def _filterFocusEvent(self, event):
289 ''' 290 Determines if a focus event is a repeat on an already focused object or 291 not. 292 293 @param event: Source of the last raw focus change event 294 @type event: C{pyatspi.Accessibility.Accessible} 295 @return: True if this method believes the event should be ignored, False if 296 not 297 @rtype: boolean 298 ''' 299 return ((event.type.klass != 'focus' and not event.detail1) or 300 DefaultEventHandlerAdapter.last_focus == event.source or 301 event.source.getRole() == pyatspi.ROLE_PAGE_TAB_LIST)
302
303 - def _handleFocusEvent(self, event, **kwargs):
304 ''' 305 Creates an L{AEEvent.FocusChange} indicating that the accessible being 306 adapted has gained the focus. Also creates a L{AEEvent.SelectorChange}. 307 These two L{AEEvent}s will be posted by the caller. 308 309 @param event: Raw focus change event 310 @type event: C{pyatspi.event.Event} 311 @param kwargs: Parameters to be passed to any created L{AEEvent} 312 @type kwargs: dictionary 313 @return: L{AEEvent.FocusChange} and L{AEEvent.SelectorChange} 314 @rtype: tuple of L{AEEvent} 315 ''' 316 por = AEPor(self.subject, None, 0) 317 # adapt the accessible to an adapter which provides an IAccesssibleInfo 318 # interface and get the accessible's item text 319 item = IAccessibleInfo(por).getAccItemText() 320 # focus events are always in the focus layer 321 kwargs['focused'] = True 322 return (FocusChange(por, True, **kwargs), 323 SelectorChange(por, item, **kwargs))
324
325 - def _handlePropertyEvent(self, event, **kwargs):
326 ''' 327 Creates an L{AEEvent.PropertyChange} indicating that some simple property 328 of an accessible changed. These two L{AEEvent}s will be posted by the 329 caller. 330 331 The L{AEPor} for the event soruce returned by this method will be marked as 332 incomplete as the accessible may actually be an item. It will be resolved 333 at a later time by the L{AEEvent} if the event will actually be processed. 334 335 @param event: Raw property change event 336 @type event: C{pyatspi.event.Event} 337 @param kwargs: Parameters to be passed to any created L{AEEvent} 338 @type kwargs: dictionary 339 @return: Property change event 340 @rtype: tuple of L{AEEvent} 341 ''' 342 por = AEPor(self.subject, None, 0, incomplete=True) 343 # strip off the initial word 344 name = event.type.minor[len('accessible-'):] 345 if name == 'value': 346 # try to get the property value from the accessible 347 iv = (self.subject).queryValue() 348 value = iv.currentValue 349 elif name == 'role': 350 value = unicode(self.subject.getLocalizedRoleName(), 'utf-8') 351 else: 352 # otherwise use the string that ships with the event 353 value = unicode(event.any_data, 'utf-8') 354 return (PropertyChange(por, name, value, **kwargs),)
355
356 - def _handleStateEvent(self, event, **kwargs):
357 ''' 358 Creates an L{AEEvent.StateChange} indicating that some simple property of 359 an accessible changed. These two L{AEEvent}s will be posted by the caller. 360 361 The L{AEPor} for the event soruce returned by this method will be marked as 362 incomplete as the accessible may actually be an item. It will be resolved 363 at a later time by the L{AEEvent} if the event will actually be processed. 364 365 @param event: Raw state change event 366 @type event: C{pyatspi.event.Event} 367 @param kwargs: Parameters to be passed to any created L{AEEvent} 368 @type kwargs: dictionary 369 @return: L{AEEvent.StateChange} event 370 @rtype: tuple of L{AEEvent} 371 ''' 372 por = AEPor(self.subject, None, 0, incomplete=True) 373 return (StateChange(por, event.type.minor, event.detail1, **kwargs),)
374
375 - def _handleChildrenEvent(self, event, **kwargs):
376 ''' 377 Creates an L{AEEvent.ChildrenChange} indicating that a child has been added/ 378 removed to an accessible. This L{AEEvent} will be posted by the caller. 379 380 The L{AEPor} for the event soruce returned by this method will be marked as 381 incomplete as the accessible may actually be an item. It will be resolved 382 at a later time by the L{AEEvent} if the event will actually be processed. 383 384 @param event: Raw children change event 385 @type event: C{pyatspi.event.Event} 386 @param kwargs: Parameters to be passed to any created L{AEEvent} 387 @type kwargs: dictionary 388 @return: L{AEEvent.ChildrenChange} event 389 @rtype: tuple of L{AEEvent} 390 ''' 391 por = AEPor(self.subject, None, 0) 392 child_por = AEPor(event.any_data, None, 0, incomplete=True) 393 return (ChildrenChange(por, event.type.minor == 'add', child_por, 394 **kwargs),)
395
396 - def _handleTableEvent(self, event, **kwargs):
397 ''' 398 Creates an L{AEEvent.TableChange} indicating a insert/delete/reorder of a 399 row/column in a table-based accessible. This L{AEEvent} will be posted by 400 the caller. 401 402 @param event: Raw table change event 403 @type event: C{pyatspi.event.Event} 404 @param kwargs: Parameters to be passed to any created L{AEEvent} 405 @type kwargs: dictionary 406 @return: L{AEEvent.TableChange} event 407 @rtype: tuple of L{AEEvent} 408 ''' 409 por = AEPor(self.subject, None, 0) 410 # per AT-SPI 1.7 specification 411 it = (por.accessible).queryTable() 412 # index of first child of ins/del/reorder 413 index1 = it.getIndexAt(event.detail1, 0) 414 # index of last child of ins/del/reorder 415 index2 = it.getIndexAt(event.detail1 + event.detail2 - 1, 0) 416 first_child_por = AEPor(self.subject, index1, 0) 417 last_child_por = AEPor(self.subject, index2, 0) 418 name = event.type.major 419 # determine whether a row or column event? 420 is_row = name.startswith('row') 421 # event: inserted, deleted or reordered? 422 if name.endswith('inserted'): 423 added = True 424 elif name.endswith('deleted'): 425 added = False 426 else: # re-ordered 427 added = None 428 return (TableChange(por, is_row, added, first_child_por, 429 last_child_por, **kwargs),)
430
431 - def _handleScreenEvent(self, event, **kwargs):
432 ''' 433 Creates an L{AEEvent.ScreenChange} event in response to a visible data or 434 bounds change on an object or text. 435 436 @param event: Raw visibility or bounds change event 437 @type event: C{pyatspi.event.Event} 438 @param kwargs: Parameters to be passed to any created L{AEEvent} 439 @type kwargs: dictionary 440 @return: L{AEEvent.ScreenChange} event 441 @rtype: tuple of L{AEEvent} 442 ''' 443 major = event.type.major 444 if major.startswith('text'): 445 kind = AEConstants.EVENT_TEXT_BOUNDS 446 elif major.startswith('bounds'): 447 kind = AEConstants.EVENT_OBJECT_BOUNDS 448 else: 449 kind = AEConstants.EVENT_VISIBLE_DATA 450 por = AEPor(self.subject, None, 0, incomplete=True) 451 return (ScreenChange(por, kind, **kwargs),)
452
453 - def _handleMouseEvent(self, event, **kwargs):
454 ''' 455 Creates an L{AEEvent.ScreenChange} event in response to a visible data or 456 bounds change on an object or text. 457 458 @param event: Raw visibility or bounds change event 459 @type event: C{pyatspi.event.Event} 460 @param kwargs: Parameters to be passed to any created L{AEEvent} 461 @type kwargs: dictionary 462 @return: L{AEEvent.ScreenChange} event 463 @rtype: tuple of L{AEEvent} 464 ''' 465 major = event.type.major 466 if major == 'abs': 467 return (MouseChange(AEConstants.EVENT_MOUSE_MOVE, pos=(event.detail1, 468 event.detail2)),) 469 elif event.type.minor.endswith('p'): 470 kind = AEConstants.EVENT_MOUSE_PRESS 471 else: 472 kind = AEConstants.EVENT_MOUSE_RELEASE 473 return (MouseChange(kind, button=int(event.type.minor[0])),)
474
475 - def _handleKeyPressEvent(self, event, **kwargs):
476 ''' 477 Creates an L{AEEvent.PrivateChange} event in response to a key press on 478 the keyboard. This event is kept private because 479 L{AEScript <AEScript.AEScript>}s and tasks should not be relying on key 480 codes and key syms to trigger actions, but rather using abstracted 481 L{AEEvent.InputGesture} events and Input-Tasks. 482 483 @param event: Raw keyboard event 484 @type event: C{pyatspi.event.Event} 485 @param kwargs: Parameters to be passed to any created L{AEEvent} 486 @type kwargs: dictionary 487 @return: L{AEEvent.PrivateChange} event 488 @rtype: tuple of L{AEEvent} 489 ''' 490 return (PrivateChange(PrivateChange.KEY_PRESS, sym=event.any_data[0], 491 code=event.detail2, mod=event.any_data[1]),)
492