1 '''
2 Defines L{AccAdapt.Adapter}s for combo boxes that receive the focus but have
3 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 All rights reserved. This program and the accompanying materials are made
12 available under the terms of the BSD license which accompanies
13 this distribution, and is available at
14 U{http://www.opensource.org/licenses/bsd-license.php}
15 '''
16
17 from POR import POR
18 from AEEvent import *
19 from AEInterfaces import *
20 from TextAdapter import TextEventHandlerAdapter
21 from pyLinAcc import Constants, Interfaces
22 import pyLinAcc
23 from DefaultNav import *
24
26 '''
27 Overrides L{TextEventHandlerAdapter} to enable processing of events from
28 text areas within combo boxes where the combo box gets focus, the text area
29 doesn't, and the text area is the source of all text events. Fires a
30 L{AEEvent.FocusChange} and either a L{AEEvent.CaretChange} or
31 L{AEEvent.SelectorChange} on focus. Expects the subject to be a
32 L{pyLinAcc.Accessible}.
33
34 Adapts subject accessibles that have a role of combo box or whose parent have
35 have a role of combo box.
36 '''
37 provides = [IEventHandler]
38
39 @staticmethod
41 '''
42 Tests if the given subject can be adapted by this class.
43
44 @param subject: Accessible to test
45 @type subject: L{pyLinAcc.Accessible}
46 @return: True when the subject meets the condition named in the docstring
47 for this class, False otherwise
48 @rtype: boolean
49 '''
50 r = subject.getRole()
51 pr = subject.parent.getRole()
52 c = Constants
53 roles = (c.ROLE_COMBO_BOX, c.ROLE_EDITBAR, c.ROLE_AUTOCOMPLETE)
54 return (r in roles or pr in roles)
55
56 - def _getTextArea(self):
57 '''
58 Looks for a widgets supporting the text interface within the combo box.
59
60 @return: The accessible that provides the text interface and the accessible
61 already queried to that interface
62 @rtype: 2-tuple of L{pyLinAcc.Accessible}
63 '''
64 text = None
65
66 try:
67 text = Interfaces.IText(self.subject)
68 return self.subject, text
69 except NotImplementedError:
70 pass
71
72 for i in xrange(self.subject.childCount):
73 try:
74 acc = self.subject.getChildAtIndex(i)
75 text = Interfaces.IText(acc)
76 return acc, text
77 except NotImplementedError:
78 pass
79 raise NotImplementedError
80
82 '''
83 Gets if the subject or its parent is focused.
84
85 @return: Does the subject or its parent have focus?
86 @rtype: boolean
87 '''
88 f = Constants.STATE_FOCUSED
89 if (self.subject.getState().contains(f) or
90 self.subject.parent.getState().contains(f)):
91 return True
92 return False
93
95 '''
96 Creates an L{AEEvent.FocusChange} indicating that the accessible being
97 adapted has gained the focus. Also a L{AEEvent.CaretChange}. This sequence
98 of L{AEEvent}s will be posted by the caller.
99
100 @param event: Raw focus change event
101 @type event: L{pyLinAcc.Event.Event}
102 @param kwargs: Parameters to be passed to any created L{AEEvent}
103 @type kwargs: dictionary
104 @return: L{AEEvent.FocusChange} and L{AEEvent.CaretChange}
105 @rtype: tuple of L{AEEvent}
106 '''
107
108 kwargs['focused'] = True
109 por = POR(self.subject, None, 0)
110 focus_evt = FocusChange(por, True, **kwargs)
111
112
113 try:
114 acc, text = self._getTextArea()
115 except NotImplementedError:
116
117 item = IAccessibleInfo(por).getAccItemText()
118 return (focus_evt, SelectorChange(por, item, **kwargs))
119
120 char_offset = text.caretOffset
121
122 line, sOff, eOff = \
123 text.getTextAtOffset(char_offset, Constants.TEXT_BOUNDARY_LINE_START)
124
125 por = POR(acc, sOff, char_offset - sOff)
126
127
128
129 return (focus_evt, CaretChange(por, unicode(line, 'utf-8'), sOff, **kwargs))
130
131 - def _handleTextEvent(self, event, focused, **kwargs):
132 '''
133 Called when text is inserted or deleted (object:text-changed:insert &
134 object:text-changed:delete). Creates and returns an L{AEEvent.CaretChange}
135 to indicate a change in the caret context.
136
137 L{pyLinAcc.Event.Event}.type.minor is "insert" or "delete".
138 L{pyLinAcc.Event.Event}.detail1 has start offset of text change.
139 L{pyLinAcc.Event.Event}.detail2 has length of text change.
140 L{pyLinAcc.Event.Event}.any_data has text inserted/deleted.
141
142 @param event: Raw text-changed event
143 @type event: L{pyLinAcc.Event.Event}
144 @param kwargs: Parameters to be passed to any created L{AEEvent}
145 @type kwargs: dictionary
146 @return: L{AEEvent.CaretChange}
147 @rtype: tuple of L{AEEvent}
148 '''
149
150 try:
151 acc, text = self._getTextArea()
152 except NotImplementedError:
153 return None
154
155
156 focused = focused or self._isFocused()
157
158
159 line, sOff, eOff = \
160 text.getTextAtOffset(text.caretOffset, Constants.TEXT_BOUNDARY_LINE_START)
161
162 por = POR(self.subject, sOff, text.caretOffset - sOff)
163
164
165
166 return (CaretChange(por, unicode(event.any_data, 'utf-8'),
167 event.detail1, (event.type.minor == 'insert'),
168 focused=focused, **kwargs),)
169
171 '''
172 Creates and returns an L{AEEvent.CaretChange} indicating the caret moved in
173 the subject accessible.
174
175 L{pyLinAcc.Event.Event}.detail1 has caret offset.
176
177 @param event: Raw caret movement event
178 @type event: L{pyLinAcc.Event.Event}
179 @param kwargs: Parameters to be passed to any created L{AEEvent}
180 @type kwargs: dictionary
181 @return: L{AEEvent.CaretChange}
182 @rtype: tuple of L{AEEvent}
183 '''
184
185 try:
186 acc, text = self._getTextArea()
187 except NotImplementedError:
188 return None
189
190
191 focused = focused or self._isFocused()
192
193
194 caret_offset = event.detail1
195
196
197 line, sOff, eOff = \
198 text.getTextAtOffset(caret_offset, Constants.TEXT_BOUNDARY_LINE_START)
199
200
201 por = POR(self.subject, sOff, caret_offset - sOff)
202
203
204 return (CaretChange(por, unicode(line, 'utf-8'), caret_offset,
205 focused=focused, **kwargs),)
206
207
209 '''
210 Overrides L{DefaultNavAdapter} to provide navigation over text lines as
211 items and to avoid traversing text children as separate accessible children
212 in the L{IAccessibleNav} interface.
213
214 Adapts accessibles that have a role of terminal or have a state of multiline,
215 single line, or editable and provide the Text interface.
216 Does not adapt L{POR} accessibles with role of page tab.
217 '''
218 provides = [IItemNav, IAccessibleNav]
219
220 @staticmethod
222 '''
223 Tests if the given subject can be adapted by this class.
224
225 @param subject: Accessible to test
226 @type subject: L{pyLinAcc.Accessible}
227 @return: True when the subject meets the condition named in the docstring
228 for this class, False otherwise
229 @rtype: boolean
230 '''
231 acc = subject.accessible
232 r = acc.getRole()
233 pr = acc.parent.getRole()
234 c = Constants
235 roles = (c.ROLE_COMBO_BOX, c.ROLE_EDITBAR, c.ROLE_AUTOCOMPLETE)
236 return (r in roles or pr in roles)
237
239 '''
240 Gets the next accessible relative to the one providing this interface.
241
242 @return: Point of regard to the next accessible
243 @rtype: L{POR}
244 @raise IndexError: When there is no next accessible
245 @raise LookupError: When lookup for the next accessible fails even though
246 it may exist
247 '''
248 if self.accessible.getRole() == Constants.ROLE_COMBO_BOX:
249 cb = self.accessible
250 else:
251 cb = self.accessible.parent
252
253 i = cb.getIndexInParent()
254 has_parent = cb.parent is not None
255 if i < 0 or not has_parent:
256
257 raise LookupError
258
259 child = cb.parent.getChildAtIndex(i+1)
260 if child is None:
261
262 raise IndexError
263 return POR(child, None, 0)
264
266 '''
267 Gets the previous accessible relative to the one providing this interface.
268
269 @return: Point of regard to the previous accessible
270 @rtype: L{POR}
271 @raise IndexError: When there is no previous accessible
272 @raise LookupError: When lookup for the previous accessible fails even
273 though it may exist
274 '''
275 if self.accessible.getRole() == Constants.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 POR(child, None, 0)
291
293 '''
294 Gets the parent accessible relative to the one providing this interface.
295
296 @return: Point of regard to the parent accessible
297 @rtype: L{POR}
298 @raise LookupError: When lookup for the parent accessible fails because it
299 does not exist
300 '''
301 if self.accessible.getRole() == Constants.ROLE_COMBO_BOX:
302 cb = self.accessible
303 else:
304 cb = self.accessible.parent
305
306 parent = cb.parent
307 if parent is None:
308 raise LookupError
309 return POR(parent, None, 0)
310
312 '''
313 Gets the first accessible child in the combobox list.
314
315 @return: Point of regard to the first list item
316 @rtype: L{POR}
317 @raise LookupError: When lookup for child fails because it does not exist
318 '''
319 if self.accessible.getRole() == Constants.ROLE_COMBO_BOX:
320 cb = self.accessible
321 else:
322 cb = self.accessible.parent
323
324
325 listhead = self._findListHead(cb)
326 if listhead is None:
327 raise LookupError
328
329 return POR(listhead, None, 0)
330
332 '''
333 Gets the last accessible child in combobox list.
334
335 @return: Point of regard to the last child accessible
336 @rtype: L{POR}
337 @raise LookupError: When lookup for child fails because it does not exist
338 '''
339 if self.accessible.getRole() == Constants.ROLE_COMBO_BOX:
340 cb = self.accessible
341 else:
342 cb = self.accessible.parent
343
344 listhead = self._findListHead(cb)
345 if listhead is None:
346 raise LookupError
347
348 child = listhead.parent.getChildAtIndex(listhead.parent.childCount-1)
349 if child is None:
350 raise LookupError
351 return POR(child, None, 0)
352
354 '''
355 Performs a depth only search to find the first list item in a
356 combobox list. Could be of type menu item or list item.
357
358 @return: Point of regard to the first child accessible
359 @rtype: L{POR}
360 @raise LookupError: When lookup for child fails because it does not exist
361 '''
362 listhead = None
363 c = cb.getChildAtIndex(0)
364 while c is not None:
365 c_role = c.getRoleName()
366 if c_role == 'menu' or c_role == 'list':
367 if c.childCount > 0:
368 listhead = c.getChildAtIndex(0)
369 else:
370 listhead = cb
371 break
372 c = c.getChildAtIndex(0)
373 return listhead
374