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