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
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
49
50
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
105
106 }
107
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
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
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
164 et = event.type.major
165 role = event.source.getRole()
166 except Exception:
167
168 return
169
170
171 is_win = role == pyatspi.ROLE_WINDOW
172 if et == 'activate' and (vm.setRawView(event.source) or
173 not vm.getRawActive()):
174
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
181 vm.setRawActive(False)
182 collector(ViewChange(por, AEConstants.EVENT_VIEW_LOST))
183 elif not vm.getRawActive() and is_win:
184
185 if et == 'create' and vm.setRawView(event.source):
186 collector(ViewChange(vm.getRawView(), AEConstants.EVENT_VIEW_GAINED))
187
188
189 DefaultEventHandlerAdapter.last_focus = None
190 elif et == 'destroy':
191 collector(ViewChange(AEPor(event.source), AEConstants.EVENT_VIEW_LOST))
192
193
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
211 name = EVENT_HANDLERS[event.type.name]
212 method = getattr(self, name)
213 except (KeyError, AttributeError):
214 return
215
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
224
225 return
226
227
228
229
230
231
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
238 return
239
240 try:
241
242 name = EVENT_HANDLERS[event.type.name]
243 method = getattr(self, name)
244 except (KeyError, AttributeError):
245
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
256 return
257
258
259
260 events = method(event, focused=focused)
261
262 if events:
263 if method == self._handleFocusEvent:
264
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
270 collector(*events)
271
272
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
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
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
318
319 item = IAccessibleInfo(por).getAccItemText()
320
321 kwargs['focused'] = True
322 return (FocusChange(por, True, **kwargs),
323 SelectorChange(por, item, **kwargs))
324
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
344 name = event.type.minor[len('accessible-'):]
345 if name == 'value':
346
347 iv = (self.subject).queryValue()
348 value = iv.currentValue
349 elif name == 'role':
350 value = unicode(self.subject.getLocalizedRoleName(), 'utf-8')
351 else:
352
353 value = unicode(event.any_data, 'utf-8')
354 return (PropertyChange(por, name, value, **kwargs),)
355
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
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
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
411 it = (por.accessible).queryTable()
412
413 index1 = it.getIndexAt(event.detail1, 0)
414
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
420 is_row = name.startswith('row')
421
422 if name.endswith('inserted'):
423 added = True
424 elif name.endswith('deleted'):
425 added = False
426 else:
427 added = None
428 return (TableChange(por, is_row, added, first_child_por,
429 last_child_por, **kwargs),)
430
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
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
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