1 '''
2 Defines L{AEAccAdapter.AEAccAdapter}s for AT-SPI table accessibles. Tables
3 implement the Table interface but not the Selection interface.
4
5 @author: Pete Brunet
6 @author: Peter Parente
7 @author: Eirikur Hallgrimsson
8 @organization: IBM Corporation
9 @copyright: Copyright (c) 2005, 2007 IBM Corporation
10 @license: The BSD License
11
12 @author: Frank Zenker
13 @organization: IT Science Center Ruegen gGmbH, Germany
14 @copyright: Copyright (c) 2007, 2008 ITSC Ruegen
15 @license: The BSD License
16
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD license which accompanies
19 this distribution, and is available at
20 U{http://www.opensource.org/licenses/bsd-license.php}
21 '''
22 from AccessEngine.AEPor import AEPor
23 from AccessEngine.AEEvent import *
24 from AccessEngine.AEAccInterfaces import *
25 from DefaultEventHandler import *
26 from DefaultNav import *
27 from ContainerAdapter import *
28 import pyatspi
29
30 FUDGE_PX = 5
31
33 '''
34 Overrides L{ContainerAccInfoAdapter} to generate selector events on focus
35 and on selection. Expects the subject to be a C{pyatspi.Accessibility.Accessible}.
36
37 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible.Table}
38 interface and have ROLE_TABLE.
39 '''
40 provides = [IAccessibleInfo]
41
42 @staticmethod
44 '''
45 Tests if the given POR can be adapted by this class.
46
47 @param por: Accessible to test
48 @type por: L{AEPor}
49 @return: True when the subject meets the condition named in the docstring
50 for this class, False otherwise
51 @rtype: boolean
52 '''
53 acc = por.accessible
54 r = acc.getRole()
55
56 if r != pyatspi.ROLE_TABLE:
57 return False
58
59 tab = acc.queryTable()
60 return True
61
62
64 '''
65 Gets the row of an item in a table.
66
67 @return: Zero indexed row of the item
68 @rtype: integer
69 @raise LookupError: When the table or item is no longer valid
70 '''
71 if self.item_offset is None:
72 return None
73 tab = (self.accessible).queryTable()
74
75 return tab.getRowAtIndex(self.item_offset)
76
77
79 '''
80 Gets the column of an item in a table.
81
82 @return: Zero indexed column of the item
83 @rtype: integer
84 @raise LookupError: When the table or item is no longer valid
85 '''
86 if self.item_offset is None:
87 return None
88 tab = (self.accessible).queryTable()
89 return tab.getColumnAtIndex(self.item_offset)
90
91
93 '''
94 Gets the 1D index of the cell at the given 2D row and column.
95
96 @param row: Row index
97 @type row: integer
98 @param col: Column index
99 @type col: integer
100 @return: 1D index into the table
101 @rtype: integer
102 @raise IndexError: When the row/column offsets are invalid
103 @raise LookupError: When the table is no longer valid
104 '''
105 tab = (self.accessible).queryTable()
106 i = tab.getIndexAt(row, col)
107 if i < 0:
108 raise IndexError
109 return i
110
111
113 '''
114 Gets the text description of a row in a table.
115
116 @return: The descriptive text.
117 @rtype: string
118 @raise LookupError: When the table or item is no longer valid
119 '''
120 tab = (self.accessible).queryTable()
121 if self.item_offset is not None:
122 row = tab.getRowAtIndex(self.item_offset)
123 return tab.getRowDescription(row)
124 return None
125
126
128 '''
129 Gets the text description of a column in a table.
130
131 @return: The descriptive text.
132 @rtype: string
133 @raise LookupError: When the table or item is no longer valid
134 '''
135 tab = (self.accessible).queryTable()
136 if self.item_offset is not None:
137 col = tab.getColumnAtIndex(self.item_offset)
138 return tab.getColumnDescription(col)
139 return None
140
141
143 '''
144 Returns the number of rows and columns in the table.
145
146 @return: Count of rows and columns
147 @rtype: 2-tuple of integer
148 @raise LookupError: When the table is no longer valid
149 '''
150 tab = (self.accessible).queryTable()
151 return (tab.nRows, tab.nColumns)
152
154 '''
155 Overrides L{DefaultNavAdapter} to provide navigation over table cells as
156 items. Expects the subject to be a L{AEPor}. Does not walk headers.
157 Those can be gotten and reported separately as context information.
158
159 Note that not all tables properly respond to requests for accessibles at
160 (x,y) coordinates on the screen. Most tables seem to always return their
161 first accessible (not first visible accessible) for the top left corner and
162 last accessible (not last visible accessible) for the bottom right corner, but
163 this depends on the application.
164
165 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible..ITable}
166 interface.
167 '''
168 provides = [IAccessibleNav, IItemNav]
169
170 @staticmethod
172 '''
173 Tests if the given subject can be adapted by this class.
174
175 @param subject: L{AEPor} containing an accessible to test
176 @type subject: L{AEPor}
177 @return: True when the subject meets the condition named in the docstring
178 for this class, False otherwise
179 @rtype: boolean
180 '''
181 acc = subject.accessible
182 ss = acc.getState()
183 if not ss.contains(pyatspi.STATE_MANAGES_DESCENDANTS):
184 return False
185 return acc.queryTable()
186
187
189 '''
190 Gets the item offsets of the first and last items in a table of cells.
191
192 @param only_visible: Only consider the first and last cells visible in
193 the table (True) or the absolute first and last cells (False)?
194 @type only_visible: boolean
195 @return: First and last item offsets
196 @rtype: 2-tuple of integer
197 @raise LookupError: When the first or last item or parent accessible is
198 not available
199 '''
200 acc = self.accessible
201 if only_visible:
202 comp = acc.queryComponent()
203 e = comp.getExtents(pyatspi.WINDOW_COORDS)
204
205 x, y = e.x+FUDGE_PX, e.y+FUDGE_PX
206 try:
207 first = comp.getAccessibleAtPoint(x, y, pyatspi.WINDOW_COORDS)
208 except TypeError:
209 first = None
210
211 x, y = e.x+e.width-FUDGE_PX, e.y+e.height-FUDGE_PX
212 try:
213 last = comp.getAccessibleAtPoint(x, y, pyatspi.WINDOW_COORDS)
214 except TypeError:
215 last = None
216 else:
217 first = None
218 last = None
219
220 if first:
221 i = first.getIndexInParent()
222 else:
223 i = 0
224 if last:
225 j = last.getIndexInParent()
226 else:
227 t = acc.queryTable()
228 j = t.getIndexAt(t.nRows-1, t.nColumns-1)
229 return i, j
230
231
233 '''
234 Gets the next item relative to the one indicated by the L{AEPor}
235 providing this interface.
236
237 @param only_visible: True when Item in the returned L{AEPor} must be visible
238 @type only_visible: boolean
239 @return: Point of regard to the next item in the same accessible
240 @rtype: L{AEPor}
241 @raise IndexError: When there is no next item
242 @raise LookupError: When lookup for the next item fails even though it may
243 exist
244 '''
245 acc = self.accessible
246 off = self.item_offset
247
248 i, j = self._getVisibleItemExtents(only_visible)
249 if off is None or off < i:
250
251 if not IAccessibleInfo(AEPor(acc.getChildAtIndex(i))).isAccVisible():
252 raise IndexError
253 return AEPor(acc, i, 0)
254 elif off+1 >= i and off+1 <= j:
255 if IAccessibleInfo(AEPor(acc.getChildAtIndex(off+1))).isAccVisible():
256
257 return AEPor(acc, off+1, 0)
258 else:
259
260
261 t = acc.queryTable()
262 r = t.getRowAtIndex(off)
263 r += 1
264 c = t.getColumnAtIndex(i)
265 n_off = t.getIndexAt(r, c)
266 if not IAccessibleInfo(AEPor(acc.getChildAtIndex(n_off))).isAccVisible():
267 raise IndexError
268 return AEPor(acc, n_off, 0)
269 else:
270
271 raise IndexError
272
273
275 '''
276 Gets the previous item relative to the one indicated by the L{AEPor} providing
277 this interface.
278
279 @param only_visible: True when Item in the returned L{AEPor} must be visible
280 @type only_visible: boolean
281 @return: Point of regard to the previous item in the same accessible
282 @rtype: L{AEPor}
283 @raise IndexError: When there is no previous item
284 @raise LookupError: When lookup for the previous item fails even though it
285 may exist
286 '''
287 acc = self.accessible
288 off = self.item_offset
289 comp = acc.queryComponent()
290
291 i, j = self._getVisibleItemExtents(only_visible)
292
293 if off is None:
294
295 raise IndexError
296 elif off > j:
297
298 return AEPor(acc, j, 0)
299 elif off-1 >= i and off-1 <= j:
300
301 if IAccessibleInfo(AEPor(acc.getChildAtIndex(off-1))).isAccVisible():
302
303 return AEPor(acc, off-1, 0)
304 else:
305
306
307 t = acc.queryTable()
308 r, c = t.getRowAtIndex(off), t.getColumnAtIndex(j)
309 r -= 1
310 n_off = t.getIndexAt(r, c)
311 if n_off <= i:
312 raise IndexError
313 return AEPor(acc, n_off, 0)
314 else:
315
316 return AEPor(acc, None, 0)
317
318
320 '''
321 Gets the last item relative to the one indicated by the L{AEPor}
322 providing this interface.
323
324 @param only_visible: True when Item in the returned L{AEPor} must be visible
325 @type only_visible: boolean
326 @return: Point of regard to the last item in the same accessible
327 @rtype: L{AEPor}
328 @raise LookupError: When lookup for the last item fails even though it may
329 exist
330 '''
331 acc = self.accessible
332 comp = acc.queryComponent()
333
334 child = acc.getChildAtIndex(acc.childCount-1)
335 if IAccessibleInfo(AEPor(child)).isAccVisible() or not only_visible:
336 return AEPor(acc, acc.childCount-1, 0)
337
338 i, j = self._getVisibleItemExtents(only_visible)
339 return AEPor(acc, j, 0)
340
341
343 '''
344 Gets the first item relative to the one indicated by the L{AEPor}
345 providing this interface.
346
347 @param only_visible: True when Item in the returned L{AEPor} must be visible
348 @type only_visible: boolean
349 @return: Point of regard to the last item in the same accessible
350 @rtype: L{AEPor}
351 @raise LookupError: When lookup for the last item fails even though it may
352 exist
353 '''
354 acc = self.accessible
355 comp = acc.queryComponent()
356
357 child = acc.getChildAtIndex(0)
358 if IAccessibleInfo(AEPor(child)).isAccVisible() or not only_visible:
359 return AEPor(acc, 0, 0)
360
361 i, j = self._getVisibleItemExtents(only_visible)
362 return AEPor(acc, i, 0)
363
364
366 '''
367 Always raises LookupError. Tables have items but no children.
368
369 @raise LookupError: Always
370 '''
371 raise LookupError
372
373
375 '''
376 Always raises LookupError. Tables have items but no children.
377
378 @raise LookupError: Always
379 '''
380 raise LookupError
381
382
384 '''
385 Always raises LookupError. Tables have items but no children.
386
387 @raise LookupError: Always
388 '''
389 raise LookupError
390
392 '''
393 Overrides L{DefaultEventHandlerAdapter} to generate selector events on
394 selection change. Does not generate the ideal selector events on focus
395 because the degenerate subject does not implement the Selection interface. As
396 a result, the active descendant cannot be determined. Expects the subject to
397 be a raw C{pyatspi.Accessibility.Accessible}.
398
399 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible..ITable}
400 interface.
401 '''
402 @staticmethod
404 '''
405 Tests if the given subject can be adapted by this class.
406
407 @param subject: Accessible to test
408 @type subject: C{pyatspi.Accessibility.Accessible}
409 @return: True when the subject meets the condition named in the docstring
410 for this class, False otherwise
411 @rtype: boolean
412 '''
413 return subject.queryTable()
414
416 '''
417 Creates an L{AEEvent.SelectorChange} indicating the "selector" moved in this
418 accessible.
419
420 @param event: Raw decendent changed event
421 @type event: C{pyatspi.event.Event}
422 @param kwargs: Parameters to be passed to any created L{AEEvent}
423 @type kwargs: dictionary
424 @return: L{AEEvent.SelectorChange}
425 @rtype: tuple of L{AEEvent}
426 '''
427 return (self._getSelectorEvent(event.any_data, event.detail1, **kwargs),)
428
430 '''
431 Creates an L{AEEvent.SelectorChange} indicating the selector moved in this
432 accessible.
433
434 This method corrects for the possibility that the selected item actually
435 have children that have the important information which are themselves not
436 selected but returned as children of the even source. Right now, the last
437 child in such a case appears to carry the information. More robust
438 processing may be needed in the future.
439
440 @param accessible: Accessible that generated this event
441 @type accessible: C{pyatspi.Accessibility.Accessible}
442 @param item_offset: Offset of item involved in the selection event
443 @type item_offset: integer
444 @param kwargs: Parameters to be passed to any created L{AEEvent}
445 @type kwargs: dictionary
446 @return: Selection event
447 @rtype: L{AEEvent.SelectorChange}
448 '''
449
450 if accessible.childCount > 0:
451 accessible = accessible.getChildAtIndex(accessible.childCount - 1)
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466 por = AEPor(self.subject, item_offset, 0)
467 acc_child_text = IAccessibleInfo(por).getAccItemText()
468 return SelectorChange(por, acc_child_text, **kwargs)
469
471 '''
472 Overrides L{DegenerateTableEventHandlerAdapter} to generate selector events
473 on focus and on selection. Expects the subject to be a raw
474 C{pyatspi.Accessibility.Accessibility.Accessible}.
475
476 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible..ISelection},
477 interface and have ROLE_TABLE or ROLE_TREE_TABLE.
478 '''
479 provides = [IEventHandler]
480
481 @staticmethod
483 '''
484 Tests if the given subject can be adapted by this class.
485
486 @param subject: Accessible to test
487 @type subject: C{pyatspi.Accessibility.Accessible}
488 @return: True when the subject meets the condition named in the docstring
489 for this class, False otherwise
490 @rtype: boolean
491 '''
492 r = subject.getRole()
493 return (r in (pyatspi.ROLE_TABLE, pyatspi.ROLE_TREE_TABLE) and
494 (subject).querySelection())
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
534 '''
535 Creates an L{AEEvent.FocusChange} indicating that the accessible being
536 adapted has gained the focus. Also creates a L{AEEvent.SelectorChange}.
537 These two L{AEEvent}s will be posted by the caller.
538
539 @param event: Raw focus change event
540 @type event: C{pyatspi.event.Event}
541 @param kwargs: Parameters to be passed to any created L{AEEvent}
542 @type kwargs: dictionary
543 @return: L{AEEvent.FocusChange} and L{AEEvent.SelectorChange}
544 @rtype: tuple of L{AEEvent}
545 '''
546
547 kwargs['focused'] = True
548 por = AEPor(self.subject, None, 0)
549 focus_event = FocusChange(por, True, **kwargs)
550
551
552 selection = (self.subject).querySelection()
553
554 if selection.nSelectedChildren == 0:
555 return (focus_event,)
556
557 acc_child = selection.getSelectedChild(0)
558 if acc_child is None:
559 return (focus_event,)
560 item_offset = acc_child.getIndexInParent()
561
562 return focus_event, self._getSelectorEvent(acc_child, item_offset,**kwargs)
563