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
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
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
73 try:
74 text = self.subject.queryText()
75 return self.subject, text
76 except NotImplementedError:
77 pass
78
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
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
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
115 kwargs['focused'] = True
116 por = AEPor(self.subject, None, 0)
117 focus_evt = FocusChange(por, True, **kwargs)
118
119
120 try:
121 acc, text = self._getTextArea()
122 except NotImplementedError:
123
124 item = IAccessibleInfo(por).getAccItemText()
125 return (focus_evt, SelectorChange(por, item, **kwargs))
126
127 char_offset = text.caretOffset
128
129 line, sOff, eOff = \
130 text.getTextAtOffset(char_offset, pyatspi.TEXT_BOUNDARY_LINE_START)
131
132 por = AEPor(acc, sOff, char_offset - sOff)
133
134
135
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
157 try:
158 acc, text = self._getTextArea()
159 except NotImplementedError:
160 return None
161
162
163 focused = focused or self._isFocused()
164
165
166 line, sOff, eOff = \
167 text.getTextAtOffset(text.caretOffset, pyatspi.TEXT_BOUNDARY_LINE_START)
168
169 por = AEPor(self.subject, sOff, text.caretOffset - sOff)
170
171
172
173 return (CaretChange(por, unicode(event.any_data, 'utf-8'),
174 event.detail1, (event.type.minor == 'insert'),
175 focused=focused, **kwargs),)
176
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
192 try:
193 acc, text = self._getTextArea()
194 except NotImplementedError:
195 return None
196
197
198 focused = focused or self._isFocused()
199
200
201 caret_offset = event.detail1
202
203
204 line, sOff, eOff = \
205 text.getTextAtOffset(caret_offset, pyatspi.TEXT_BOUNDARY_LINE_START)
206
207
208 por = AEPor(self.subject, sOff, caret_offset - sOff)
209
210
211 return (CaretChange(por, unicode(line, 'utf-8'), caret_offset,
212 focused=focused, **kwargs),)
213
214
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
232 por = AEPor(self.subject, None, 0)
233 item = IAccessibleInfo(por).getAccItemText()
234 return (SelectorChange(por, item, **kwargs),)
235
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
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
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
284 raise LookupError
285
286 child = cb.parent.getChildAtIndex(i+1)
287 if child is None:
288
289 raise IndexError
290 return AEPor(child, None, 0)
291
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
311 raise LookupError
312
313 child = cb.parent.getChildAtIndex(i-1)
314 if child is None:
315
316 raise IndexError
317 return AEPor(child, None, 0)
318
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:
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
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:
349 cb = self.accessible.parent
350
351
352 listhead = self._findListHead(cb)
353 if listhead is None:
354 raise LookupError
355
356 return AEPor(listhead, None, 0)
357
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:
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
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