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

Source Code for Module AccessEngine.AEAccAdapters.ATSPI.TableAdapter

  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   
32 -class TableAccInfoAdapter(ContainerAccInfoAdapter):
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
43 - def when(por):
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 # make sure the role is a table 56 if r != pyatspi.ROLE_TABLE: 57 return False 58 # make sure the table interface exists 59 tab = acc.queryTable() 60 return True
61 62
63 - def getAccRow(self):
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
78 - def getAccColumn(self):
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
92 - def getAccRowColIndex(self, row, col):
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
112 - def getAccRowHeader(self):
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
127 - def getAccColumnHeader(self):
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
142 - def getAccTableExtents(self):
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
153 -class TableNavAdapter(DefaultNavAdapter):
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
171 - def when(subject):
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
188 - def _getVisibleItemExtents(self, only_visible):
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 # get the first item 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 # get the last item 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 # compute indices 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
232 - def getNextItem(self, only_visible=True):
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 # get the first and last indices 248 i, j = self._getVisibleItemExtents(only_visible) 249 if off is None or off < i: 250 # return the first visible, non-header item 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 # use the next higher index if it was found to be visible 257 return AEPor(acc, off+1, 0) 258 else: 259 # wrap to the first visible item in the next row 260 # compute the row and column offsets 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 # no more visible items 271 raise IndexError
272 273
274 - def getPrevItem(self, only_visible=True):
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 # get the first and last indices 291 i, j = self._getVisibleItemExtents(only_visible) 292 # and check if the first item is visible since it might be a header 293 if off is None: 294 # no more visible items 295 raise IndexError 296 elif off > j: 297 # return the last visible item 298 return AEPor(acc, j, 0) 299 elif off-1 >= i and off-1 <= j: 300 # compute the row and column offsets 301 if IAccessibleInfo(AEPor(acc.getChildAtIndex(off-1))).isAccVisible(): 302 # use the next higher index if it is visible 303 return AEPor(acc, off-1, 0) 304 else: 305 # wrap to the first visible item in the next row 306 # compute the row and column offsets 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 # return the table iteself 316 return AEPor(acc, None, 0)
317 318
319 - def getLastItem(self, only_visible=True):
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 # try getting the last item by index first 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 # use coords to get the last visible item 338 i, j = self._getVisibleItemExtents(only_visible) 339 return AEPor(acc, j, 0)
340 341
342 - def getFirstItem(self, only_visible=True):
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 # try getting the first item by index first 357 child = acc.getChildAtIndex(0) 358 if IAccessibleInfo(AEPor(child)).isAccVisible() or not only_visible: 359 return AEPor(acc, 0, 0) 360 # use coords to get the first visible item 361 i, j = self._getVisibleItemExtents(only_visible) 362 return AEPor(acc, i, 0)
363 364
365 - def getFirstAccChild(self):
366 ''' 367 Always raises LookupError. Tables have items but no children. 368 369 @raise LookupError: Always 370 ''' 371 raise LookupError
372 373
374 - def getLastAccChild(self):
375 ''' 376 Always raises LookupError. Tables have items but no children. 377 378 @raise LookupError: Always 379 ''' 380 raise LookupError
381 382
383 - def getChildAcc(self, index):
384 ''' 385 Always raises LookupError. Tables have items but no children. 386 387 @raise LookupError: Always 388 ''' 389 raise LookupError
390
391 -class DegenerateTableEventHandlerAdapter(DefaultEventHandlerAdapter):
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
403 - def when(subject):
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
415 - def _handleDescendantEvent(self, event, **kwargs):
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
429 - def _getSelectorEvent(self, accessible, item_offset, **kwargs):
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 # TODO: are the next two lines redundant? 450 if accessible.childCount > 0: 451 accessible = accessible.getChildAtIndex(accessible.childCount - 1) 452 453 # Notes: 454 # When adding support for more than one selection, we'll probably have 455 # to keep track of each state change (SELECTED or not) and report the most 456 # recently changed item or pair of items. Some examples: 457 # - a single item became selected or unselected 458 # - one item became unselected and an adjacent one became selected 459 # In the second case the script should probably receive both items 460 # The Selection interface has a list of one or more selected items. 461 # selection.nSelectedChildren indicates how many there are and 462 # selection.getSelectedChild(index) accesses one of them. 463 464 465 # create a POR, pass it and the item text at the POR to the AETier 466 por = AEPor(self.subject, item_offset, 0) 467 acc_child_text = IAccessibleInfo(por).getAccItemText() 468 return SelectorChange(por, acc_child_text, **kwargs)
469
470 -class TableEventHandlerAdapter(DegenerateTableEventHandlerAdapter):
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
482 - def when(subject):
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 #def _handleDescendantEvent(self, event, **kwargs): 497 #''' 498 #Creates an L{AEEvent.SelectorChange} indicating the "selector" moved in this 499 #accessible. 500 501 #@param event: Raw decendent changed event 502 #@type event: C{pyatspi.event.Event} 503 #@param kwargs: Parameters to be passed to any created L{AEEvent} 504 #@type kwargs: dictionary 505 #@return: L{AEEvent.SelectorChange} 506 #@rtype: tuple of L{AEEvent} 507 #''' 508 ## clear the seen descendant state for this POR 509 #try: 510 #del self.selection_state[por] 511 #except KeyError: 512 #pass 513 #parent = super(TableEventHandlerAdapter, self) 514 #return parent._handleDescendantEvent(event, **kwargs) 515 516 #def _handleSelectionChangedEvent(self, event, **kwargs): 517 #''' 518 #Creates an L{AEEvent.SelectorChange} indicating an item was added to or 519 #removed from a selection. 520 521 #@param event: Raw selection changed event 522 #@type event: C{pyatspi.event.Event} 523 #@param kwargs: Parameters to be passed to any created L{AEEvent} 524 #@type kwargs: dictionary 525 #@return: L{AEEvent.SelectorChange} 526 #@rtype: tuple of L{AEEvent} 527 #''' 528 #if event.type.minor == 'add': 529 #return (self._getSelectorEvent(event.any_data, event.detail1, **kwargs),) 530 #else: 531 #return (self._getSelectorEvent(event.any_data, event.detail1, **kwargs),) 532
533 - def _handleFocusEvent(self, event, **kwargs):
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 # build a focus event 547 kwargs['focused'] = True 548 por = AEPor(self.subject, None, 0) 549 focus_event = FocusChange(por, True, **kwargs) 550 551 # get the selection interface 552 selection = (self.subject).querySelection() 553 # ignore selector events when nothing is selected 554 if selection.nSelectedChildren == 0: 555 return (focus_event,) 556 # get the selected object, for now, just the first item in the selection 557 acc_child = selection.getSelectedChild(0) 558 if acc_child is None: 559 return (focus_event,) 560 item_offset = acc_child.getIndexInParent() 561 # build and return a selector event 562 return focus_event, self._getSelectorEvent(acc_child, item_offset,**kwargs)
563