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
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
61 text, lstart, lend = \
62 it.getTextAtOffset(char, pyatspi.TEXT_BOUNDARY_LINE_START)
63
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
69 rio = char-lstart
70
71 index = text.rfind(EMBED_CHAR, 0, rio)
72 if index < 0:
73 if rio <= 0:
74
75 raise IndexError
76 else:
77
78 return lstart
79 elif (rio-index) > 1:
80
81
82
83 return lstart+index+1
84 else:
85
86 return lstart+index
87
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
109 text, lstart, lend = \
110 it.getTextAtOffset(char, pyatspi.TEXT_BOUNDARY_LINE_START)
111 text = unicode(text, 'utf-8')
112
113 rio = char-lstart
114
115 index = text.find(EMBED_CHAR, rio)
116 if index < 0:
117 if lend >= it.characterCount-1:
118
119 raise IndexError
120 else:
121
122 return lend
123 return lstart+index
124
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
141 raise IndexError
142 elif index < 0:
143
144 return 0
145
146
147 hlink = acc.queryHypertext().getLink(index)
148
149 return hlink.startIndex
150
151
152
153
154
155
156
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
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
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
210 if char >= count:
211
212 raise IndexError
213 elif char != 0 and text.getCharacterAtOffset(char-1) != EMBED_VAL:
214
215 char = _getNextEmbedCharOffset(acc, char-1)
216
217 if text.getCharacterAtOffset(char) == EMBED_VAL:
218
219 index = htext.getLinkIndex(char)
220 if index < 0:
221
222 raise LookupError
223 return AEPor(acc.getChildAtIndex(index), None, 0)
224
225 else:
226
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
246 acc = self.accessible
247 if self.item_offset is None:
248 raise IndexError
249
250 else:
251 char = self.item_offset - 1
252 text = acc.queryText()
253 htext = acc.queryHypertext()
254
255 if char < 0:
256
257 return AEPor(acc, None, 0)
258 elif text.getCharacterAtOffset(char+1) != EMBED_VAL:
259
260 char = _getPrevItem(acc, char+1)
261
262
263 if text.getCharacterAtOffset(char) == EMBED_VAL:
264
265 index = htext.getLinkIndex(char)
266 if index < 0:
267
268 raise LookupError
269 por = AEPor(acc.getChildAtIndex(index), None, 0)
270 li = IItemNav(por).getLastItem(only_visible)
271 return li
272 else:
273
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
297 por = AEPor(acc.getChildAtIndex(acc.childCount-1), None, 0)
298 return IItemNav(por).getLastItem(only_visible)
299 else:
300
301 char = _getPrevItem(acc, text.characterCount-1)
302 if text.getCharacterAtOffset(char) == EMBED_VAL:
303
304 return AEPor(acc, char+1, 0)
305 else:
306
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
325
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
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
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
423 text, lstart, lend = \
424 it.getTextAtOffset(self.item_offset,pyatspi.TEXT_BOUNDARY_LINE_START)
425
426 text = unicode(text, 'utf-8')
427
428 ro = self.item_offset-lstart
429
430 i = text.find(EMBED_CHAR, ro)
431 if i < 0:
432 return text[ro:]
433 else:
434
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
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
480 item_por = IItemNav(focus_por).getFirstItem(False)
481
482
483 item = IAccessibleInfo(item_por).getAccItemText()
484
485 kwargs['focused'] = True
486 return (FocusChange(focus_por, True, **kwargs),
487 SelectorChange(item_por, item, **kwargs))
488