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

Source Code for Module AccessEngine.AEAccAdapters.ATSPI.HypertextAdapter

  1  ''' 
  2  Defines L{AEAccAdapter.AEAccAdapter}s for AT-SPI hypertext accessibles  
  3  potentially containing embedded objects. 
  4   
  5  @var EMBED_CHAR: Unicode embed character u'\u0xfffc' 
  6  @type EMBED_CHAR: unicode 
  7  @var EMBED_VAL: Unicode embed character value 0xfffc 
  8  @type EMBED_VAL: integer 
  9  @var embed_rex: Compiled regular expression for finding embed characters 
 10  @type embed_rex: _sre.SRE_Pattern 
 11   
 12  @author: Peter Parente 
 13  @organization: IBM Corporation 
 14  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
 15  @license: The BSD License 
 16   
 17  @author: Frank Zenker 
 18  @organization: IT Science Center Ruegen gGmbH, Germany 
 19  @copyright: Copyright (c) 2007, 2008 ITSC Ruegen 
 20  @license: The BSD License 
 21   
 22  All rights reserved. This program and the accompanying materials are made 
 23  available under the terms of the BSD license which accompanies 
 24  this distribution, and is available at 
 25  U{http://www.opensource.org/licenses/bsd-license.php} 
 26  ''' 
 27  import re 
 28  import pyatspi 
 29  from DefaultNav import * 
 30  from DefaultInfo import * 
 31  from TextAdapter import * 
 32  from AccessEngine.AEAccInterfaces import * 
 33  from AccessEngine.AEPor import AEPor 
 34   
 35  EMBED_CHAR = u'\ufffc' 
 36  EMBED_VAL = 0xfffc 
 37  embed_rex = re.compile(EMBED_CHAR) 
 38   
39 -def _getPrevItem(acc, char):
40 ''' 41 Gets the offset of the start of the previous item. The first of the following 42 rules satisfied working backward from the offset in char defines the start 43 of the previous item: 44 45 1) the previous embed 46 2) the beginning of a wrapped line 47 3) the character one greater than the previous embed on the previous line 48 49 @param acc: Accessible object supporting embed characters 50 @type acc: C{pyatspi.Accessibility.Accessible} 51 @param char: Starting offset relative to the start of all text in the object 52 @type char: integer 53 @return: Index of the start of the previous item relative to the start of all 54 text in the object 55 @rtype: integer 56 @raise IndexError: When no previous item is available in this object 57 ''' 58 if char < 0: char = 0 59 it = acc.queryText() 60 # get the current line start and end offsets 61 text, lstart, lend = \ 62 it.getTextAtOffset(char, pyatspi.TEXT_BOUNDARY_LINE_START) 63 # if we're at the beginning of a line, use the previous line instead 64 if lstart == char: 65 text, lstart, lend = \ 66 it.getTextAtOffset(char-1, pyatspi.TEXT_BOUNDARY_LINE_START) 67 text = unicode(text, 'utf-8') 68 # compute the item offset relative to the start of this line 69 rio = char-lstart 70 # seek backward from the starting offset 71 index = text.rfind(EMBED_CHAR, 0, rio) 72 if index < 0: 73 if rio <= 0: 74 # beginning of text 75 raise IndexError 76 else: 77 # start of line 78 return lstart 79 elif (rio-index) > 1: 80 # if we didn't move back just one character, there's some more text on 81 # the previous line that should count as its own chunk, so move ahead one 82 # again 83 return lstart+index+1 84 else: 85 # return the offset of the embed 86 return lstart+index
87
88 -def _getNextEmbedCharOffset(acc, char):
89 ''' 90 Gets the offset of the start of the next item. The first of the following 91 rules satisfied working forward from the offset in char defines the start 92 of the next item: 93 94 1) the next embed 95 2) the beginning of a wrapped line 96 97 @param acc: Accessible object supporting embed characters 98 @type acc: C{pyatspi.Accessibility.Accessible} 99 @param char: Starting offset relative to the start of all text in the object 100 @type char: integer 101 @return: Index of the start of the next item relative to the start of all 102 text in the object 103 @rtype: integer 104 @raise IndexError: When no next item is available in this object 105 ''' 106 if char < 0: char = 0 107 it = acc.queryText() 108 # get the current line start and end offsets 109 text, lstart, lend = \ 110 it.getTextAtOffset(char, pyatspi.TEXT_BOUNDARY_LINE_START) 111 text = unicode(text, 'utf-8') 112 # compute the item offset relative to the start of this line 113 rio = char-lstart 114 # seek forward from the starting offset 115 index = text.find(EMBED_CHAR, rio) 116 if index < 0: 117 if lend >= it.characterCount-1: 118 # end of text 119 raise IndexError 120 else: 121 # end of line 122 return lend 123 return lstart+index
124
125 -def _getEmbedCharOffset(acc, index):
126 ''' 127 Gets the character offset of the embedded character at the given index. 128 129 @param acc: Accessible which should be searched for embed characters 130 @type acc: C{pyatspi.Accessibility.Accessible} 131 @param index: Index of the embedded character in the count of all embedded 132 characters 133 @type index: integer 134 @return: Offset in characters of the embedded character 135 @rtype: integer 136 @raise IndexError: When the given index is outside the bounds of the number 137 of embed characters in the text 138 ''' 139 if index is None: 140 # no embedded characters at None index 141 raise IndexError 142 elif index < 0: 143 # no embedded characters, return start index 144 return 0 145 # IHypertext(acc).getLink(index) returns the Hyperlink object associated with 146 # this index 147 hlink = acc.queryHypertext().getLink(index) 148 # returns the starting offset of the Hyperlink within the Hypertext object 149 return hlink.startIndex
150 151 #htext.getLinkIndex(self.item_offset) 152 #link = htext.getLink(i-1) 153 #if link is None: 154 #return AEPor(acc, 0, 0) 155 #else: 156 #return AEPor(acc, link.endOffset, 0) 157
158 -class HypertextNavAdapter(DefaultNavAdapter):
159 ''' 160 Overrides L{DefaultNavAdapter} to provide navigation over hypertext embedded 161 objects as items and children. Expects the subject to be a L{AEPor}. 162 163 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible.Hypertext} 164 and C{pyatspi.Accessibility.Accessible.Text} interfaces. 165 ''' 166 provides = [IAccessibleNav, IItemNav] 167 168 @staticmethod
169 - def when(subject):
170 ''' 171 Tests if the given subject can be adapted by this class. 172 173 @param subject: L{AEPor} containing an accessible to test 174 @type subject: L{AEPor} 175 @return: True when the subject meets the condition named in the docstring 176 for this class, False otherwise 177 @rtype: boolean 178 ''' 179 acc = subject.accessible 180 acc.queryHypertext() 181 acc.queryText() 182 return True
183 184
185 - def getNextItem(self, only_visible=True):
186 ''' 187 Gets the next item relative to the one indicated by the L{AEPor} providing 188 this interface. 189 190 Currently ignores only_visible. 191 192 @param only_visible: True when Item in the returned L{AEPor} must be visible 193 @type only_visible: boolean 194 @return: Point of regard to the next item in the same accessible 195 @rtype: L{AEPor} 196 @raise IndexError: When there is no next item 197 @raise LookupError: When lookup for the next item fails even though it may 198 exist 199 ''' 200 # get local references to accessible, link offset, etc. 201 acc = self.accessible 202 if self.item_offset is None: 203 char = 0 204 else: 205 char = self.item_offset + 1 206 text = acc.queryText() 207 htext = acc.queryHypertext() 208 count = text.characterCount 209 # look one character ahead 210 if char >= count: 211 # if it does not exist, raise IndexError 212 raise IndexError 213 elif char != 0 and text.getCharacterAtOffset(char-1) != EMBED_VAL: 214 # if we're currently on text, not an item, find the next embed 215 char = _getNextEmbedCharOffset(acc, char-1) 216 # continue with the remaining if statements using the new char 217 if text.getCharacterAtOffset(char) == EMBED_VAL: 218 # if it's the embed character, return the POR of the embed 219 index = htext.getLinkIndex(char) 220 if index < 0: 221 # raise an error if there is a next embed, but it's not fetchable 222 raise LookupError 223 return AEPor(acc.getChildAtIndex(index), None, 0) 224 #return IAccessibleNav(por).getFirstItem(only_visible) 225 else: 226 # else, return the POR for the next chunk of text 227 return AEPor(acc, char, 0)
228 229
230 - def getPrevItem(self, only_visible=True):
231 '''' 232 Gets the previous item relative to the one indicated by the L{AEPor} 233 providing this interface. 234 235 Currently ignores only_visible. 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 previous item in the same accessible 240 @rtype: L{AEPor} 241 @raise IndexError: When there is no previous item 242 @raise LookupError: When lookup for the previous item fails even though it 243 may exist 244 ''' 245 # get local references to accessible, link offset, ... 246 acc = self.accessible 247 if self.item_offset is None: 248 raise IndexError 249 #char = 0 250 else: 251 char = self.item_offset - 1 252 text = acc.queryText() 253 htext = acc.queryHypertext() 254 # look one character back 255 if char < 0: 256 # if it does not exist, return the item offset as None 257 return AEPor(acc, None, 0) 258 elif text.getCharacterAtOffset(char+1) != EMBED_VAL: 259 # if we're currently on text, not an item, find the previous embed 260 char = _getPrevItem(acc, char+1) 261 # continue with the remaining if statements using the new char 262 263 if text.getCharacterAtOffset(char) == EMBED_VAL: 264 # if it's the embed character, return the POR of the embed 265 index = htext.getLinkIndex(char) 266 if index < 0: 267 # raise an error if there is a previous embed, but it's not fetchable 268 raise LookupError 269 por = AEPor(acc.getChildAtIndex(index), None, 0) 270 li = IItemNav(por).getLastItem(only_visible) 271 return li 272 else: 273 # else, return the POR for the previous chunk of text 274 por = AEPor(acc, char, 0) 275 return por
276 277
278 - def getLastItem(self, only_visible=True):
279 ''' 280 Gets the last item relative to the one indicated by the L{AEPor} 281 providing this interface. 282 283 Currently ignores only_visible. 284 285 @param only_visible: True when Item in the returned L{AEPor} must be visible 286 @type only_visible: boolean 287 @return: Point of regard to the last item in the same accessible 288 @rtype: L{AEPor} 289 @raise LookupError: When lookup for the last item fails even though it may 290 exist 291 ''' 292 acc = self.accessible 293 text = acc.queryText() 294 htext = acc.queryHypertext() 295 if text.getCharacterAtOffset(text.characterCount-1) == EMBED_VAL: 296 # return the POR of the last embed 297 por = AEPor(acc.getChildAtIndex(acc.childCount-1), None, 0) 298 return IItemNav(por).getLastItem(only_visible) 299 else: 300 # get the previous embed + 1 301 char = _getPrevItem(acc, text.characterCount-1) 302 if text.getCharacterAtOffset(char) == EMBED_VAL: 303 # move ahead one from the located embed 304 return AEPor(acc, char+1, 0) 305 else: 306 # just use the given POR 307 return AEPor(acc, char, 0)
308 309
310 - def getFirstItem(self, only_visible=True):
311 ''' 312 Gets the first item relative to the one indicated by the L{AEPor} 313 providing this interface. 314 315 Currently ignores only_visible. 316 317 @param only_visible: True when Item in the returned L{AEPor} must be visible 318 @type only_visible: boolean 319 @return: Point of regard to the first item in the same accessible 320 @rtype: L{AEPor} 321 @raise LookupError: When lookup for the first item fails even though it may 322 exist 323 ''' 324 # don't recurse into embeds, let a Script handle that if it doesn't want to 325 # deal with item_offset=None blankness 326 return AEPor(self.accessible, None, 0)
327 328
329 - def getAccAsItem(self, por):
330 ''' 331 Converts the L{AEPor} to a child accessible to an equivalent L{AEPor} to an 332 item of the subject. 333 334 @param por: Point of regard to a child of the subject 335 @type por: L{AEPor} 336 @return: Point of regard to an item of the subject 337 @rtype: L{AEPor} 338 @raise LookupError: When lookup for the offset fails 339 @raise IndexError: When the offset of the child is invalid as an item index 340 ''' 341 index = IAccessibleInfo(por).getAccIndex() 342 off = _getEmbedCharOffset(self.accessible, index) 343 por = AEPor(self.accessible, off, 0) 344 return por
345 346
347 - def getFirstAccChild(self):
348 ''' 349 Always raises LookupError. Hypertext has no children per se, only embeds. 350 351 @raise LookupError: Always 352 ''' 353 raise LookupError
354 355
356 - def getLastAccChild(self):
357 ''' 358 Always raises LookupError. Hypertext has no children per se, only embeds. 359 360 @raise LookupError: Always 361 ''' 362 raise LookupError
363 364
365 - def getChildAcc(self, index):
366 ''' 367 Always raises LookupError. Hypertext has no children per se, only embeds. 368 369 @raise LookupError: Always 370 ''' 371 raise LookupError
372
373 -class HypertextAccInfoAdapter(DefaultAccInfoAdapter):
374 ''' 375 Overrides L{DefaultNavAdapter} to provide information about hypertext 376 objects. Expects the subject to be a L{AEPor}. 377 378 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible.Hypertext} 379 and C{pyatspi.Accessibility.Accessible.Hypertext} interfaces. 380 ''' 381 provides = [IAccessibleInfo] 382 383 @staticmethod
384 - def when(subject):
385 ''' 386 Tests if the given subject can be adapted by this class. 387 388 @param subject: L{AEPor} containing an accessible to test 389 @type subject: L{AEPor} 390 @return: True when the subject meets the condition named in the docstring 391 for this class, False otherwise 392 @rtype: boolean 393 ''' 394 acc = subject.accessible 395 off = subject.item_offset 396 acc.queryHypertext() 397 text = acc.queryText() 398 return True
399
400 - def allowsAccEmbeds(self):
401 ''' 402 Always True. Hypertext allows embedding. 403 404 @return: True 405 @rtype: boolean 406 ''' 407 return True
408 409
410 - def getAccItemText(self):
411 ''' 412 Gets a chunk of accessible text past the embed character indicated by the 413 item offset to the next embed character or end of line. 414 415 @return: Accessible text of requested item 416 @rtype: string 417 @raise LookupError: When the accessible object is dead 418 ''' 419 if self.item_offset is None: 420 return self.accessible.name 421 it = (self.accessible).queryText() 422 # get the current line start and end offsets 423 text, lstart, lend = \ 424 it.getTextAtOffset(self.item_offset,pyatspi.TEXT_BOUNDARY_LINE_START) 425 # convert to unicode 426 text = unicode(text, 'utf-8') 427 # compute the item offset relative to the start of this line 428 ro = self.item_offset-lstart 429 # locate the next embed character 430 i = text.find(EMBED_CHAR, ro) 431 if i < 0: 432 return text[ro:] # text is substring, not all text which io is relative to 433 else: 434 # slice up to the embed character 435 return text[ro:i]
436
437 -class HypertextEventHandlerAdapter(TextEventHandlerAdapter):
438 ''' 439 Overrides L{DefaultEventHandlerAdapter} to create proper L{AEPor}s for 440 hypertext objects having embed characters. Expects the subject to be a raw 441 C{pyatspi.Accessible}. 442 443 Adapts subject accessibles that provide the C{pyatspi.Accessibility.Accessible.Hypertext} 444 and C{pyatspi.Accessibility.Accessible.Text} interfaces. 445 ''' 446 provides = [IEventHandler] 447 448 @staticmethod
449 - def when(subject):
450 ''' 451 Tests if the given subject can be adapted by this class. 452 453 @param subject: Accessible to test 454 @type subject: C{pyatspi.Accessibility.Accessible} 455 @return: True when the subject meets the condition named in the docstring 456 for this class, False otherwise 457 @rtype: boolean 458 ''' 459 subject.queryHypertext() 460 subject.queryText() 461 return True
462
463 - def _handleFocusEvent(self, event, **kwargs):
464 ''' 465 Creates an L{AEEvent.FocusChange} indicating that the accessible being 466 adapted has gained the focus. Corrects the L{AEPor} for the focus to account 467 for the case where the hypertext object receiving the focus has an embed 468 character at the first position in its text such that the embedded object 469 should probably be the target of the first selector event instead. 470 471 @param event: Raw focus change event 472 @type event: C{pyatspi.event.Event} 473 @param kwargs: Parameters to be passed to any created L{AEEvent} 474 @type kwargs: dictionary 475 @return: L{AEEvent.FocusChange} and L{AEEvent.SelectorChange} 476 @rtype: tuple of L{AEEvent} 477 ''' 478 focus_por = AEPor(self.subject, None, 0) 479 # navigate to the first item of the given POR 480 item_por = IItemNav(focus_por).getFirstItem(False) 481 # adapt the accessible to an adapter which provides an IAccesssibleInfo 482 # interface and get the accessible's item text 483 item = IAccessibleInfo(item_por).getAccItemText() 484 # focus events are always in the focus layer 485 kwargs['focused'] = True 486 return (FocusChange(focus_por, True, **kwargs), 487 SelectorChange(item_por, item, **kwargs))
488