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

Source Code for Module AccessEngine.AEAccAdapters.ATSPI.ComboboxAdapter

  1  ''' 
  2  Defines L{AEAccAdapter.AEAccAdapter}s for combo boxes that receive the focus but  
  3  have children that fire events. 
  4   
  5   
  6  @author: Peter Parente 
  7  @organization: IBM Corporation 
  8  @copyright: Copyright (c) 2005, 2007 IBM Corporation 
  9  @license: The BSD License 
 10   
 11  @author: Frank Zenker 
 12  @author: Nicole Anacker 
 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   
 23  from AccessEngine.AEPor import AEPor 
 24  from AccessEngine.AEEvent import * 
 25  from AccessEngine.AEAccInterfaces import * 
 26  from TextAdapter import TextEventHandlerAdapter 
 27  import pyatspi 
 28  from DefaultNav import * 
 29   
 30  import logging 
 31  log = logging.getLogger('ComboboxAdapter') 
 32   
33 -class ComboboxEventHandlerAdapter(TextEventHandlerAdapter):
34 ''' 35 Overrides L{TextEventHandlerAdapter} to enable processing of events from 36 text areas within combo boxes where the combo box gets focus, the text area 37 doesn't, and the text area is the source of all text events. Fires a 38 L{AEEvent.FocusChange} and either a L{AEEvent.CaretChange} or 39 L{AEEvent.SelectorChange} on focus. Expects the subject to be a 40 C{pyatspi.Accessibility.Accessible}. 41 42 Adapts subject accessibles that have a role of combo box or whose parent have 43 have a role of combo box. 44 ''' 45 provides = [IEventHandler] 46 47 @staticmethod
48 - def when(subject):
49 ''' 50 Tests if the given subject can be adapted by this class. 51 52 @param subject: Accessible to test 53 @type subject: C{pyatspi.Accessibility.Accessible} 54 @return: True when the subject meets the condition named in the docstring 55 for this class, False otherwise 56 @rtype: boolean 57 ''' 58 r = subject.getRole() 59 pr = subject.parent.getRole() 60 roles = (pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_EDITBAR, pyatspi.ROLE_AUTOCOMPLETE) 61 return (r in roles or pr in roles)
62
63 - def _getTextArea(self):
64 ''' 65 Looks for a widgets supporting the text interface within the combo box. 66 67 @return: The accessible that provides the text interface and the accessible 68 already queried to that interface 69 @rtype: 2-tuple of C{pyatspi.Accessibility.Accessible} 70 ''' 71 text = None 72 # see if the combobox is acting as a proxy for the text box 73 try: 74 text = self.subject.queryText() 75 return self.subject, text 76 except NotImplementedError: 77 pass 78 # if not, find the child text box 79 for i in xrange(self.subject.childCount): 80 try: 81 acc = self.subject.getChildAtIndex(i) 82 text = acc.queryText() 83 return acc, text 84 except NotImplementedError: 85 pass 86 raise NotImplementedError
87
88 - def _isFocused(self):
89 ''' 90 Gets if the subject or its parent is focused. 91 92 @return: Does the subject or its parent have focus? 93 @rtype: boolean 94 ''' 95 f = pyatspi.STATE_FOCUSED 96 if (self.subject.getState().contains(f) or 97 self.subject.parent.getState().contains(f)): 98 return True 99 return False
100
101 - def _handleFocusEvent(self, event, **kwargs):
102 ''' 103 Creates an L{AEEvent.FocusChange} indicating that the accessible being 104 adapted has gained the focus. Also a L{AEEvent.CaretChange}. This sequence 105 of L{AEEvent}s will be posted by the caller. 106 107 @param event: Raw focus change event 108 @type event: C{pyatspi.event.Event} 109 @param kwargs: Parameters to be passed to any created L{AEEvent} 110 @type kwargs: dictionary 111 @return: L{AEEvent.FocusChange} and L{AEEvent.CaretChange} 112 @rtype: tuple of L{AEEvent} 113 ''' 114 # build a focus event on the subject 115 kwargs['focused'] = True 116 por = AEPor(self.subject, None, 0) 117 focus_evt = FocusChange(por, True, **kwargs) 118 119 # try to locate the text area in the combo box 120 try: 121 acc, text = self._getTextArea() 122 except NotImplementedError: 123 # if we don't support text, then we should have a selection at least 124 item = IAccessibleInfo(por).getAccItemText() 125 return (focus_evt, SelectorChange(por, item, **kwargs)) 126 127 char_offset = text.caretOffset 128 # get the text of the caret line, it's starting and ending offsets 129 line, sOff, eOff = \ 130 text.getTextAtOffset(char_offset, pyatspi.TEXT_BOUNDARY_LINE_START) 131 # create a POR with start offset of the line and relative caret offset 132 por = AEPor(acc, sOff, char_offset - sOff) 133 134 # create a focus event for this POR 135 # and add a caret event to indicate the current position 136 return (focus_evt, CaretChange(por, unicode(line, 'utf-8'), sOff, **kwargs))
137
138 - def _handleTextEvent(self, event, focused, **kwargs):
139 ''' 140 Called when text is inserted or deleted (object:text-changed:insert & 141 object:text-changed:delete). Creates and returns an L{AEEvent.CaretChange} 142 to indicate a change in the caret context. 143 144 C{pyatspi.event.Event.type.minor} is "insert" or "delete". 145 C{pyatspi.event.Event.detail1} has start offset of text change. 146 C{pyatspi.event.Event.detail2} has length of text change. 147 C{pyatspi.event.Event.any_data} has text inserted/deleted. 148 149 @param event: Raw text-changed event 150 @type event: C{pyatspi.event.Event} 151 @param kwargs: Parameters to be passed to any created L{AEEvent} 152 @type kwargs: dictionary 153 @return: L{AEEvent.CaretChange} 154 @rtype: tuple of L{AEEvent} 155 ''' 156 # try to locate the text area in the combo box 157 try: 158 acc, text = self._getTextArea() 159 except NotImplementedError: 160 return None 161 162 # recompute focus 163 focused = focused or self._isFocused() 164 165 # get the text of the caret line, it's starting and ending offsets 166 line, sOff, eOff = \ 167 text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_LINE_START) 168 # create a POR with start offset of the line and relative caret offset 169 por = AEPor(self.subject, sOff, text.caretOffset - sOff) 170 171 # create Caret event with current POR, text added/removed, where it changed, 172 # and whether it was inserted. 173 return (CaretChange(por, unicode(event.any_data, 'utf-8'), 174 event.detail1, (event.type.minor == 'insert'), 175 focused=focused, **kwargs),)
176
177 - def _handleCaretEvent(self, event, focused, **kwargs):
178 ''' 179 Creates and returns an L{AEEvent.CaretChange} indicating the caret moved in 180 the subject accessible. 181 182 C{pyatspi.event.Event.detail1} has caret offset. 183 184 @param event: Raw caret movement event 185 @type event: C{pyatspi.event.Event} 186 @param kwargs: Parameters to be passed to any created L{AEEvent} 187 @type kwargs: dictionary 188 @return: L{AEEvent.CaretChange} 189 @rtype: tuple of L{AEEvent} 190 ''' 191 # try to locate the text area in the combo box 192 try: 193 acc, text = self._getTextArea() 194 except NotImplementedError: 195 return None 196 197 # recompute focus 198 focused = focused or self._isFocused() 199 200 # detail1 has new caret position 201 caret_offset = event.detail1 202 203 # get the text of the new caret line, it's starting and ending offsets 204 line, sOff, eOff = \ 205 text.getTextAtOffset(caret_offset, pyatspi.TEXT_BOUNDARY_LINE_START) 206 207 # create a POR with start offset of the line and relative caret offset 208 por = AEPor(self.subject, sOff, caret_offset - sOff) 209 210 # create Caret event with new POR, line at caret, and offset of caret 211 return (CaretChange(por, unicode(line, 'utf-8'), caret_offset, 212 focused=focused, **kwargs),)
213 214
215 - def _handleSelectionChangedEvent(self, event, focused, **kwargs):
216 ''' 217 Creates an L{AEEvent.SelectorChange}. 218 219 @param event: Raw selection changed event 220 @type event: C{pyatspi.event.Event} 221 @param kwargs: Parameters to be passed to any created L{AEEvent} 222 @type kwargs: dictionary 223 @return: L{AEEvent.SelectorChange} 224 @rtype: tuple of L{AEEvent} 225 ''' 226 if not focused: 227 return None 228 try: 229 acc, text = self._getTextArea() 230 except NotImplementedError: 231 # if we don't support text, then we should have a selection at least 232 por = AEPor(self.subject, None, 0) 233 item = IAccessibleInfo(por).getAccItemText() 234 return (SelectorChange(por, item, **kwargs),)
235
236 -class ComboBoxNavAdapter(DefaultNavAdapter):
237 ''' 238 Overrides L{DefaultNavAdapter} to provide navigation over text lines as 239 items and to avoid traversing text children as separate accessible children 240 in the L{IAccessibleNav} interface. 241 242 Adapts accessibles that have a role of terminal or have a state of multiline, 243 single line, or editable and provide the Text interface. 244 Does not adapt L{AEPor} accessibles with role of page tab. 245 ''' 246 provides = [IItemNav, IAccessibleNav] 247 248 @staticmethod
249 - def when(subject):
250 ''' 251 Tests if the given subject can be adapted by this class. 252 253 @param subject: Accessible to test 254 @type subject: C{pyatspi.Accessibility.Accessible} 255 @return: True when the subject meets the condition named in the docstring 256 for this class, False otherwise 257 @rtype: boolean 258 ''' 259 acc = subject.accessible 260 r = acc.getRole() 261 pr = acc.parent.getRole() 262 roles = (pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_EDITBAR, pyatspi.ROLE_AUTOCOMPLETE) 263 return (r in roles or pr in roles)
264
265 - def getNextAcc(self):
266 ''' 267 Gets the next accessible relative to the one providing this interface. 268 269 @return: Point of regard to the next accessible 270 @rtype: L{AEPor} 271 @raise IndexError: When there is no next accessible 272 @raise LookupError: When lookup for the next accessible fails even though 273 it may exist 274 ''' 275 if self.accessible.getRole() == pyatspi.ROLE_COMBO_BOX: 276 cb = self.accessible 277 else: 278 cb = self.accessible.parent 279 280 i = cb.getIndexInParent() 281 has_parent = cb.parent is not None 282 if i < 0 or not has_parent: 283 # indicate lookup of the next peer failed 284 raise LookupError 285 # get the accessible at the next index (the peer) 286 child = cb.parent.getChildAtIndex(i+1) 287 if child is None: 288 # indicate there is no next peer 289 raise IndexError 290 return AEPor(child, None, 0)
291
292 - def getPrevAcc(self):
293 ''' 294 Gets the previous accessible relative to the one providing this interface. 295 296 @return: Point of regard to the previous accessible 297 @rtype: L{AEPor} 298 @raise IndexError: When there is no previous accessible 299 @raise LookupError: When lookup for the previous accessible fails even 300 though it may exist 301 ''' 302 if self.accessible.getRole() == pyatspi.ROLE_COMBO_BOX: 303 cb = self.accessible 304 else: 305 cb = self.accessible.parent 306 307 i = cb.getIndexInParent() 308 has_parent = cb.parent is not None 309 if i <= 0 or not has_parent: 310 # indicate lookup of the previous peer failed 311 raise LookupError 312 # get the accessible at the previous index (the peer) 313 child = cb.parent.getChildAtIndex(i-1) 314 if child is None: 315 # indicate there is no previous peer 316 raise IndexError 317 return AEPor(child, None, 0)
318
319 - def getParentAcc(self):
320 ''' 321 Gets the parent accessible relative to the one providing this interface. 322 323 @return: Point of regard to the parent accessible 324 @rtype: L{AEPor} 325 @raise LookupError: When lookup for the parent accessible fails because it 326 does not exist 327 ''' 328 if self.accessible.getRole() == pyatspi.ROLE_COMBO_BOX: 329 cb = self.accessible 330 else: # probably textbox 331 cb = self.accessible.parent 332 333 parent = cb.parent 334 if parent is None: 335 raise LookupError 336 return AEPor(parent, None, 0)
337
338 - def getFirstAccChild(self):
339 ''' 340 Gets the first accessible child in the combobox list. 341 342 @return: Point of regard to the first list item 343 @rtype: L{AEPor} 344 @raise LookupError: When lookup for child fails because it does not exist 345 ''' 346 if self.accessible.getRole() == pyatspi.ROLE_COMBO_BOX: 347 cb = self.accessible 348 else: # probably textbox 349 cb = self.accessible.parent 350 351 # find head of list based on known parent roles 352 listhead = self._findListHead(cb) 353 if listhead is None: 354 raise LookupError 355 356 return AEPor(listhead, None, 0)
357
358 - def getLastAccChild(self):
359 ''' 360 Gets the last accessible child in combobox list. 361 362 @return: Point of regard to the last child accessible 363 @rtype: L{AEPor} 364 @raise LookupError: When lookup for child fails because it does not exist 365 ''' 366 if self.accessible.getRole() == pyatspi.ROLE_COMBO_BOX: 367 cb = self.accessible 368 else: # probably textbox 369 cb = self.accessible.parent 370 371 listhead = self._findListHead(cb) 372 if listhead is None: 373 raise LookupError 374 375 child = listhead.parent.getChildAtIndex(listhead.parent.childCount-1) 376 if child is None: 377 raise LookupError 378 return AEPor(child, None, 0)
379
380 - def _findListHead(self, cb):
381 ''' 382 Performs a depth only search to find the first list item in a 383 combobox list. Could be of type menu item or list item. 384 385 @return: Point of regard to the first child accessible 386 @rtype: L{AEPor} 387 @raise LookupError: When lookup for child fails because it does not exist 388 ''' 389 listhead = None 390 c = cb.getChildAtIndex(0) 391 while c is not None: 392 c_role = c.getRoleName() 393 if c_role == 'menu' or c_role == 'list': 394 if c.childCount > 0: 395 listhead = c.getChildAtIndex(0) 396 else: 397 listhead = cb 398 break 399 c = c.getChildAtIndex(0) 400 return listhead
401