|
Module BasicBrailleScript
|
|
1
2 '''
3 Provides basic Braille services.
4
5 @author: Scott Haeger
6 @author: Peter Parente
7 @organization: IBM Corporation
8 @copyright: Copyright (c) 2005, 2007 IBM Corporation
9
10 @author: Martina Weicht
11 @organization: IT Science Center Ruegen gGmbH, Germany
12 @copyright: Copyright (c) 2007, 2008 ITSC Ruegen
13
14 @license: I{The BSD License}
15 All rights reserved. This program and the accompanying materials are made
16 available under the terms of the BSD license which accompanies
17 this distribution, and is available at
18 U{http://www.opensource.org/licenses/bsd-license.php}
19 '''
20
21
22 import pygtk
23 pygtk.require('2.0')
24 import gtk, gobject, logging
25 import pango
26
27
28
29 from AccessEngine import AEScript
30 from AccessEngine import AccessEngineAPI
31 from AccessEngine.AccessEngineAPI.AEApiError import *
32
33 from Tools.i18n import bind, _
34 from AccessEngine.AEConstants import *
35
36
37 __uie__ = dict(kind='script', tier=None, all_tiers=True)
38 log = logging.getLogger('BasicBrailleScript')
39
41 '''
42 Settings for BasicBrailleScript
43
44 B{Overlap (integer):} Number of orig. chars remaining on page forward/backward
45
46 B{LeftPadding (integer):} remaining cells on left side during scrolling
47
48 B{RightPadding (integer):} remaining cells on right side during scrolling
49
50 B{SnapOnUpdate (bool):} snap braille display to previous location upon update?
51
52 B{SnapToWord (bool):} snap braille display to nearest word upon paging?
53
54 B{MoveCaret (bool):} move caret on Home, EOL, and SOL?
55
56 B{BrailleReview (bool):} track review movements on braille display?
57
58 @ivar BrailleAvailable: Is Braille device available?
59 @type BrailleAvailable: boolean
60 '''
62 '''
63 Initializes setting objects.
64 '''
65 self.newBool('SnapOnUpdate', False, _('Snap on update?'),
66 _('When set, display snaps to caret location on any event '
67 'after scrolling.'))
68 self.newNumeric('Overlap', 0, _('Overlapping cells'), 0, 10, 0)
69 self.newBool('SnapToWord', False, _('Snap to word?'),
70 _('When set, display snaps to nearest word during display '
71 'paging.'))
72 self.newNumeric('LeftPadding', 0, _('Left padding'), 0, 10, 0,
73 _('Number of cells to pad for caret movement on the left '
74 'side of the display.'))
75 self.newNumeric('RightPadding', 0, _('Right padding'), 0, 10, 0,
76 _('Number of cells to pad for caret movement on the right '
77 'side of the display.'))
78 self.newBool('MoveCaret', False, _('Move caret on Home, EOL, SOL?'),
79 _('When set, Home, End of Line, and Start of Line commands '
80 'move caret to respective position. Otherwise, display '
81 'scrolls but caret remains in the same location.'))
82 self.newBool('BrailleReview', False, _('Track review keys?'),
83 _('When set, reviewing is tracked by the Braille display. '
84 'Otherwise, display is not updated when reviewing.'))
85 self.BrailleAvailable = True
86
87
89 '''
90 Creates configuration groups from pre-defined settings.
91
92 @return: root setting group
93 @rtype: L{AEState.Setting.Group}
94 '''
95 root = self.newGroup()
96
97 g = root.newGroup(_('General Navigation'))
98 g.append('SnapOnUpdate')
99 g.append('MoveCaret')
100
101 g = root.newGroup(_('Page Forwarding'))
102 g.append('SnapToWord')
103 g.append('Overlap')
104
105 g = root.newGroup(_('Padding'))
106 g.append('LeftPadding')
107 g.append('RightPadding')
108
109 g = root.newGroup(_('Reviewing'))
110 g.append('BrailleReview')
111
112 return root
113
114
116 '''
117 Defines a default braille user interface.
118
119 @ivar left_slice: Left slice index for current_text including scroll offset
120 @type left_slice: integer
121 @ivar pre_left_slice: Left slice index into current_text before scroll_offset
122 @type pre_left_slice: integer
123 @ivar scroll_offset: Scroll offset relative to pre_left_slice
124 @type scroll_offset: integer
125 @ivar abscaretpos: Index of the caret position within current_text
126 @type abscaretpos: integer
127 @ivar current_text: String containing current item accessible on the Braille
128 display. Managed to support scrolling.
129 @type current_text: string
130 '''
131
132 STATE = BasicBrailleScriptState
133
135 '''
136 Registers tasks to handle focus, caret, selector, state, and property change
137 events.
138 '''
139 self.left_slice = 0
140 self.pre_left_slice = 0
141 self.scroll_offset = 0
142 self.abscaretpos = -1
143 self.current_text = ""
144
145
146 AccessEngineAPI.setScriptIdealOutput(self, 'braille')
147
148
149 self.registerEventTask('braille focus', EVENT_TYPE_FOCUS_CHANGE)
150 self.registerEventTask('braille caret', EVENT_TYPE_CARET_CHANGE)
151 self.registerEventTask('braille selector', EVENT_TYPE_SELECTOR_CHANGE)
152
153
154 self.registerTask('braille current text', self.outputText)
155
156
157 self.registerTask('braille scroll forward', self.handleScrollForward)
158 self.registerTask('braille scroll backward', self.handleScrollBackward)
159 self.registerTask('braille page forward', self.handlePageForward)
160 self.registerTask('braille page backward', self.handlePageBackward)
161 self.registerTask('braille touch cursor', self.handleTouchCursor)
162 self.registerTask('braille line begin', self.handleLineBegin)
163 self.registerTask('braille line end', self.handleLineEnd)
164 self.registerTask('braille home', self.handleHome)
165 self.registerTask('braille review', self.handleReview)
166 self.registerTask('braille pointer to por', self.handlePointerToFocus)
167 self.registerTask('braille text', self.brailleText)
168
169
170 try:
171 brlindev = AccessEngineAPI.getInputDevice(None, 'braille')
172 except:
173 if self.state.BrailleAvailable:
174
175 self.doTask('read message', 'BasicSpeechScript',
176 text=_('braille device unavailable'), sem=SEM_ERROR)
177 self.state.BrailleAvailable = False
178 return
179
180
181 self.registerCommand(brlindev, 'braille scroll backward',
182 _('braille scroll backward'),
183 True, [brlindev.KEY_CMD_FWINLT])
184 self.registerCommand(brlindev, 'braille scroll forward',
185 _('braille scroll forward'),
186 True, [brlindev.KEY_CMD_FWINRT])
187 self.registerCommand(brlindev, 'braille page backward',
188 _('braille page backward'),
189 True, [brlindev.KEY_CMD_FWINLTSKIP])
190 self.registerCommand(brlindev, 'braille page forward',
191 _('braille page forward'),
192 True, [brlindev.KEY_CMD_FWINRTSKIP])
193 self.registerCommand(brlindev, 'braille touch cursor',
194 _('braille touch cursor'),
195 True, [brlindev.KEY_CMD_ROUTE])
196 self.registerCommand(brlindev, 'braille home',
197 _('braille home'),
198 True, [brlindev.KEY_CMD_HOME])
199 self.registerCommand(brlindev, 'braille line begin',
200 _('braille line begin'),
201 True, [brlindev.KEY_CMD_LNBEG])
202 self.registerCommand(brlindev, 'braille line end',
203 _('braille line end'),
204 True, [brlindev.KEY_CMD_LNEND])
205
206
207 self.chainTask('braille review', CHAIN_AFTER,
208 'review previous item', 'ReviewScript')
209 self.chainTask('braille review', CHAIN_AFTER,
210 'review current item', 'ReviewScript')
211 self.chainTask('braille review', CHAIN_AFTER,
212 'review next item', 'ReviewScript')
213 self.chainTask('braille review', CHAIN_AFTER,
214 'review previous word', 'ReviewScript')
215 self.chainTask('braille review', CHAIN_AFTER,
216 'review current word', 'ReviewScript')
217 self.chainTask('braille review', CHAIN_AFTER,
218 'review next word', 'ReviewScript')
219 self.chainTask('braille review', CHAIN_AFTER,
220 'review previous char', 'ReviewScript')
221 self.chainTask('braille review', CHAIN_AFTER,
222 'review current char', 'ReviewScript')
223 self.chainTask('braille review', CHAIN_AFTER,
224 'review next char', 'ReviewScript')
225 self.chainTask('braille pointer to por', CHAIN_AFTER,
226 'pointer to por', 'ReviewScript')
227
244
246 '''
247 Provides a localized description of this L{AccessEngine.AEScript} to be
248 shown in L{SettingsChooser} and L{ScriptChooser}.
249 '''
250 return _('Manages basic screen reader Braille input and output. Requires '
251 'a loaded Braille device.')
252
254 '''
255 Provides the localized name of this L{AccessEngine.AEScript}.
256 '''
257 return _('Basic Braille')
258
260 '''
261 Set absolute caret position
262 '''
263 if pos >= -1 and pos < len(self.current_text):
264 self.abscaretpos = pos
265 else:
266 self.abscaretpos = -1
267
269 '''
270 Get relative (to device display) caret position
271
272 @return: caret position
273 @rtype: Integer
274 '''
275
276 if self.abscaretpos == -1:
277 return -1
278 else:
279 return max(-1, self.abscaretpos - self.getLeftSlice(totalcells, por, layer))
280
282 '''
283 Get the leftmost slice indice for the current braille output
284
285 @return: Left slice index
286 @rtype: int
287 '''
288
289 misscellcnt = AccessEngineAPI.send(self, layer, CMD_GET_MISSINGCELL_COUNT,
290 None, cap='braille', role=None)
291
292 if por.char_offset - self.pre_left_slice < self.state.LeftPadding:
293 retval = max(0, por.char_offset - self.state.LeftPadding)
294 elif por.char_offset - self.pre_left_slice >= \
295 totalcells - self.state.RightPadding - misscellcnt:
296 retval = por.char_offset - totalcells + self.state.RightPadding + 1
297 retval += misscellcnt
298 else:
299 retval = self.pre_left_slice
300
301
302 self.pre_left_slice = retval
303
304
305 if self.scroll_offset > 0:
306 retval += self.scroll_offset
307 elif self.scroll_offset < 0:
308 retval = max(0, retval + self.scroll_offset)
309 return retval
310
311
312
313
333
335 '''
336 Moves caret to first cell of first line, redraws braille display
337 '''
338 por = kwargs['por']
339 caret_pos = por.char_offset + por.item_offset
340 por.char_offset = 0
341 por.item_offset = 0
342
343
344 if self.state.MoveCaret:
345
346 AccessEngineAPI.leftClickPOR(por)
347 else:
348
349 self.current_text = AccessEngineAPI.getItemText(por)
350 self.setAbsCaretPos(caret_pos)
351
352 self.scroll_offset = 0
353 self.doTask("braille current text", **kwargs)
354
356 '''
357 Moves caret to first cell in current line of text, redraws braille display
358 '''
359 por = kwargs['por']
360 por.char_offset = 0
361
362
363 if self.state.MoveCaret:
364
365 AccessEngineAPI.leftClickPOR(por)
366 else:
367
368 self.current_text = AccessEngineAPI.getItemText(por)
369
370 self.scroll_offset = 0
371 kwargs['por'] = por
372 self.doTask('braille current text', **kwargs)
373
375 '''
376 Sets caret at given location in text accessibles or left clicks the center
377 of the given accessible.
378
379 @note: MW: LSR developers used to set the caret by generating a mouse event
380 on that new caret position. This did not work in a text area where we do not
381 want to perfom a left click on the center of a component but set the caret
382 to a certain position within a line of text. Instead setAccCaret() is called
383 to set the caret to its new position. This module needs testing for the
384 component case - where do we decide which action to take? (See Bug#13)
385
386 @param argument:
387 @type argument: int
388 '''
389 if argument is None:
390 return
391 if argument < 0 or argument > \
392 AccessEngineAPI.getStyle(self, kwargs['layer']).TotalCells:
393 return
394
395 newoffset = self.pre_left_slice + self.scroll_offset + argument
396 self.pre_left_slice += self.scroll_offset
397 if not self.state.SnapOnUpdate:
398 self.scroll_offset = 0
399
400 if newoffset < 0:
401 newoffset = 0
402 elif newoffset > len(self.current_text) - 1:
403 newoffset = len(self.current_text) - 1
404
405
406 por = kwargs['por']
407 por.char_offset = newoffset
408
409 AccessEngineAPI.setAccCaret(por)
410
423
431
432 - def handlePageForward(self, **kwargs):
433 '''
434 Scrolls Braille viewport to right one display length minus overlap
435 '''
436 style = AccessEngineAPI.getStyle(self, kwargs['layer'])
437 if style is None:
438 return
439
440 ctlen = len(self.current_text)
441 l_el_len, r_el_len = AccessEngineAPI.send(self, kwargs['layer'],
442 CMD_GET_ELLIPSIS_SIZE, None,
443 cap='braille', role=None)
444 if 2 * style.TotalCells + self.pre_left_slice + self.scroll_offset - \
445 self.state.Overlap < ctlen + self.state.RightPadding:
446 self.scroll_offset += style.TotalCells - self.state.Overlap - \
447 l_el_len - r_el_len
448 else:
449 self.scroll_offset = ctlen + self.state.RightPadding - \
450 style.TotalCells - self.pre_left_slice
451
452 if self.state.SnapToWord:
453 found = False
454 wordadj = 0
455 for i in xrange(self.scroll_offset + \
456 self.pre_left_slice + l_el_len, -1, -1):
457 if self.current_text[i] == ' ' or i == 0:
458 if i != 0:
459 wordadj += 1
460 found = True
461 break
462 else:
463 wordadj -= 1
464 if found:
465 self.scroll_offset += wordadj
466 self.doTask('braille current text', **kwargs)
467
468 - def handlePageBackward(self, **kwargs):
469 '''
470 Scrolls Braille viewport to left one display length minus overlap
471 '''
472 style = AccessEngineAPI.getStyle(self, kwargs['layer'])
473 if style is None:
474 return
475
476 l_el_len, r_el_len = AccessEngineAPI.send(self, kwargs['layer'],
477 CMD_GET_ELLIPSIS_SIZE, None,
478 cap='braille', role=None)
479
480 if self.pre_left_slice + self.scroll_offset - \
481 (style.TotalCells - self.state.Overlap) > 0:
482 self.scroll_offset -= style.TotalCells - self.state.Overlap - \
483 l_el_len - r_el_len
484 else:
485 self.scroll_offset = (self.pre_left_slice * -1)
486
487 if self.state.SnapToWord:
488 found = False
489 wordadj = 0
490 for i in xrange(self.scroll_offset + self.pre_left_slice, -1, -1):
491 if self.current_text[i] == ' ' or i == 0:
492 if i != 0:
493 wordadj += 1
494 found = True
495 break
496 else:
497 wordadj -= 1
498 if found:
499 self.scroll_offset += wordadj
500 self.doTask('braille current text', **kwargs)
501
502
503
504
506 '''
507 Task that handles a L{AEEvent.FocusChange}.
508 '''
509 self.scroll_offset = 0
510 self.current_text = ""
511 self.doTask('braille current text', **kwargs)
512 return True
513
515 '''
516 Announces the text of the active item.
517
518 @note: One way to create this event was by placing the pointer por to
519 virtual por.
520
521 @param por: Point of regard where the selection event occurred
522 @type por: L{AEPor}
523 @param text: The text of the active item
524 @type text: string
525 '''
526
527 self.current_text = AccessEngineAPI.getItemText(por)
528 self.scroll_offset = 0
529
530 self.setAbsCaretPos(-1)
531
532 self.doTask('braille current text', por=por, **kwargs)
533
534
536 '''
537 Outputs the entire text to braille display in a managed fashion (overlaps,
538 caret position, padding etc.). Snaps to original position (before any
539 scrolling) if requested.
540
541 @param por: Point of regard where the caret event occurred
542 @type por: L{AEPor}
543 @param text: The text inserted
544 @type text: string
545 @param text_offset: The offset of the inserted text
546 @type text_offset: integer
547 '''
548
549 style = AccessEngineAPI.getStyle(self, kwargs['layer'])
550 if style is None:
551 return
552 self.current_text = AccessEngineAPI.getItemText(por)
553 if self.state.SnapOnUpdate:
554 self.scroll_offset = 0
555 self.setAbsCaretPos(por.char_offset)
556
557 self.doTask('braille current text', **kwargs)
558
560 '''
561 Outputs the entire text to braille display in a managed fashion (overlaps,
562 caret position, padding etc.). Snaps to original position (before any
563 scrolling) if requested.
564
565 @note: This method does exactly the same as onCaretInserted(), so we're
566 calling this one instead of programming things twice.
567 '''
568 self.onCaretInserted(**kwargs)
569
571 '''
572 Outputs the entire text to braille display in a managed fashion (overlaps,
573 caret position, padding etc.). Snaps to original position (before any
574 scrolling) if requested.
575
576 @note: This method does exactly the same as onCaretInserted(), so we're
577 calling this one instead of programming things twice.
578 '''
579 self.onCaretInserted(**kwargs)
580
581
583 '''
584 Updates state variables and relies on other methods to handle any output.
585
586 @param por: Point of regard where the caret event occurred
587 @type por: L{AEPor}
588 @param text: The text inserted, deleted or the line of the caret
589 @type text: string
590 @param text_offset: The offset of the inserted/deleted text or the line
591 offset when movement only
592 @type text_offset: integer
593 @param added: True when text added, False when text deleted, and None
594 when event is for caret movement only
595 @type added: boolean
596 '''
597
598 style = AccessEngineAPI.getStyle(self, kwargs['layer'])
599 if style is None:
600 return
601 self.current_text = AccessEngineAPI.getItemText(por)
602 if self.state.SnapOnUpdate:
603 self.scroll_offset = 0
604 self.setAbsCaretPos(por.char_offset)
605
606 - def outputText(self, **kwargs):
607 '''
608 Outputs the current text to all Braille devices found.
609 Relays caret position and truncation information to device.
610 '''
611 layer = kwargs['layer']
612 style = AccessEngineAPI.getStyle(self, layer)
613 if style is None:
614 return
615
616
617
618
619 caretpos = self.getRelCaretPos(style.TotalCells, kwargs['por'], layer)
620 if caretpos < 0 or caretpos >= style.TotalCells:
621 AccessEngineAPI.send(self, layer, CMD_CARET, -1, cap='braille', role=None)
622 else:
623 AccessEngineAPI.send(self, layer, CMD_CARET, caretpos, cap='braille',
624 role=None)
625
626 leftslice = self.getLeftSlice(style.TotalCells, kwargs['por'], layer)
627
628 rightslice = leftslice + style.TotalCells
629
630
631 misscellcnt = AccessEngineAPI.send(self, layer, CMD_GET_MISSINGCELL_COUNT,
632 None, cap='braille', role=None)
633 AccessEngineAPI.send(self, layer, CMD_TRUNCATE, (leftslice > 0,
634 rightslice < len(self.current_text) + misscellcnt))
635
636
637 AccessEngineAPI.sayItem(self, text=self.current_text[leftslice:rightslice],
638 cap='braille', role=None, **kwargs)
639
675
677 '''
678 Synchronizes the Braille display with the text at the focus when the pointer
679 is warped back to the caret.
680 '''
681
682 self.current_text = AccessEngineAPI.getItemText(kwargs['por'])
683 if self.state.SnapOnUpdate:
684 self.scroll_offset = 0
685
686 self.doTask('braille current text', **kwargs)
687
688 - def brailleText(self, text=None, **kwargs):
689 '''
690 Outputs item text to Braille display.
691 This method should be called from custom scripts when braille output is
692 desired.
693 '''
694
695 if text is None:
696 self.current_text = AccessEngineAPI.getItemText(kwargs['por'])
697 else:
698 self.current_text = text
699 if self.state.SnapOnUpdate:
700 self.scroll_offset = 0
701
702
703 self.doTask('braille current text', **kwargs)
704