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