Coverage Report - orca.default

ModuleCoverage %
orca.default
59%
1
# Orca
2
#
3
# Copyright 2004-2007 Sun Microsystems Inc.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 of the License, or (at your option) any later version.
9
#
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# Library General Public License for more details.
14
#
15
# You should have received a copy of the GNU Library General Public
16
# License along with this library; if not, write to the
17
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
# Boston, MA 02111-1307, USA.
19
20
"""The default Script for presenting information to the user using
21
both speech and Braille.  This is based primarily on the de-facto
22
standard implementation of the AT-SPI, which is the GAIL support
23 1
for GTK."""
24
25 1
__id__        = "$Id: default.py 2126 2007-03-06 21:35:17Z richb $"
26 1
__version__   = "$Revision: 2126 $"
27 1
__date__      = "$Date: 2007-03-06 13:35:17 -0800 (Tue, 06 Mar 2007) $"
28 1
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
29 1
__license__   = "LGPL"
30
31 1
try:
32
    # This can fail due to gtk not being available.  We want to
33
    # be able to recover from that if possible.  The main driver
34
    # for this is to allow "orca --text-setup" to work even if
35
    # the desktop is not running.
36
    #
37 1
    import gtk
38 0
except:
39 0
    pass
40
41 1
import time
42
43 1
import atspi
44 1
import braille
45 1
import chnames
46 1
import debug
47 1
import find
48 1
import flat_review
49 1
import input_event
50 1
import keybindings
51 1
import mag
52 1
import orca
53 1
import orca_prefs
54 1
import orca_state
55 1
import phonnames
56 1
import pronunciation_dict
57 1
import punctuation_settings
58 1
import rolenames
59 1
import script
60 1
import settings
61 1
import speech
62 1
import speechserver
63 1
import string
64 1
import where_am_I
65
66 1
from orca_i18n import _                          # for gettext support
67
68
########################################################################
69
#                                                                      #
70
# The Default script class.                                            #
71
#                                                                      #
72
########################################################################
73
74 2
class Script(script.Script):
75
76 1
    def __init__(self, app):
77
        """Creates a new script for the given application.
78
79
        Arguments:
80
        - app: the application to create a script for.
81
        """
82
83 61
        script.Script.__init__(self, app)
84
85 61
        self.flatReviewContext  = None
86 61
        self.windowActivateTime = None
87 61
        self.lastReviewCurrentEvent = None
88
89
        # Used to determine whether the used double clicked on the
90
        # "where am I" key.
91
        #
92 61
        self.lastWhereAmIEvent = None
93
94
        # Embedded object character used to indicate that an object is
95
        # embedded in a string.
96
        #
97 61
        self.EMBEDDED_OBJECT_CHARACTER = u'\ufffc'
98
99
        # Unicode currency symbols (populated by the 
100
        # getUnicodeCurrencySymbols() routine).
101
        #
102 61
        self._unicodeCurrencySymbols = []
103
104
        # Used by the drawOutline routine.
105
        #
106 61
        self._display = None
107 61
        self._visibleRectangle = None
108
109 1
    def setupInputEventHandlers(self):
110
        """Defines InputEventHandler fields for this script that can be
111
        called by the key and braille bindings."""
112
113 62
        self.inputEventHandlers["leftClickReviewItemHandler"] = \
114
            input_event.InputEventHandler(
115
                Script.leftClickReviewItem,
116 62
                _("Performs left click on current flat review item."))
117
118 62
        self.inputEventHandlers["rightClickReviewItemHandler"] = \
119
             input_event.InputEventHandler(
120
                Script.rightClickReviewItem,
121 62
                _("Performs right click on current flat review item."))
122
123 62
        self.inputEventHandlers["sayAllHandler"] = \
124
            input_event.InputEventHandler(
125
                Script.sayAll,
126 62
                _("Speaks entire document."))
127
128 62
        self.inputEventHandlers["whereAmIHandler"] = \
129
            input_event.InputEventHandler(
130
                Script.whereAmI,
131 62
                _("Performs the where am I operation."))
132
133 62
        self.inputEventHandlers["findHandler"] = \
134
            input_event.InputEventHandler(
135
                orca._showFindGUI,
136 62
                _("Opens the Orca Find dialog."))
137
138 62
        self.inputEventHandlers["findNextHandler"] = \
139
            input_event.InputEventHandler(
140
                Script.findNext,
141 62
                _("Searches for the next instance of a string."))
142
143 62
        self.inputEventHandlers["findPreviousHandler"] = \
144
            input_event.InputEventHandler(
145
                Script.findPrevious,
146 62
                _("Searches for the previous instance of a string."))
147
148 62
        self.inputEventHandlers["showZonesHandler"] = \
149
            input_event.InputEventHandler(
150
                Script.showZones,
151 62
                _("Paints and prints the visible zones in the active window."))
152
153 62
        self.inputEventHandlers["toggleFlatReviewModeHandler"] = \
154
            input_event.InputEventHandler(
155
                Script.toggleFlatReviewMode,
156 62
                _("Enters and exits flat review mode."))
157
158 62
        self.inputEventHandlers["reviewPreviousLineHandler"] = \
159
            input_event.InputEventHandler(
160
                Script.reviewPreviousLine,
161 62
                _("Moves flat review to the beginning of the previous line."))
162
163 62
        self.inputEventHandlers["reviewHomeHandler"] = \
164
            input_event.InputEventHandler(
165
                Script.reviewHome,
166 62
                _("Moves flat review to the home position."))
167
168 62
        self.inputEventHandlers["reviewCurrentLineHandler"] = \
169
            input_event.InputEventHandler(
170
                Script.reviewCurrentLine,
171 62
                _("Speaks the current flat review line."))
172
173 62
        self.inputEventHandlers["reviewNextLineHandler"] = \
174
            input_event.InputEventHandler(
175
                Script.reviewNextLine,
176 62
                _("Moves flat review to the beginning of the next line."))
177
178 62
        self.inputEventHandlers["reviewEndHandler"] = \
179
            input_event.InputEventHandler(
180
                Script.reviewEnd,
181 62
                _("Moves flat review to the end position."))
182
183 62
        self.inputEventHandlers["reviewPreviousItemHandler"] = \
184
            input_event.InputEventHandler(
185
                Script.reviewPreviousItem,
186 62
                _("Moves flat review to the previous item or word."))
187
188 62
        self.inputEventHandlers["reviewAboveHandler"] = \
189
            input_event.InputEventHandler(
190
                Script.reviewAbove,
191 62
                _("Moves flat review to the word above the current word."))
192
193 62
        self.inputEventHandlers["reviewCurrentItemHandler"] = \
194
            input_event.InputEventHandler(
195
                Script.reviewCurrentItem,
196 62
                _("Speaks or spells the current flat review item or word."))
197
198 62
        self.inputEventHandlers["reviewCurrentAccessibleHandler"] = \
199
            input_event.InputEventHandler(
200
                Script.reviewCurrentAccessible,
201 62
                _("Speaks the current flat review object."))
202
203 62
        self.inputEventHandlers["reviewNextItemHandler"] = \
204
            input_event.InputEventHandler(
205
                Script.reviewNextItem,
206 62
                _("Moves flat review to the next item or word."))
207
208 62
        self.inputEventHandlers["reviewBelowHandler"] = \
209
            input_event.InputEventHandler(
210
                Script.reviewBelow,
211 62
                _("Moves flat review to the word below the current word."))
212
213 62
        self.inputEventHandlers["reviewPreviousCharacterHandler"] = \
214
            input_event.InputEventHandler(
215
                Script.reviewPreviousCharacter,
216 62
                _("Moves flat review to the previous character."))
217
218 62
        self.inputEventHandlers["reviewEndOfLineHandler"] = \
219
            input_event.InputEventHandler(
220
                Script.reviewEndOfLine,
221 62
                _("Moves flat review to the end of the line."))
222
223 62
        self.inputEventHandlers["reviewCurrentCharacterHandler"] = \
224
            input_event.InputEventHandler(
225
                Script.reviewCurrentCharacter,
226 62
                _("Speaks the current flat review character."))
227
228 62
        self.inputEventHandlers["reviewNextCharacterHandler"] = \
229
            input_event.InputEventHandler(
230
                Script.reviewNextCharacter,
231 62
                _("Moves flat review to the next character."))
232
233 62
        self.inputEventHandlers["toggleTableCellReadModeHandler"] = \
234
            input_event.InputEventHandler(
235
                Script.toggleTableCellReadMode,
236 62
                _("Toggles whether to read just the current table cell or the whole row."))
237
238 62
        self.inputEventHandlers["readCharAttributesHandler"] = \
239
            input_event.InputEventHandler(
240
                Script.readCharAttributes,
241 62
                _("Reads the attributes associated with the current text character."))
242
243 62
        self.inputEventHandlers["reportScriptInfoHandler"] = \
244
            input_event.InputEventHandler(
245
                Script.reportScriptInfo,
246 62
                _("Reports information on current script."))
247
248 62
        self.inputEventHandlers["panBrailleLeftHandler"] = \
249
            input_event.InputEventHandler(
250
                Script.panBrailleLeft,
251 62
                _("Pans the braille display to the left."),
252 62
                False) # Do not enable learn mode for this action
253
254 62
        self.inputEventHandlers["panBrailleRightHandler"] = \
255
            input_event.InputEventHandler(
256
                Script.panBrailleRight,
257 62
                _("Pans the braille display to the right."),
258 62
                False) # Do not enable learn mode for this action
259
260 62
        self.inputEventHandlers["reviewBottomLeftHandler"] = \
261
            input_event.InputEventHandler(
262
                Script.reviewBottomLeft,
263 62
                _("Moves flat review to the bottom left."))
264
265 62
        self.inputEventHandlers["goBrailleHomeHandler"] = \
266
            input_event.InputEventHandler(
267
                Script.goBrailleHome,
268 62
                _("Returns to object with keyboard focus."))
269
270 62
        self.inputEventHandlers["enterLearnModeHandler"] = \
271
            input_event.InputEventHandler(
272
                Script.enterLearnMode,
273 62
                _("Enters learn mode.  Press escape to exit learn mode."))
274
275 62
        self.inputEventHandlers["exitLearnModeHandler"] = \
276
            input_event.InputEventHandler(
277
                Script.exitLearnMode,
278 62
                _("Exits learn mode."))
279
280 62
        self.inputEventHandlers["decreaseSpeechRateHandler"] = \
281
            input_event.InputEventHandler(
282
                speech.decreaseSpeechRate,
283 62
                _("Decreases the speech rate."))
284
285 62
        self.inputEventHandlers["increaseSpeechRateHandler"] = \
286
            input_event.InputEventHandler(
287
                speech.increaseSpeechRate,
288 62
                _("Increases the speech rate."))
289
290 62
        self.inputEventHandlers["decreaseSpeechPitchHandler"] = \
291
            input_event.InputEventHandler(
292
                speech.decreaseSpeechPitch,
293 62
                _("Decreases the speech pitch."))
294
295 62
        self.inputEventHandlers["increaseSpeechPitchHandler"] = \
296
            input_event.InputEventHandler(
297
                speech.increaseSpeechPitch,
298 62
                _("Increases the speech pitch."))
299
300 62
        self.inputEventHandlers["shutdownHandler"] = \
301
            input_event.InputEventHandler(
302
                orca._showQuitGUI,
303 62
                _("Quits Orca"))
304
305 62
        self.inputEventHandlers["keystrokeRecordingHandler"] = \
306
            input_event.InputEventHandler(
307
                orca.toggleKeystrokeRecording,
308 62
                _("Toggles keystroke recording on and off."))
309
310 62
        self.inputEventHandlers["preferencesSettingsHandler"] = \
311
            input_event.InputEventHandler(
312
                orca._showPreferencesGUI,
313 62
                _("Displays the preferences configuration dialog."))
314
315 62
        self.inputEventHandlers["loadUserSettingsHandler"] = \
316
            input_event.InputEventHandler(
317
                orca.loadUserSettings,
318 62
                _("Reloads user settings and reinitializes services as necessary."))
319
320 62
        self.inputEventHandlers["toggleSilenceSpeechHandler"] = \
321
            input_event.InputEventHandler(
322
                orca._toggleSilenceSpeech,
323 62
                _("Toggles the silencing of speech."))
324
325 62
        self.inputEventHandlers["listAppsHandler"] = \
326
            input_event.InputEventHandler(
327
                Script.printAppsHandler,
328 62
                _("Prints a debug listing of all known applications to the console where Orca is running."))
329
330 62
        self.inputEventHandlers["cycleDebugLevelHandler"] = \
331
            input_event.InputEventHandler(
332
                orca.cycleDebugLevel,
333 62
                _("Cycles the debug level at run time."))
334
335 62
        self.inputEventHandlers["printActiveAppHandler"] = \
336
            input_event.InputEventHandler(
337
                Script.printActiveAppHandler,
338 62
                _("Prints debug information about the currently active application to the console where Orca is running."))
339
340 62
        self.inputEventHandlers["printAncestryHandler"] = \
341
            input_event.InputEventHandler(
342
                Script.printAncestryHandler,
343 62
                _("Prints debug information about the ancestry of the object with focus"))
344
345 62
        self.inputEventHandlers["printHierarchyHandler"] = \
346
            input_event.InputEventHandler(
347
                Script.printHierarchyHandler,
348 62
                _("Prints debug information about the application with focus"))
349
350 62
        self.inputEventHandlers["nextPresentationManagerHandler"] = \
351
            input_event.InputEventHandler(
352
                orca._switchToNextPresentationManager,
353 62
                _("Switches to the next presentation manager."))
354
355 1
    def getInputEventHandlerKey(self, inputEventHandler):
356
        """Returns the name of the key that contains an inputEventHadler
357
        passed as argument
358
        """
359
360 50
        key = None
361
362 2450
        for keyName,handler in self.inputEventHandlers.iteritems():
363 2400
            if handler._function == inputEventHandler._function:
364 50
                key = keyName
365
366 50
        return key
367
368 1
    def getListeners(self):
369
        """Sets up the AT-SPI event listeners for this script.
370
        """
371 61
        listeners = script.Script.getListeners(self)
372 61
        listeners["focus:"]                                 = \
373
            self.onFocus
374
        #listeners["keyboard:modifiers"]                     = \
375
        #    self.noOp
376 61
        listeners["object:property-change:accessible-name"] = \
377
            self.onNameChanged
378 61
        listeners["object:text-caret-moved"]                = \
379
            self.onCaretMoved
380 61
        listeners["object:text-changed:delete"]             = \
381
            self.onTextDeleted
382 61
        listeners["object:text-changed:insert"]             = \
383
            self.onTextInserted
384 61
        listeners["object:text-selection-changed"]          = \
385
            self.noOp
386 61
        listeners["object:active-descendant-changed"]       = \
387
            self.onActiveDescendantChanged
388 61
        listeners["object:children-changed:"]               = \
389
            self.noOp
390 61
        listeners["object:link-selected"]                   = \
391
            self.onLinkSelected
392 61
        listeners["object:bounds-changed"]                  = \
393
            self.noOp
394 61
        listeners["object:state-changed:"]                  = \
395
            self.onStateChanged
396 61
        listeners["object:selection-changed"]               = \
397
            self.onSelectionChanged
398 61
        listeners["object:property-change:accessible-value"] = \
399
            self.onValueChanged
400 61
        listeners["object:property-change"]                 = \
401
            self.noOp
402 61
        listeners["object:value-changed:"]                  = \
403
            self.onValueChanged
404 61
        listeners["object:visible-changed"]                 = \
405
            self.noOp
406 61
        listeners["window:activate"]                        = \
407
            self.onWindowActivated
408 61
        listeners["window:create"]                          = \
409
            self.noOp
410 61
        listeners["window:deactivate"]                      = \
411
            self.onWindowDeactivated
412 61
        listeners["window:destroy"]                         = \
413
            self.noOp
414 61
        listeners["window:maximize"]                        = \
415
            self.noOp
416 61
        listeners["window:minimize"]                        = \
417
            self.noOp
418 61
        listeners["window:rename"]                          = \
419
            self.noOp
420 61
        listeners["window:restore"]                         = \
421
            self.noOp
422 61
        listeners["window:switch"]                          = \
423
            self.noOp
424 61
        listeners["window:titlelize"]                       = \
425
            self.noOp
426
427 61
        return listeners
428
429 1
    def __getDesktopBindings(self):
430
        """Returns an instance of keybindings.KeyBindings that use the
431
        numeric keypad for focus tracking and flat review.
432
        """
433
434 62
        keyBindings = keybindings.KeyBindings()
435
436 62
        keyBindings.add(
437
            keybindings.KeyBinding(
438
                "KP_Divide",
439 62
                0,
440 62
                0,
441 62
                self.inputEventHandlers["leftClickReviewItemHandler"]))
442
443 62
        keyBindings.add(
444
            keybindings.KeyBinding(
445
                "KP_Multiply",
446 62
                0,
447 62
                0,
448 62
                self.inputEventHandlers["rightClickReviewItemHandler"]))
449
450 62
        keyBindings.add(
451
            keybindings.KeyBinding(
452
                "KP_Subtract",
453 62
                0,
454 62
                0,
455 62
                self.inputEventHandlers["toggleFlatReviewModeHandler"]))
456
457 62
        keyBindings.add(
458
            keybindings.KeyBinding(
459
                "KP_Add",
460 62
                0,
461 62
                0,
462 62
                self.inputEventHandlers["sayAllHandler"]))
463
464 62
        keyBindings.add(
465
            keybindings.KeyBinding(
466
                "KP_Enter",
467 62
                0,
468 62
                0,
469 62
                self.inputEventHandlers["whereAmIHandler"]))
470
471 62
        keyBindings.add(
472
            keybindings.KeyBinding(
473
                "KP_Delete",
474 62
                1 << settings.MODIFIER_ORCA,
475 62
                0,
476 62
                self.inputEventHandlers["findHandler"]))
477
478 62
        keyBindings.add(
479
            keybindings.KeyBinding(
480
                "KP_Delete",
481 62
                (1 << settings.MODIFIER_ORCA
482
                 | 1 << atspi.Accessibility.MODIFIER_SHIFT),
483 62
                1 << settings.MODIFIER_ORCA,
484 62
                self.inputEventHandlers["findNextHandler"]))
485
486 62
        keyBindings.add(
487
            keybindings.KeyBinding(
488
                "KP_Delete",
489 62
                (1 << settings.MODIFIER_ORCA
490
                 | 1 << atspi.Accessibility.MODIFIER_SHIFT),
491 62
                (1 << settings.MODIFIER_ORCA | \
492
                     1 << atspi.Accessibility.MODIFIER_SHIFT),
493 62
                self.inputEventHandlers["findPreviousHandler"]))
494
495 62
        keyBindings.add(
496
            keybindings.KeyBinding(
497
                "KP_7",
498 62
                1 << settings.MODIFIER_ORCA,
499 62
                0,
500 62
                self.inputEventHandlers["reviewPreviousLineHandler"]))
501
502 62
        keyBindings.add(
503
            keybindings.KeyBinding(
504
                "KP_Home",
505 62
                1 << settings.MODIFIER_ORCA,
506 62
                0,
507 62
                self.inputEventHandlers["reviewPreviousLineHandler"]))
508
509 62
        keyBindings.add(
510
            keybindings.KeyBinding(
511
                "KP_7",
512 62
                1 << settings.MODIFIER_ORCA,
513 62
                1 << settings.MODIFIER_ORCA,
514 62
                self.inputEventHandlers["reviewHomeHandler"]))
515
516 62
        keyBindings.add(
517
            keybindings.KeyBinding(
518
                "KP_Home",
519 62
                1 << settings.MODIFIER_ORCA,
520 62
                1 << settings.MODIFIER_ORCA,
521 62
                self.inputEventHandlers["reviewHomeHandler"]))
522
523 62
        keyBindings.add(
524
            keybindings.KeyBinding(
525
                "KP_8",
526 62
                0,
527 62
                0,
528 62
                self.inputEventHandlers["reviewCurrentLineHandler"]))
529
530 62
        keyBindings.add(
531
            keybindings.KeyBinding(
532
                "KP_Up",
533 62
                0,
534 62
                0,
535 62
                self.inputEventHandlers["reviewCurrentLineHandler"]))
536
537 62
        keyBindings.add(
538
            keybindings.KeyBinding(
539
                "KP_9",
540 62
                1 << settings.MODIFIER_ORCA,
541 62
                0,
542 62
                self.inputEventHandlers["reviewNextLineHandler"]))
543
544 62
        keyBindings.add(
545
            keybindings.KeyBinding(
546
                "KP_Page_Up",
547 62
                1 << settings.MODIFIER_ORCA,
548 62
                0,
549 62
                self.inputEventHandlers["reviewNextLineHandler"]))
550
551 62
        keyBindings.add(
552
            keybindings.KeyBinding(
553
                "KP_9",
554 62
                1 << settings.MODIFIER_ORCA,
555 62
                1 << settings.MODIFIER_ORCA,
556 62
                self.inputEventHandlers["reviewEndHandler"]))
557
558 62
        keyBindings.add(
559
            keybindings.KeyBinding(
560
                "KP_Page_Up",
561 62
                1 << settings.MODIFIER_ORCA,
562 62
                1 << settings.MODIFIER_ORCA,
563 62
                self.inputEventHandlers["reviewEndHandler"]))
564
565 62
        keyBindings.add(
566
            keybindings.KeyBinding(
567
                "KP_4",
568 62
                1 << settings.MODIFIER_ORCA,
569 62
                0,
570 62
                self.inputEventHandlers["reviewPreviousItemHandler"]))
571
572 62
        keyBindings.add(
573
            keybindings.KeyBinding(
574
                "KP_Left",
575 62
                1 << settings.MODIFIER_ORCA,
576 62
                0,
577 62
                self.inputEventHandlers["reviewPreviousItemHandler"]))
578
579 62
        keyBindings.add(
580
            keybindings.KeyBinding(
581
                "KP_4",
582 62
                1 << settings.MODIFIER_ORCA,
583 62
                1 << settings.MODIFIER_ORCA,
584 62
                self.inputEventHandlers["reviewAboveHandler"]))
585
586 62
        keyBindings.add(
587
            keybindings.KeyBinding(
588
                "KP_Left",
589 62
                1 << settings.MODIFIER_ORCA,
590 62
                1 << settings.MODIFIER_ORCA,
591 62
                self.inputEventHandlers["reviewAboveHandler"]))
592
593 62
        keyBindings.add(
594
            keybindings.KeyBinding(
595
                "KP_5",
596 62
                1 << settings.MODIFIER_ORCA,
597 62
                0,
598 62
                self.inputEventHandlers["reviewCurrentItemHandler"]))
599
600 62
        keyBindings.add(
601
            keybindings.KeyBinding(
602
                "KP_Begin",
603 62
                1 << settings.MODIFIER_ORCA,
604 62
                0,
605 62
                self.inputEventHandlers["reviewCurrentItemHandler"]))
606
607 62
        keyBindings.add(
608
            keybindings.KeyBinding(
609
                "KP_5",
610 62
                1 << settings.MODIFIER_ORCA,
611 62
                1 << settings.MODIFIER_ORCA,
612 62
                self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
613
614 62
        keyBindings.add(
615
            keybindings.KeyBinding(
616
                "KP_Begin",
617 62
                1 << settings.MODIFIER_ORCA,
618 62
                1 << settings.MODIFIER_ORCA,
619 62
                self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
620
621 62
        keyBindings.add(
622
            keybindings.KeyBinding(
623
                "KP_6",
624 62
                1 << settings.MODIFIER_ORCA,
625 62
                0,
626 62
                self.inputEventHandlers["reviewNextItemHandler"]))
627
628 62
        keyBindings.add(
629
            keybindings.KeyBinding(
630
                "KP_Right",
631 62
                1 << settings.MODIFIER_ORCA,
632 62
                0,
633 62
                self.inputEventHandlers["reviewNextItemHandler"]))
634
635 62
        keyBindings.add(
636
            keybindings.KeyBinding(
637
                "KP_6",
638 62
                1 << settings.MODIFIER_ORCA,
639 62
                1 << settings.MODIFIER_ORCA,
640 62
                self.inputEventHandlers["reviewBelowHandler"]))
641
642 62
        keyBindings.add(
643
            keybindings.KeyBinding(
644
                "KP_Right",
645 62
                1 << settings.MODIFIER_ORCA,
646 62
                1 << settings.MODIFIER_ORCA,
647 62
                self.inputEventHandlers["reviewBelowHandler"]))
648
649 62
        keyBindings.add(
650
            keybindings.KeyBinding(
651
                "KP_1",
652 62
                1 << settings.MODIFIER_ORCA,
653 62
                0,
654 62
                self.inputEventHandlers["reviewPreviousCharacterHandler"]))
655
656 62
        keyBindings.add(
657
            keybindings.KeyBinding(
658
                "KP_End",
659 62
                1 << settings.MODIFIER_ORCA,
660 62
                0,
661 62
                self.inputEventHandlers["reviewPreviousCharacterHandler"]))
662
663 62
        keyBindings.add(
664
            keybindings.KeyBinding(
665
                "KP_1",
666 62
                1 << settings.MODIFIER_ORCA,
667 62
                1 << settings.MODIFIER_ORCA,
668 62
                self.inputEventHandlers["reviewEndOfLineHandler"]))
669
670 62
        keyBindings.add(
671
            keybindings.KeyBinding(
672
                "KP_End",
673 62
                1 << settings.MODIFIER_ORCA,
674 62
                1 << settings.MODIFIER_ORCA,
675 62
                self.inputEventHandlers["reviewEndOfLineHandler"]))
676
677 62
        keyBindings.add(
678
            keybindings.KeyBinding(
679
                "KP_2",
680 62
                0,
681 62
                0,
682 62
                self.inputEventHandlers["reviewCurrentCharacterHandler"]))
683
684 62
        keyBindings.add(
685
            keybindings.KeyBinding(
686
                "KP_Down",
687 62
                0,
688 62
                0,
689 62
                self.inputEventHandlers["reviewCurrentCharacterHandler"]))
690
691 62
        keyBindings.add(
692
            keybindings.KeyBinding(
693
                "KP_3",
694 62
                0,
695 62
                0,
696 62
                self.inputEventHandlers["reviewNextCharacterHandler"]))
697
698 62
        keyBindings.add(
699
            keybindings.KeyBinding(
700
                "KP_Page_Down",
701 62
                0,
702 62
                0,
703 62
                self.inputEventHandlers["reviewNextCharacterHandler"]))
704
705 62
        return keyBindings
706
707 1
    def __getLaptopBindings(self):
708
        """Returns an instance of keybindings.KeyBindings that use the
709
        the main keyboard keys for focus tracking and flat review.
710
        """
711
712 0
        keyBindings = keybindings.KeyBindings()
713
714 0
        keyBindings.add(
715
            keybindings.KeyBinding(
716
                "7",
717 0
                1 << settings.MODIFIER_ORCA,
718 0
                1 << settings.MODIFIER_ORCA,
719 0
                self.inputEventHandlers["leftClickReviewItemHandler"]))
720
721 0
        keyBindings.add(
722
            keybindings.KeyBinding(
723
                "8",
724 0
                1 << settings.MODIFIER_ORCA,
725 0
                1 << settings.MODIFIER_ORCA,
726 0
                self.inputEventHandlers["rightClickReviewItemHandler"]))
727
728 0
        keyBindings.add(
729
            keybindings.KeyBinding(
730
                "p",
731 0
                1 << settings.MODIFIER_ORCA,
732 0
                1 << settings.MODIFIER_ORCA,
733 0
                self.inputEventHandlers["toggleFlatReviewModeHandler"]))
734
735 0
        keyBindings.add(
736
            keybindings.KeyBinding(
737
                "semicolon",
738 0
                1 << settings.MODIFIER_ORCA,
739 0
                1 << settings.MODIFIER_ORCA,
740 0
                self.inputEventHandlers["sayAllHandler"]))
741
742 0
        keyBindings.add(
743
            keybindings.KeyBinding(
744
                "Return",
745 0
                1 << settings.MODIFIER_ORCA,
746 0
                1 << settings.MODIFIER_ORCA,
747 0
                self.inputEventHandlers["whereAmIHandler"]))
748
749 0
        keyBindings.add(
750
            keybindings.KeyBinding(
751
                "slash",
752 0
                1 << settings.MODIFIER_ORCA,
753 0
                1 << settings.MODIFIER_ORCA,
754 0
                self.inputEventHandlers["whereAmIHandler"]))
755
756 0
        keyBindings.add(
757
            keybindings.KeyBinding(
758
                "bracketleft",
759 0
                1 << settings.MODIFIER_ORCA,
760 0
                1 << settings.MODIFIER_ORCA,
761 0
                self.inputEventHandlers["findHandler"]))
762
763 0
        keyBindings.add(
764
            keybindings.KeyBinding(
765
                "bracketright",
766 0
                (1 << settings.MODIFIER_ORCA
767
                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
768 0
                1 << settings.MODIFIER_ORCA,
769 0
                self.inputEventHandlers["findNextHandler"]))
770
771 0
        keyBindings.add(
772
            keybindings.KeyBinding(
773
                "bracketright",
774 0
                (1 << settings.MODIFIER_ORCA
775
                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
776 0
                (1 << settings.MODIFIER_ORCA | \
777
                     1 << atspi.Accessibility.MODIFIER_CONTROL),
778 0
                self.inputEventHandlers["findPreviousHandler"]))
779
780 0
        keyBindings.add(
781
            keybindings.KeyBinding(
782
                "u",
783 0
                (1 << settings.MODIFIER_ORCA | \
784
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
785 0
                1 << settings.MODIFIER_ORCA,
786 0
                self.inputEventHandlers["reviewPreviousLineHandler"]))
787
788 0
        keyBindings.add(
789
            keybindings.KeyBinding(
790
                "u",
791 0
                (1 << settings.MODIFIER_ORCA | \
792
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
793 0
                (1 << settings.MODIFIER_ORCA | \
794
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
795 0
                self.inputEventHandlers["reviewHomeHandler"]))
796
797 0
        keyBindings.add(
798
            keybindings.KeyBinding(
799
                "i",
800 0
                1 << settings.MODIFIER_ORCA,
801 0
                1 << settings.MODIFIER_ORCA,
802 0
                self.inputEventHandlers["reviewCurrentLineHandler"]))
803
804 0
        keyBindings.add(
805
            keybindings.KeyBinding(
806
                "o",
807 0
                (1 << settings.MODIFIER_ORCA | \
808
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
809 0
                1 << settings.MODIFIER_ORCA,
810 0
                self.inputEventHandlers["reviewNextLineHandler"]))
811
812 0
        keyBindings.add(
813
            keybindings.KeyBinding(
814
                "o",
815 0
                (1 << settings.MODIFIER_ORCA | \
816
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
817 0
                (1 << settings.MODIFIER_ORCA | \
818
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
819 0
                self.inputEventHandlers["reviewEndHandler"]))
820
821 0
        keyBindings.add(
822
            keybindings.KeyBinding(
823
                "j",
824 0
                (1 << settings.MODIFIER_ORCA | \
825
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
826 0
                1 << settings.MODIFIER_ORCA,
827 0
                self.inputEventHandlers["reviewPreviousItemHandler"]))
828
829 0
        keyBindings.add(
830
            keybindings.KeyBinding(
831
                "j",
832 0
                (1 << settings.MODIFIER_ORCA | \
833
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
834 0
                (1 << settings.MODIFIER_ORCA | \
835
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
836 0
                self.inputEventHandlers["reviewAboveHandler"]))
837
838 0
        keyBindings.add(
839 0
            keybindings.KeyBinding(
840
                "k",
841 0
                (1 << settings.MODIFIER_ORCA | \
842
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
843 0
                1 << settings.MODIFIER_ORCA,
844 0
                self.inputEventHandlers["reviewCurrentItemHandler"]))
845
846 0
        keyBindings.add(
847
            keybindings.KeyBinding(
848
                "k",
849 0
                (1 << settings.MODIFIER_ORCA | \
850
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
851 0
                (1 << settings.MODIFIER_ORCA | \
852
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
853 0
                self.inputEventHandlers["reviewCurrentAccessibleHandler"]))
854
855 0
        keyBindings.add(
856
            keybindings.KeyBinding(
857
                "l",
858 0
                (1 << settings.MODIFIER_ORCA | \
859
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
860 0
                1 << settings.MODIFIER_ORCA,
861 0
                self.inputEventHandlers["reviewNextItemHandler"]))
862
863 0
        keyBindings.add(
864
            keybindings.KeyBinding(
865
                "l",
866 0
                (1 << settings.MODIFIER_ORCA | \
867
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
868 0
                (1 << settings.MODIFIER_ORCA | \
869
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
870 0
                self.inputEventHandlers["reviewBelowHandler"]))
871
872 0
        keyBindings.add(
873
            keybindings.KeyBinding(
874
                "m",
875 0
                (1 << settings.MODIFIER_ORCA | \
876
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
877 0
                1 << settings.MODIFIER_ORCA,
878 0
                self.inputEventHandlers["reviewPreviousCharacterHandler"]))
879
880 0
        keyBindings.add(
881
            keybindings.KeyBinding(
882
                "m",
883 0
                (1 << settings.MODIFIER_ORCA | \
884
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
885 0
                (1 << settings.MODIFIER_ORCA | \
886
                 1 << atspi.Accessibility.MODIFIER_CONTROL),
887 0
                self.inputEventHandlers["reviewEndOfLineHandler"]))
888
889 0
        keyBindings.add(
890
            keybindings.KeyBinding(
891
                "comma",
892 0
                1 << settings.MODIFIER_ORCA,
893 0
                1 << settings.MODIFIER_ORCA,
894 0
                self.inputEventHandlers["reviewCurrentCharacterHandler"]))
895
896 0
        keyBindings.add(
897
            keybindings.KeyBinding(
898
                "period",
899 0
                1 << settings.MODIFIER_ORCA,
900 0
                1 << settings.MODIFIER_ORCA,
901 0
                self.inputEventHandlers["reviewNextCharacterHandler"]))
902
903 0
        return keyBindings
904
905 1
    def getKeyBindings(self):
906
        """Defines the key bindings for this script.
907
908
        Returns an instance of keybindings.KeyBindings.
909
        """
910 62
        keyBindings = script.Script.getKeyBindings(self)
911
912 62
        if settings.keyboardLayout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP:
913 2418
            for keyBinding in self.__getDesktopBindings().keyBindings:
914 2356
                keyBindings.add(keyBinding)
915
        else:
916 0
            for keyBinding in self.__getLaptopBindings().keyBindings:
917 0
                keyBindings.add(keyBinding)
918
919 62
        keyBindings.add(
920
            keybindings.KeyBinding(
921
                "Num_Lock",
922 62
                1 << settings.MODIFIER_ORCA,
923 62
                1 << settings.MODIFIER_ORCA,
924 62
                self.inputEventHandlers["showZonesHandler"]))
925
926 62
        keyBindings.add(
927
            keybindings.KeyBinding(
928
                "F11",
929 62
                1 << settings.MODIFIER_ORCA,
930 62
                1 << settings.MODIFIER_ORCA,
931 62
                self.inputEventHandlers["toggleTableCellReadModeHandler"]))
932
933 62
        keyBindings.add(
934
            keybindings.KeyBinding(
935
                "SunF36",
936 62
                1 << settings.MODIFIER_ORCA,
937 62
                1 << settings.MODIFIER_ORCA,
938 62
                self.inputEventHandlers["toggleTableCellReadModeHandler"]))
939
940 62
        keyBindings.add(
941
            keybindings.KeyBinding(
942
                "f",
943 62
                1 << settings.MODIFIER_ORCA,
944 62
                1 << settings.MODIFIER_ORCA,
945 62
                self.inputEventHandlers["readCharAttributesHandler"]))
946
947 62
        keyBindings.add(
948
            keybindings.KeyBinding(
949
                "F3",
950 62
                1 << settings.MODIFIER_ORCA,
951 62
                1 << settings.MODIFIER_ORCA,
952 62
                self.inputEventHandlers["reportScriptInfoHandler"]))
953
954 62
        keyBindings.add(
955
            keybindings.KeyBinding(
956
                "F1",
957 62
                1 << settings.MODIFIER_ORCA,
958 62
                1 << settings.MODIFIER_ORCA,
959 62
                self.inputEventHandlers["enterLearnModeHandler"]))
960
961 62
        keyBindings.add(
962
            keybindings.KeyBinding(
963
                "Left",
964 62
                1 << settings.MODIFIER_ORCA,
965 62
                1 << settings.MODIFIER_ORCA,
966 62
                self.inputEventHandlers["decreaseSpeechRateHandler"]))
967
968 62
        keyBindings.add(
969
            keybindings.KeyBinding(
970
                "Right",
971 62
                1 << settings.MODIFIER_ORCA,
972 62
                1 << settings.MODIFIER_ORCA,
973 62
                self.inputEventHandlers["increaseSpeechRateHandler"]))
974
975 62
        keyBindings.add(
976
            keybindings.KeyBinding(
977
                "Down",
978 62
                1 << settings.MODIFIER_ORCA,
979 62
                1 << settings.MODIFIER_ORCA,
980 62
                self.inputEventHandlers["decreaseSpeechPitchHandler"]))
981
982 62
        keyBindings.add(
983
            keybindings.KeyBinding(
984
                "Up",
985 62
                1 << settings.MODIFIER_ORCA,
986 62
                1 << settings.MODIFIER_ORCA,
987 62
                self.inputEventHandlers["increaseSpeechPitchHandler"]))
988
989 62
        keyBindings.add(
990
            keybindings.KeyBinding(
991
                "q",
992 62
                1 << settings.MODIFIER_ORCA,
993 62
                1 << settings.MODIFIER_ORCA,
994 62
                self.inputEventHandlers["shutdownHandler"]))
995
996 62
        keyBindings.add(
997
            keybindings.KeyBinding(
998
                "Pause",
999 62
                0,
1000 62
                0,
1001 62
                self.inputEventHandlers["keystrokeRecordingHandler"]))
1002
1003 62
        keyBindings.add(
1004
            keybindings.KeyBinding(
1005
                "space",
1006 62
                (1 << settings.MODIFIER_ORCA
1007
                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
1008 62
                1 << settings.MODIFIER_ORCA,
1009 62
                self.inputEventHandlers["preferencesSettingsHandler"]))
1010
1011 62
        keyBindings.add(
1012
            keybindings.KeyBinding(
1013
                "space",
1014 62
                (1 << settings.MODIFIER_ORCA
1015
                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
1016 62
                (1 << settings.MODIFIER_ORCA | \
1017
                     1 << atspi.Accessibility.MODIFIER_CONTROL),
1018 62
                self.inputEventHandlers["loadUserSettingsHandler"]))
1019
1020 62
        keyBindings.add(
1021
            keybindings.KeyBinding(
1022
                "s",
1023 62
                1 << settings.MODIFIER_ORCA,
1024 62
                1 << settings.MODIFIER_ORCA,
1025 62
                self.inputEventHandlers["toggleSilenceSpeechHandler"]))
1026
1027 62
        keyBindings.add(
1028
            keybindings.KeyBinding(
1029
                "F5",
1030 62
                (1 << settings.MODIFIER_ORCA
1031
                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
1032 62
                1 << settings.MODIFIER_ORCA,
1033 62
                self.inputEventHandlers["listAppsHandler"]))
1034
1035 62
        keyBindings.add(
1036
            keybindings.KeyBinding(
1037
                "F4",
1038 62
                1 << settings.MODIFIER_ORCA,
1039 62
                1 << settings.MODIFIER_ORCA,
1040 62
                self.inputEventHandlers["cycleDebugLevelHandler"]))
1041
1042 62
        keyBindings.add(
1043
            keybindings.KeyBinding(
1044
                "F6",
1045 62
                1 << settings.MODIFIER_ORCA,
1046 62
                1 << settings.MODIFIER_ORCA,
1047 62
                self.inputEventHandlers["printActiveAppHandler"]))
1048
1049 62
        keyBindings.add(
1050
            keybindings.KeyBinding(
1051
                "F7",
1052 62
                (1 << settings.MODIFIER_ORCA | \
1053
                     1 << atspi.Accessibility.MODIFIER_CONTROL),
1054 62
                1 << settings.MODIFIER_ORCA,
1055 62
                self.inputEventHandlers["printAncestryHandler"]))
1056
1057 62
        keyBindings.add(
1058
            keybindings.KeyBinding(
1059
                "F8",
1060 62
                1 << settings.MODIFIER_ORCA,
1061 62
                1 << settings.MODIFIER_ORCA,
1062 62
                self.inputEventHandlers["printHierarchyHandler"]))
1063
1064 62
        keyBindings.add(
1065
            keybindings.KeyBinding(
1066
                "F10",
1067 62
                1 << settings.MODIFIER_ORCA,
1068 62
                1 << settings.MODIFIER_ORCA,
1069 62
                self.inputEventHandlers["nextPresentationManagerHandler"]))
1070
1071 62
        keyBindings = settings.overrideKeyBindings(self, keyBindings)
1072
1073 62
        return keyBindings
1074
1075 1
    def getBrailleBindings(self):
1076
        """Defines the braille bindings for this script.
1077
1078
        Returns a dictionary where the keys are BrlTTY commands and the
1079
        values are InputEventHandler instances.
1080
        """
1081 62
        brailleBindings = script.Script.getBrailleBindings(self)
1082 62
        brailleBindings[braille.CMD_FWINLT]   = \
1083
            self.inputEventHandlers["panBrailleLeftHandler"]
1084 62
        brailleBindings[braille.CMD_FWINRT]   = \
1085
            self.inputEventHandlers["panBrailleRightHandler"]
1086 62
        brailleBindings[braille.CMD_LNUP]     = \
1087
            self.inputEventHandlers["reviewAboveHandler"]
1088 62
        brailleBindings[braille.CMD_LNDN]     = \
1089
            self.inputEventHandlers["reviewBelowHandler"]
1090 62
        brailleBindings[braille.CMD_TOP_LEFT] = \
1091
            self.inputEventHandlers["reviewHomeHandler"]
1092 62
        brailleBindings[braille.CMD_BOT_LEFT] = \
1093
            self.inputEventHandlers["reviewBottomLeftHandler"]
1094 62
        brailleBindings[braille.CMD_HOME]     = \
1095
            self.inputEventHandlers["goBrailleHomeHandler"]
1096
1097 62
        return brailleBindings
1098
1099 1
    def processKeyboardEvent(self, keyboardEvent):
1100
        """Processes the given keyboard event. It uses the super
1101
        class equivalent to do most of the work. The only thing done here
1102
        is to detect when the user is trying to get out of learn mode.
1103
1104
        Arguments:
1105
        - keyboardEvent: an instance of input_event.KeyboardEvent
1106
        """
1107
1108 154
        if (keyboardEvent.type == atspi.Accessibility.KEY_PRESSED_EVENT) and \
1109
           (keyboardEvent.event_string == "Escape"):
1110 0
            settings.learnModeEnabled = False
1111
1112 154
        return script.Script.processKeyboardEvent(self, keyboardEvent)
1113
1114 1
    def __sayAllProgressCallback(self, context, type):
1115
        # [[[TODO: WDW - this needs work.  Need to be able to manage
1116
        # the monitoring of progress and couple that with both updating
1117
        # the visual progress of what is being spoken as well as
1118
        # positioning the cursor when speech has stopped.]]]
1119
        #
1120 0
        if type == speechserver.SayAllContext.PROGRESS:
1121
            #print "PROGRESS", context.utterance, context.currentOffset
1122
            #obj = context.obj
1123
            #[x, y, width, height] = obj.text.getCharacterExtents(
1124
            #    context.currentOffset, 0)
1125
            #print context.currentOffset, x, y, width, height
1126
            #self.drawOutline(x, y, width, height)
1127 0
            pass
1128 0
        elif type == speechserver.SayAllContext.INTERRUPTED:
1129
            #print "INTERRUPTED", context.utterance, context.currentOffset
1130 0
            context.obj.text.setCaretOffset(context.currentOffset);
1131 0
        elif type == speechserver.SayAllContext.COMPLETED:
1132
            #print "COMPLETED", context.utterance, context.currentOffset
1133 0
            orca.setLocusOfFocus(None, context.obj, False)
1134 0
            context.obj.text.setCaretOffset(context.currentOffset)
1135
1136 1
    def sayAll(self, inputEvent):
1137 0
        if not orca_state.locusOfFocus:
1138 0
            pass
1139 0
        elif orca_state.locusOfFocus.text:
1140 0
            speech.sayAll(self.textLines(orca_state.locusOfFocus),
1141 0
                          self.__sayAllProgressCallback)
1142
        else:
1143 0
            speech.speakUtterances(
1144
                self.speechGenerator.getSpeech(orca_state.locusOfFocus, False))
1145 0
        return True
1146
1147 1
    def isTextArea(self, obj):
1148
        """Returns True if obj is a GUI component that is for entering text.
1149
1150
        Arguments:
1151
        - obj: an accessible
1152
        """
1153 1405
        return obj and obj.role and ((obj.role == rolenames.ROLE_TEXT) \
1154
                                     or (obj.role == rolenames.ROLE_PARAGRAPH))
1155
1156 1
    def getText(self, obj, startOffset, endOffset):
1157
        """Returns the substring of the given object's text specialization.
1158
1159
        Arguments:
1160
        - obj: an accessible supporting the accessible text specialization
1161
        - startOffset: the starting character position
1162
        - endOffset: the ending character position
1163
        """
1164 47
        return obj.text.getText(startOffset, endOffset)
1165
1166 1
    def sayPhrase(self, obj, startOffset, endOffset):
1167
        """Speaks the text of an Accessible object between the start and
1168
        end offsets, unless the phrase is empty in which case it's ignored.
1169
1170
        Arguments:
1171
        - obj: an Accessible object that implements the AccessibleText
1172
               interface
1173
        - startOffset: the start text offset.
1174
        - endOffset: the end text offset.
1175
        """
1176
1177
        # Swap values if in wrong order (StarOffice is fussy about that).
1178
        #
1179 0
        if ((startOffset > endOffset) and (endOffset != -1)) or \
1180
           (startOffset == -1):
1181 0
            temp = endOffset
1182 0
            endOffset = startOffset
1183 0
            startOffset = temp
1184
1185 0
        phrase = self.getText(obj, startOffset, endOffset)
1186
1187 0
        if len(phrase):
1188 0
            if phrase.isupper():
1189 0
                voice = self.voices[settings.UPPERCASE_VOICE]
1190
            else:
1191 0
                voice = self.voices[settings.DEFAULT_VOICE]
1192
1193 0
            phrase = self.adjustForRepeats(phrase)
1194 0
            speech.speak(phrase, voice)
1195 0
            self.speakTextSelectionState(obj, startOffset, endOffset)
1196
1197 1
    def sayLine(self, obj):
1198
        """Speaks the line of an AccessibleText object that contains the
1199
        caret, unless the line is empty in which case it's ignored.
1200
1201
        Arguments:
1202
        - obj: an Accessible object that implements the AccessibleText
1203
               interface
1204
        """
1205
1206
        # Get the AccessibleText interface of the provided object
1207
        #
1208 44
        [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
1209 44
        debug.println(debug.LEVEL_FINEST, \
1210 44
            "sayLine: line=<%s>, len=%d, start=%d, caret=%d, speakBlankLines=%s" % \
1211
            (line, len(line), startOffset, caretOffset, settings.speakBlankLines))
1212
1213 44
        if len(line):
1214 22
            if line.isupper():
1215 0
                voice = self.voices[settings.UPPERCASE_VOICE]
1216
            else:
1217 22
                voice = self.voices[settings.DEFAULT_VOICE]
1218
1219 22
            if settings.enableSpeechIndentation:
1220 0
                self.speakTextIndentation(obj, line)
1221 22
            line = self.adjustForRepeats(line)
1222 22
            speech.speak(line, voice)
1223 22
            self.speakTextSelectionState(obj, startOffset, caretOffset)
1224
1225
        else:
1226
            # Speak blank line if appropriate. It's necessary to
1227
            # test whether the first character is a newline, because
1228
            # StarOffice blank lines are empty, and so StarOffice.py
1229
            # handles speaking blank lines.
1230 22
            char = obj.text.getTextAtOffset(caretOffset,
1231 22
                atspi.Accessibility.TEXT_BOUNDARY_CHAR)
1232 22
            debug.println(debug.LEVEL_FINEST,
1233 22
                "sayLine: character=<%s>, start=%d, end=%d" % \
1234
                (char[0], char[1], char[2]))
1235
1236 22
            if char[0] == "\n" and startOffset == caretOffset \
1237
                   and settings.speakBlankLines:
1238 4
                speech.speak(_("blank"))
1239
1240
1241 1
    def sayWord(self, obj):
1242
        """Speaks the word at the caret.  [[[TODO: WDW - what if there is no
1243
        word at the caret?]]]
1244
1245
        Arguments:
1246
        - obj: an Accessible object that implements the AccessibleText
1247
               interface
1248
        """
1249
1250 0
        text = obj.text
1251 0
        offset = text.caretOffset
1252 0
        lastKey = orca_state.lastInputEvent.event_string
1253 0
        lastWord = orca_state.lastWord
1254
1255 0
        [word, startOffset, endOffset] = \
1256
            text.getTextAtOffset(offset,
1257 0
                                 atspi.Accessibility.TEXT_BOUNDARY_WORD_START)
1258
1259
        # Speak a newline if a control-right-arrow or control-left-arrow
1260
        # was used to cross a line boundary. Handling is different for
1261
        # the two keys since control-right-arrow places the cursor after
1262
        # the last character in a word, but control-left-arrow places
1263
        # the cursor at the beginning of a word.
1264
        #
1265 0
        if lastKey == "Right" and len(lastWord) > 0:
1266 0
            lastChar = lastWord[len(lastWord) - 1]
1267 0
            if lastChar == "\n" and lastWord != word:
1268 0
                voice = self.voices[settings.DEFAULT_VOICE]
1269 0
                speech.speak(chnames.getCharacterName("\n"), voice, False)
1270
1271 0
        if lastKey == "Left" and len(word) > 0:
1272 0
            lastChar = word[len(word) - 1]
1273 0
            if lastChar == "\n" and lastWord != word:
1274 0
                voice = self.voices[settings.DEFAULT_VOICE]
1275 0
                speech.speak(chnames.getCharacterName("\n"), voice, False)
1276
1277 0
        if self.getLinkIndex(obj, offset) >= 0:
1278 0
            voice = self.voices[settings.HYPERLINK_VOICE]
1279 0
        elif word.isupper():
1280 0
            voice = self.voices[settings.UPPERCASE_VOICE]
1281
        else:
1282 0
            voice = self.voices[settings.DEFAULT_VOICE]
1283
1284 0
        word = self.adjustForRepeats(word)
1285 0
        orca_state.lastWord = word
1286 0
        speech.speak(word, voice)
1287 0
        self.speakTextSelectionState(obj, startOffset, endOffset)
1288
1289 1
    def speakTextIndentation(self, obj, line):
1290
        """Speaks a summary of the number of spaces and/or tabs at the
1291
        beginning of the given line.
1292
1293
        Arguments:
1294
        - obj: the text object.
1295
        - line: the string to check for spaces and tabs.
1296
        """
1297
1298
        # For the purpose of speaking the text indentation, replace
1299
        # occurances of UTF-8 '\302\240' (non breaking space) with
1300
        # spaces.
1301
        #
1302 0
        line = line.replace("\302\240",  " ")
1303 0
        line = line.decode("UTF-8")
1304
1305 0
        spaceCount = 0
1306 0
        tabCount = 0
1307 0
        for offset in range(0, len(line)):
1308 0
            if line[offset] == ' ':
1309 0
                spaceCount += 1
1310 0
            elif line[offset] == '\t':
1311 0
                tabCount += 1
1312
            else:
1313 0
                break
1314
1315 0
        utterance = ''
1316 0
        if spaceCount:
1317 0
            if spaceCount == 1:
1318 0
                utterance += _("1 space ")
1319
            else:
1320 0
                utterance += (_("%d spaces ") % spaceCount)
1321 0
        if tabCount:
1322 0
            if tabCount == 1:
1323 0
                utterance += _("1 tab ")
1324
            else:
1325 0
                utterance += (_("%d tabs ") % tabCount)
1326 0
        if len(utterance):
1327 0
            speech.speak(utterance)
1328
1329 1
    def echoPreviousWord(self, obj):
1330
        """Speaks the word prior to the caret, as long as there is
1331
        a word prior to the caret and there is no intervening word
1332
        delimiter between the caret and the end of the word.
1333
1334
        The entry condition for this method is that the character
1335
        prior to the current caret position is a word delimiter,
1336
        and it's what caused this method to be called in the first
1337
        place.
1338
1339
        Arguments:
1340
        - obj: an Accessible object that implements the AccessibleText
1341
               interface.
1342
        """
1343
1344 0
        text = obj.text
1345
1346
        # Check for a bunch of preconditions we care about
1347
        #
1348 0
        if not text:
1349 0
            return
1350
1351 0
        offset = text.caretOffset - 1
1352 0
        if (offset < 0):
1353 0
            return
1354
1355 0
        [char, startOffset, endOffset] = \
1356
            text.getTextAtOffset( \
1357
                offset,
1358 0
                atspi.Accessibility.TEXT_BOUNDARY_CHAR)
1359 0
        if not self.isWordDelimiter(char):
1360 0
            return
1361
1362
        # OK - we seem to be cool so far.  So...starting with what
1363
        # should be the last character in the word (caretOffset - 2),
1364
        # work our way to the beginning of the word, stopping when
1365
        # we hit another word delimiter.
1366
        #
1367 0
        wordEndOffset = text.caretOffset - 2
1368 0
        wordStartOffset = wordEndOffset
1369
1370 0
        while wordStartOffset >= 0:
1371 0
            [char, startOffset, endOffset] = \
1372
                text.getTextAtOffset( \
1373
                    wordStartOffset,
1374 0
                    atspi.Accessibility.TEXT_BOUNDARY_CHAR)
1375 0
            if self.isWordDelimiter(char):
1376 0
                break
1377
            else:
1378 0
                wordStartOffset -= 1
1379
1380
        # If we came across a word delimiter before hitting any
1381
        # text, we really don't have a previous word.
1382
        #
1383
        # Otherwise, get the word.  Remember we stopped when we
1384
        # hit a word delimiter, so the word really starts at
1385
        # wordStartOffset + 1.  getText also does not include
1386
        # the character at wordEndOffset, so we need to adjust
1387
        # for that, too.
1388
        #
1389 0
        if wordStartOffset == wordEndOffset:
1390 0
            return
1391
        else:
1392 0
            word = self.getText(obj, wordStartOffset + 1, wordEndOffset + 1)
1393
1394 0
        if self.getLinkIndex(obj, wordStartOffset + 1) >= 0:
1395 0
            voice = self.voices[settings.HYPERLINK_VOICE]
1396 0
        elif word.isupper():
1397 0
            voice = self.voices[settings.UPPERCASE_VOICE]
1398
        else:
1399 0
            voice = self.voices[settings.DEFAULT_VOICE]
1400
1401 0
        word = self.adjustForRepeats(word)
1402 0
        speech.speak(word, voice)
1403
1404 1
    def sayCharacter(self, obj):
1405
        """Speak the character under the caret.  [[[TODO: WDW - isn't the
1406
        caret between characters?]]]
1407
1408
        Arguments:
1409
        - obj: an Accessible object that implements the AccessibleText
1410
               interface
1411
        """
1412
1413 60
        text = obj.text
1414 60
        offset = text.caretOffset
1415
1416
        # If we have selected text and the last event was a move to the
1417
        # right, then speak the character to the left of where the text
1418
        # caret is (i.e. the selected character).
1419
        #
1420 60
        mods = orca_state.lastInputEvent.modifiers
1421 60
        shiftMask = 1 << atspi.Accessibility.MODIFIER_SHIFT
1422 60
        if (mods & shiftMask) \
1423
            and orca_state.lastInputEvent.event_string == "Right":
1424 0
            startOffset = offset-1
1425 0
            endOffset = offset
1426
        else:
1427 60
            startOffset = offset
1428 60
            endOffset = offset+1
1429 60
        if endOffset > text.characterCount:
1430 14
            character = "\n"
1431
        else:
1432 46
            character = self.getText(obj, startOffset, endOffset)
1433 60
        if self.getLinkIndex(obj, offset) >= 0:
1434 0
            voice = self.voices[settings.HYPERLINK_VOICE]
1435 60
        elif character.isupper():
1436 0
            voice = self.voices[settings.UPPERCASE_VOICE]
1437
        else:
1438 60
            voice = self.voices[settings.DEFAULT_VOICE]
1439
1440 60
        prevChar = self.getText(obj, startOffset-1, endOffset-1)
1441
1442 60
        debug.println(debug.LEVEL_FINEST, \
1443 60
            "sayCharacter: prev=<%s>, char=<%s>, startOffset=%d, caretOffset=%d, endOffset=%d, speakBlankLines=%s" % \
1444
            (prevChar, character, startOffset, offset, endOffset, settings.speakBlankLines))
1445
1446
        # Handle speaking newlines when the right-arrow key is pressed.
1447 60
        if orca_state.lastInputEvent.event_string == "Right":
1448 30
            if prevChar == "\n":
1449
                # The cursor is at the beginning of a line.
1450
                # Speak a newline.
1451 6
                speech.speak(chnames.getCharacterName("\n"), voice, False)
1452
1453
        # Handle speaking newlines when the left-arrow key is pressed.
1454 30
        elif orca_state.lastInputEvent.event_string == "Left":
1455 30
            if character == "\n":
1456
                # The cursor is at the end of a line.
1457
                # Speak a newline.
1458 12
                speech.speak(chnames.getCharacterName("\n"), voice, False)
1459
1460 60
        if character == "\n":
1461 26
            if prevChar == "\n":
1462
                # This is a blank line. Announce it if the user requested
1463
                # that blank lines be spoken.
1464 1
                if settings.speakBlankLines:
1465 1
                    speech.speak(_("blank"), voice, False)
1466
        else:
1467 34
            speech.speak(character, voice, False)
1468
1469 60
        self.speakTextSelectionState(obj, startOffset, endOffset)
1470
1471
1472 1
    def whereAmI(self, inputEvent):
1473
        """
1474
        Speaks information about the current object of interest.
1475
        """
1476 0
        obj = orca_state.locusOfFocus
1477 0
        self.updateBraille(obj)
1478
1479 0
        if inputEvent and orca_state.lastInputEvent \
1480
           and isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
1481 0
            string = atspi.KeystrokeListener.keyEventToString(inputEvent)
1482 0
            debug.println(debug.LEVEL_FINEST, "default.whereAmI: %s" % string)
1483
        else:
1484 0
            context = self.speechGenerator.getSpeechContext(obj)
1485 0
            return where_am_I.whereAmI(obj, context, False, False)
1486
1487 0
        orcaKey = False
1488 0
        if settings.keyboardLayout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP:
1489 0
            orcaKey = (inputEvent.modifiers & (1 << settings.MODIFIER_ORCA)) \
1490
                        == (1 << settings.MODIFIER_ORCA)
1491
        else:
1492 0
            orcaKey = (inputEvent.event_string == "/")
1493
1494 0
        doubleClick = \
1495
           (self.getClickCount(self.lastWhereAmIEvent, inputEvent) == 2)
1496 0
        self.lastWhereAmIEvent = inputEvent
1497
1498 0
        context = self.speechGenerator.getSpeechContext(obj)
1499
1500 0
        return where_am_I.whereAmI(obj, context, doubleClick, orcaKey)
1501
1502 1
    def findCommonAncestor(self, a, b):
1503
        """Finds the common ancestor between Accessible a and Accessible b.
1504
1505
        Arguments:
1506
        - a: Accessible
1507
        - b: Accessible
1508
        """
1509
1510 907
        debug.println(debug.LEVEL_FINEST,
1511 907
                      "default.findCommonAncestor...")
1512
1513 907
        if (not a) or (not b):
1514 32
            return None
1515
1516 875
        if a == b:
1517 0
            return a
1518
1519 875
        aParents = [a]
1520 875
        try:
1521 875
            parent = a.parent
1522 5717
            while parent and (parent.parent != parent):
1523 4842
                aParents.append(parent)
1524 4842
                parent = parent.parent
1525 749
            aParents.reverse()
1526 126
        except:
1527 126
            debug.printException(debug.LEVEL_FINEST)
1528 126
            pass
1529
1530 875
        bParents = [b]
1531 875
        try:
1532 875
            parent = b.parent
1533 5441
            while parent and (parent.parent != parent):
1534 4566
                bParents.append(parent)
1535 4566
                parent = parent.parent
1536 875
            bParents.reverse()
1537 0
        except:
1538 0
            debug.printException(debug.LEVEL_FINEST)
1539 0
            pass
1540
1541 875
        commonAncestor = None
1542
1543 875
        maxSearch = min(len(aParents), len(bParents))
1544 875
        i = 0
1545 4749
        while i < maxSearch:
1546 4624
            if aParents[i] == bParents[i]:
1547 3874
                commonAncestor = aParents[i]
1548 3874
                i += 1
1549
            else:
1550 750
                break
1551
1552 875
        debug.println(debug.LEVEL_FINEST,
1553 875
                      "...default.findCommonAncestor")
1554
1555 875
        return commonAncestor
1556
1557 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
1558
        """Called when the visual object with focus changes.
1559
1560
        Arguments:
1561
        - event: if not None, the Event that caused the change
1562
        - oldLocusOfFocus: Accessible that is the old locus of focus
1563
        - newLocusOfFocus: Accessible that is the new locus of focus
1564
        """
1565
1566 958
        try:
1567 958
            if self.findCommandRun:
1568
                # Then the Orca Find dialog has just given up focus
1569
                # to the original window.  We don't want to speak
1570
                # the window title, current line, etc.
1571 0
                return
1572 0
        except:
1573 0
            pass
1574
1575 958
        if newLocusOfFocus:
1576 926
            mag.magnifyAccessible(event, newLocusOfFocus)
1577
1578
        # We always automatically go back to focus tracking mode when
1579
        # the focus changes.
1580
        #
1581 958
        if self.flatReviewContext:
1582 0
            self.toggleFlatReviewMode()
1583
1584
        # [[[TODO: WDW - HACK because parents that manage their descendants
1585
        # can give us a different object each time we ask for the same
1586
        # exact child.  So...we do a check here to see if the old object
1587
        # and new object have the same index in the parent and if they
1588
        # have the same name.  If so, then they are likely to be the same
1589
        # object.  The reason we check for the name here is a small sanity
1590
        # check.  This whole algorithm could fail because one might be
1591
        # deleting/adding identical elements from/to a list or table, thus
1592
        # the objects really could be different even though they seem the
1593
        # same.  Logged as bug 319675.]]]
1594
        #
1595 958
        if self.isSameObject(oldLocusOfFocus, newLocusOfFocus):
1596 0
            return
1597
1598
        # Well...now that we got that behind us, let's do what we're supposed
1599
        # to do.
1600
        #
1601 958
        if oldLocusOfFocus:
1602 925
            oldParent = oldLocusOfFocus.parent
1603
        else:
1604 33
            oldParent = None
1605
1606 958
        if newLocusOfFocus:
1607 926
            newParent = newLocusOfFocus.parent
1608
        else:
1609 32
            newParent = None
1610
1611 958
        if newLocusOfFocus:
1612 926
            self.updateBraille(newLocusOfFocus)
1613
1614 907
            utterances = []
1615
1616
            # Now figure out how of the container context changed and
1617
            # speech just what is different.
1618
            #
1619 907
            commonAncestor = self.findCommonAncestor(oldLocusOfFocus,
1620 907
                                                     newLocusOfFocus)
1621 907
            if commonAncestor:
1622 725
                context = self.speechGenerator.getSpeechContext( \
1623
                                           newLocusOfFocus, commonAncestor)
1624 725
                utterances.append(" ".join(context))
1625
1626
            # Now, we'll treat table row and column headers as context
1627
            # as well.  This requires special handling because we're
1628
            # making headers seem hierarchical in the context, but they
1629
            # are not hierarchical in the containment hierarchicy.
1630
            # We also only want to speak the one that changed.  If both
1631
            # changed, first speak the row header, then the column header.
1632
            #
1633
            # We also keep track of tree level depth and only announce
1634
            # that if it changes.
1635
            #
1636 907
            oldNodeLevel = -1
1637 907
            newNodeLevel = -1
1638 907
            if newLocusOfFocus.role == rolenames.ROLE_TABLE_CELL:
1639 163
                if oldParent and oldParent.table:
1640 152
                    table = oldParent.table
1641 152
                    oldRow = table.getRowAtIndex(oldLocusOfFocus.index)
1642 152
                    oldCol = table.getColumnAtIndex(oldLocusOfFocus.index)
1643
                else:
1644 11
                    oldRow = -1
1645 11
                    oldCol = -1
1646
1647 163
                if newParent and newParent.table:
1648 163
                    table = newParent.table
1649 163
                    newRow = table.getRowAtIndex(newLocusOfFocus.index)
1650 163
                    newCol = table.getColumnAtIndex(newLocusOfFocus.index)
1651
1652 162
                    if (newRow != oldRow) or (oldParent != newParent):
1653 117
                        desc = newParent.table.getRowDescription(newRow)
1654 117
                        if desc and len(desc):
1655 0
                            text = desc
1656 0
                            if settings.speechVerbosityLevel \
1657
                                   == settings.VERBOSITY_LEVEL_VERBOSE:
1658 0
                                text += " " \
1659
                                        + rolenames.rolenames[\
1660
                                        rolenames.ROLE_ROW_HEADER].speech
1661 0
                            utterances.append(text)
1662 162
                    if (newCol != oldCol) or (oldParent != newParent):
1663
                        # Don't speak Thunderbird column headers, since
1664
                        # it's not possible to navigate across a row.
1665 61
                        topName = self.getTopLevelName(newLocusOfFocus)
1666 61
                        if not topName.endswith(" - Thunderbird"):
1667 61
                            desc = newParent.table.getColumnDescription(newCol)
1668 61
                            if desc and len(desc):
1669 61
                                text = desc
1670 61
                                if settings.speechVerbosityLevel \
1671
                                       == settings.VERBOSITY_LEVEL_VERBOSE:
1672 61
                                    text += " " \
1673
                                            + rolenames.rolenames[\
1674
                                            rolenames.ROLE_COLUMN_HEADER].speech
1675 61
                                utterances.append(text)
1676
1677 906
            oldNodeLevel = self.getNodeLevel(oldLocusOfFocus)
1678 780
            newNodeLevel = self.getNodeLevel(newLocusOfFocus)
1679
1680
            # We'll also treat radio button groups as though they are
1681
            # in a context, with the label for the group being the
1682
            # name of the context.
1683
            #
1684 780
            if newLocusOfFocus.role == rolenames.ROLE_RADIO_BUTTON:
1685 6
                radioGroupLabel = None
1686 6
                inSameGroup = False
1687 6
                relations = newLocusOfFocus.relations
1688 12
                for relation in relations:
1689 6
                    if (not radioGroupLabel) \
1690
                        and (relation.getRelationType() \
1691
                             == atspi.Accessibility.RELATION_LABELLED_BY):
1692 0
                        radioGroupLabel = atspi.Accessible.makeAccessible(
1693
                            relation.getTarget(0))
1694 6
                    if (not inSameGroup) \
1695
                        and (relation.getRelationType() \
1696
                             == atspi.Accessibility.RELATION_MEMBER_OF):
1697 18
                        for i in range(0, relation.getNTargets()):
1698 15
                            target = atspi.Accessible.makeAccessible(
1699
                                relation.getTarget(i))
1700 15
                            if target == oldLocusOfFocus:
1701 3
                                inSameGroup = True
1702 3
                                break
1703
1704
                # We'll only announce the radio button group when we
1705
                # switch groups.
1706
                #
1707 6
                if (not inSameGroup) and radioGroupLabel:
1708 0
                    utterances.append(self.getDisplayedText(radioGroupLabel))
1709
1710
            # Get the text for the object itself.
1711
            #
1712 780
            utterances.extend(
1713
                self.speechGenerator.getSpeech(newLocusOfFocus, False))
1714
1715
            # Now speak the new tree node level if it has changed.
1716
            #
1717 780
            if (oldNodeLevel != newNodeLevel) \
1718
               and (newNodeLevel >= 0):
1719 16
                utterances.append(_("tree level %d") % (newNodeLevel + 1))
1720
1721
            # We might be automatically speaking the unbound labels
1722
            # in a dialog box as the result of the dialog box suddenly
1723
            # appearing.  If so, don't interrupt this because of a
1724
            # focus event that occurs when something like the "OK"
1725
            # button gets focus shortly after the window appears.
1726
            #
1727 780
            shouldNotInterrupt = (event and event.type == "focus:") \
1728
                and self.windowActivateTime \
1729
                and ((time.time() - self.windowActivateTime) < 1.0)
1730
1731 780
            if newLocusOfFocus.role == rolenames.ROLE_LINK:
1732 0
                voice = self.voices[settings.HYPERLINK_VOICE]
1733
            else:
1734 780
                voice = self.voices[settings.DEFAULT_VOICE]
1735
1736 780
            speech.speakUtterances(utterances, voice, not shouldNotInterrupt)
1737
1738
            # If this is a table cell, save the current row and column
1739
            # information in the table cell's table, so that we can use
1740
            # it the next time.
1741
            #
1742 780
            if newLocusOfFocus.role == rolenames.ROLE_TABLE_CELL:
1743 162
                if newParent and newParent.table:
1744 162
                    table = newParent.table
1745 162
                    column = table.getColumnAtIndex(newLocusOfFocus.index)
1746 162
                    newParent.lastColumn = column
1747 162
                    row = table.getRowAtIndex(newLocusOfFocus.index)
1748 162
                    newParent.lastRow = row
1749
        else:
1750 32
            orca_state.noFocusTimeStamp = time.time()
1751
1752 1
    def visualAppearanceChanged(self, event, obj):
1753
        """Called when the visual appearance of an object changes.  This
1754
        method should not be called for objects whose visual appearance
1755
        changes solely because of focus -- setLocusOfFocus is used for that.
1756
        Instead, it is intended mostly for objects whose notional 'value' has
1757
        changed, such as a checkbox changing state, a progress bar advancing,
1758
        a slider moving, text inserted, caret moved, etc.
1759
1760
        Arguments:
1761
        - event: if not None, the Event that caused this to happen
1762
        - obj: the Accessible whose visual appearance changed.
1763
        """
1764
1765
        # We care if panels are suddenly showing.  The reason for this
1766
        # is that some applications, such as Evolution, will bring up
1767
        # a wizard dialog that uses "Forward" and "Backward" buttons
1768
        # that change the contents of the dialog.  We only discover
1769
        # this through showing events. [[[TODO: WDW - perhaps what we
1770
        # really want is to speak unbound labels that are suddenly
1771
        # showing?  event.detail == 1 means object is showing.]]]
1772
        #
1773
        # [[[TODO: WDW - I added the 'False' condition to prevent this
1774
        # condition from ever working.  I wanted to keep the code around,
1775
        # though, just in case we want to reuse it somewhere else.  The
1776
        # bug that spurred all of this on is:
1777
        #
1778
        #    http://bugzilla.gnome.org/show_bug.cgi?id=338687
1779
        #
1780
        # The main problem is that the profile editor in gnome-terminal
1781
        # ended up being very verbose and speaking lots of things it
1782
        # should not have been speaking.]]]
1783
        #
1784 685
        if False and (obj.role == rolenames.ROLE_PANEL) \
1785
               and (event.detail1 == 1) \
1786
               and self.isInActiveApp(obj):
1787
1788
            # It's only showing if its parent is showing. [[[TODO: WDW -
1789
            # HACK we stop at the application level because applications
1790
            # never seem to have their showing state set.]]]
1791
            #
1792 0
            reallyShowing = True
1793 0
            parent = obj.parent
1794 0
            while reallyShowing \
1795
                      and parent \
1796
                      and (parent != parent.parent) \
1797
                      and (parent.role != rolenames.ROLE_APPLICATION):
1798 0
                debug.println(debug.LEVEL_FINEST,
1799 0
                              "default.visualAppearanceChanged - " \
1800
                              + "checking parent")
1801 0
                reallyShowing = parent.state.count(\
1802
                    atspi.Accessibility.STATE_SHOWING)
1803 0
                parent = parent.parent
1804
1805
            # Find all the unrelated labels in the dialog and speak them.
1806
            #
1807 0
            if reallyShowing:
1808 0
                utterances = []
1809 0
                labels = self.findUnrelatedLabels(obj)
1810 0
                for label in labels:
1811 0
                    utterances.append(label.name)
1812
1813 0
                speech.speakUtterances(utterances)
1814
1815 0
                return
1816
1817
        # If this object is CONTROLLED_BY the object that currently
1818
        # has focus, speak/braille this object.
1819
        #
1820 685
        relations = obj.relations
1821 826
        for relation in relations:
1822 147
            if relation.getRelationType() \
1823
                   == atspi.Accessibility.RELATION_CONTROLLED_BY:
1824 0
                target = atspi.Accessible.makeAccessible(relation.getTarget(0))
1825 0
                if target == orca_state.locusOfFocus:
1826 0
                    self.updateBraille(target)
1827 0
                    speech.speakUtterances(
1828
                        self.speechGenerator.getSpeech(target, True))
1829 0
                    return
1830
1831
        # If this object is a label, and if it has a LABEL_FOR relation
1832
        # to the focused object, then we should speak/braille the
1833
        # focused object, as if it had just got focus.
1834
        #
1835 679
        if obj.role == rolenames.ROLE_LABEL:
1836 400
            for relation in relations:
1837 117
                if relation.getRelationType() \
1838
                       == atspi.Accessibility.RELATION_LABEL_FOR:
1839 117
                    target = \
1840
                        atspi.Accessible.makeAccessible(relation.getTarget(0))
1841 117
                    if target == orca_state.locusOfFocus:
1842 0
                        self.updateBraille(target)
1843 0
                        speech.speakUtterances(
1844
                            self.speechGenerator.getSpeech(target, True))
1845 0
                        return
1846
1847 679
        if obj != orca_state.locusOfFocus:
1848 660
            return
1849
1850 19
        if event:
1851 19
            debug.println(debug.LEVEL_FINE,
1852 19
                          "VISUAL CHANGE: '%s' '%s' (event='%s')" \
1853
                          % (obj.name, obj.role, event.type))
1854
        else:
1855 0
            debug.println(debug.LEVEL_FINE,
1856 0
                          "VISUAL CHANGE: '%s' '%s' (event=None)" \
1857
                          % (obj.name, obj.role))
1858
1859 19
        mag.magnifyAccessible(event, obj)
1860 19
        self.updateBraille(obj)
1861 19
        speech.speakUtterances(self.speechGenerator.getSpeech(obj, True))
1862
1863 1
    def updateBraille(self, obj, extraRegion=None):
1864
        """Updates the braille display to show the give object.
1865
1866
        Arguments:
1867
        - obj: the Accessible
1868
        - extra: extra Region to add to the end
1869
        """
1870
1871 1710
        if not obj:
1872 0
            return
1873
1874 1710
        braille.clear()
1875
1876 1710
        line = braille.Line()
1877 1710
        braille.addLine(line)
1878
1879
        # For multiline text areas, we only show the context if we
1880
        # are on the very first line.  Otherwise, we show only the
1881
        # line.
1882
        #
1883 1710
        if obj.text and self.isTextArea(obj):
1884 747
            text = obj.text
1885 747
            [string, startOffset, endOffset] = text.getTextAtOffset(
1886
                text.caretOffset,
1887 747
                atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
1888 747
            if startOffset == 0:
1889 360
                line.addRegions(self.brailleGenerator.getBrailleContext(obj))
1890
        else:
1891 957
            line.addRegions(self.brailleGenerator.getBrailleContext(obj))
1892
1893 1703
        result = self.brailleGenerator.getBrailleRegions(obj)
1894 1691
        line.addRegions(result[0])
1895
1896 1691
        if extraRegion:
1897 0
            line.addRegion(extraRegion)
1898
1899 1691
        if extraRegion:
1900 0
            braille.setFocus(extraRegion)
1901
        else:
1902 1691
            braille.setFocus(result[1])
1903
1904 1691
        braille.refresh(True)
1905
1906
    ########################################################################
1907
    #                                                                      #
1908
    # AT-SPI OBJECT EVENT HANDLERS                                         #
1909
    #                                                                      #
1910
    ########################################################################
1911
1912 1
    def onFocus(self, event):
1913
        """Called whenever an object gets focus.
1914
1915
        Arguments:
1916
        - event: the Event
1917
        """
1918
1919
        # [[[TODO: WDW - HACK to deal with quirky GTK+ menu behavior.
1920
        # The problem is that when moving to submenus in a menu, the
1921
        # menu gets focus first and then the submenu gets focus all
1922
        # with a single keystroke.  So...focus in menus really means
1923
        # that the object has focus *and* it is selected.  Now, this
1924
        # assumes the selected state will be set before focus is given,
1925
        # which appears to be the case from empirical analysis of the
1926
        # event stream.  But of course, all menu items and menus in
1927
        # the complete menu path will have their selected state set,
1928
        # so, we really only care about the leaf menu or menu item
1929
        # that it selected.]]]
1930
        #
1931 865
        role = event.source.role
1932 865
        if (role == rolenames.ROLE_MENU) \
1933
           or (role == rolenames.ROLE_MENU_ITEM) \
1934
           or (role == rolenames.ROLE_CHECK_MENU_ITEM) \
1935
           or (role == rolenames.ROLE_RADIO_MENU_ITEM):
1936 243
            selection = event.source.selection
1937 243
            if selection and selection.nSelectedChildren > 0:
1938 24
                return
1939
1940
        # [[[TODO: WDW - HACK to deal with the fact that active cells
1941
        # may or may not get focus.  Their parents, however, do tend to
1942
        # get focus, but when the parent gets focus, it really means
1943
        # that the selected child in it has focus.  Of course, this all
1944
        # breaks when more than one child is selected.  Then, we really
1945
        # need to depend upon the model where focus really works.]]]
1946
        #
1947 841
        newFocus = event.source
1948
1949 841
        if (event.source.role == rolenames.ROLE_LAYERED_PANE) \
1950
            or (event.source.role == rolenames.ROLE_TABLE) \
1951
            or (event.source.role == rolenames.ROLE_TREE_TABLE) \
1952
            or (event.source.role == rolenames.ROLE_TREE):
1953 43
            if event.source.childCount:
1954
                # Well...we'll first see if there is a selection.  If there
1955
                # is, we'll use it.
1956
                #
1957 29
                selection = event.source.selection
1958 29
                if selection and selection.nSelectedChildren > 0:
1959 23
                    newFocus = atspi.Accessible.makeAccessible(
1960
                        selection.getSelectedChild(0))
1961
1962
                # Otherwise, we might have tucked away some information
1963
                # for this thing in the onActiveDescendantChanged method.
1964
                #
1965 6
                elif event.source.__dict__.has_key("activeDescendantInfo"):
1966 0
                    [parent, index] = event.source.activeDescendantInfo
1967 0
                    newFocus = parent.child(index)
1968
1969 830
        orca.setLocusOfFocus(event, newFocus)
1970
1971 1
    def onNameChanged(self, event):
1972
        """Called whenever a property on an object changes.
1973
1974
        Arguments:
1975
        - event: the Event
1976
        """
1977
1978
        # [[[TODO: WDW - HACK because gnome-terminal issues a name changed
1979
        # event for the edit preferences dialog even though the name really
1980
        # didn't change.  I'm guessing this is going to be a vagary in all
1981
        # of GTK+.]]]
1982
        #
1983 528
        if event.source and (event.source.role == rolenames.ROLE_DIALOG) \
1984
           and (event.source == orca_state.locusOfFocus):
1985 0
            return
1986
1987
        # We do this because we can get name change events even if the
1988
        # name doesn't change.  [[[TODO: WDW - I'm hesitant to rip the
1989
        # above TODO out, though, because it's been in here for so long.]]]
1990
        #
1991 528
        try:
1992 528
            if event.source.oldName == event.source.name:
1993 356
                return
1994 62
        except:
1995 62
            pass
1996
1997 172
        event.source.oldName = event.source.name
1998 172
        orca.visualAppearanceChanged(event, event.source)
1999
2000 1
    def _presentTextAtNewCaretPosition(self, event):
2001
2002 787
        if event.source:
2003 787
            mag.magnifyAccessible(event, event.source)
2004
2005
        # Update the Braille display - if we can just reposition
2006
        # the cursor, then go for it.
2007
        #
2008 787
        brailleNeedsRepainting = True
2009 787
        line = braille.getShowingLine()
2010 4747
        for region in line.regions:
2011 4720
            if isinstance(region, braille.Text) \
2012
               and (region.accessible == event.source):
2013 760
                if region.repositionCursor():
2014 696
                    braille.refresh(True)
2015 696
                    brailleNeedsRepainting = False
2016 760
                break
2017
2018 787
        if brailleNeedsRepainting:
2019 91
            self.updateBraille(event.source)
2020
2021 787
        if not orca_state.lastInputEvent:
2022 1
            return
2023
2024 786
        if isinstance(orca_state.lastInputEvent, input_event.MouseButtonEvent):
2025 0
            if not orca_state.lastInputEvent.pressed:
2026 0
                self.sayLine(event.source)
2027 0
            return
2028
2029
        # Guess why the caret moved and say something appropriate.
2030
        # [[[TODO: WDW - this motion assumes traditional GUI
2031
        # navigation gestures.  In an editor such as vi, line up and
2032
        # down is done via other actions such as "i" or "j".  We may
2033
        # need to think about this a little harder.]]]
2034
        #
2035 786
        if not isinstance(orca_state.lastInputEvent,
2036 786
                          input_event.KeyboardEvent):
2037 0
            return
2038
2039 786
        string = orca_state.lastInputEvent.event_string
2040 786
        mods = orca_state.lastInputEvent.modifiers
2041 786
        isControlKey = mods & (1 << atspi.Accessibility.MODIFIER_CONTROL)
2042 786
        isShiftKey = mods & (1 << atspi.Accessibility.MODIFIER_SHIFT)
2043 786
        hasLastPos = event.source.__dict__.has_key("lastCursorPosition")
2044
2045 786
        if (string == "Up") or (string == "Down"):
2046
            # If the user has typed Shift-Up or Shift-Down, then we want
2047
            # to speak the text that has just been selected or unselected,
2048
            # otherwise we speak the new line where the text cursor is
2049
            # currently positioned.
2050
            #
2051 44
            if hasLastPos and isShiftKey and not isControlKey:
2052 0
                self.sayPhrase(event.source, event.source.lastCursorPosition,
2053 0
                               event.source.text.caretOffset)
2054
            else:
2055 44
                self.sayLine(event.source)
2056
2057 742
        elif (string == "Left") or (string == "Right"):
2058
            # If the user has typed Control-Shift-Up or Control-Shift-Dowm,
2059
            # then we want to speak the text that has just been selected
2060
            # or unselected, otherwise if the user has typed Control-Left
2061
            # or Control-Right, we speak the current word otherwise we speak
2062
            # the character at the text cursor position.
2063
            #
2064 60
            if hasLastPos and isShiftKey and isControlKey:
2065 0
                self.sayPhrase(event.source, event.source.lastCursorPosition,
2066 0
                               event.source.text.caretOffset)
2067 60
            elif isControlKey:
2068 0
                self.sayWord(event.source)
2069
            else:
2070 60
                self.sayCharacter(event.source)
2071
2072 682
        elif string == "Page_Up":
2073
            # If the user has typed Control-Shift-Page_Up, then we want
2074
            # to speak the text that has just been selected or unselected,
2075
            # otherwise if the user has typed Control-Page_Up, then we
2076
            # speak the character to the right of the current text cursor
2077
            # position otherwise we speak the current line.
2078
            #
2079 0
            if hasLastPos and isShiftKey and isControlKey:
2080 0
                self.sayPhrase(event.source, event.source.lastCursorPosition,
2081 0
                               event.source.text.caretOffset)
2082 0
            elif isControlKey:
2083 0
                self.sayCharacter(event.source)
2084
            else:
2085 0
                self.sayLine(event.source)
2086
2087 682
        elif string == "Page_Down":
2088
            # If the user has typed Control-Shift-Page_Down, then we want
2089
            # to speak the text that has just been selected or unselected,
2090
            # otherwise if the user has just typed Page_Down, then we speak
2091
            # the current line.
2092
            #
2093 0
            if hasLastPos and isShiftKey and isControlKey:
2094 0
                self.sayPhrase(event.source, event.source.lastCursorPosition,
2095 0
                               event.source.text.caretOffset)
2096
            else:
2097 0
                self.sayLine(event.source)
2098
2099 682
        elif (string == "Home") or (string == "End"):
2100
            # If the user has typed Shift-Home or Shift-End, then we want
2101
            # to speak the text that has just been selected or unselected,
2102
            # otherwise if the user has typed Control-Home or Control-End,
2103
            # then we speak the current line otherwise we speak the character
2104
            # to the right of the current text cursor position.
2105
            #
2106 0
            if hasLastPos and isShiftKey and not isControlKey:
2107 0
                self.sayPhrase(event.source, event.source.lastCursorPosition,
2108 0
                               event.source.text.caretOffset)
2109 0
            elif isControlKey:
2110 0
                self.sayLine(event.source)
2111
            else:
2112 0
                self.sayCharacter(event.source)
2113
2114 682
        elif (string == "A") and isControlKey:
2115
            # The user has typed Control-A. Check to see if the entire
2116
            # document has been selected, and if so, let the user know.
2117
            #
2118 0
            text = event.source.text
2119 0
            charCount = text.characterCount
2120 0
            for i in range(0, text.getNSelections()):
2121 0
                [startSelOffset, endSelOffset] = text.getSelection(i)
2122 0
                if text.caretOffset == 0 and \
2123
                   startSelOffset == 0 and endSelOffset == charCount:
2124 0
                    speech.speak(_("entire document selected"))
2125
2126 1
    def onCaretMoved(self, event):
2127
        """Called whenever the caret moves.
2128
2129
        Arguments:
2130
        - event: the Event
2131
        """
2132
2133
        # We don't always get focus: events for text areas, so if we
2134
        # see caret moved events for a focused text area, we silently
2135
        # set them to be the locus of focus.
2136
        #
2137 787
        if event and event.source and \
2138
           (event.source != orca_state.locusOfFocus) and \
2139
            event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
2140 0
            orca.setLocusOfFocus(event, event.source, False)
2141
2142
        # Ignore caret movements from non-focused objects, unless the
2143
        # currently focused object is the parent of the object which
2144
        # has the caret.
2145
        #
2146 787
        if (event.source != orca_state.locusOfFocus) \
2147
            and (event.source.parent != orca_state.locusOfFocus):
2148 0
            return
2149
2150
        # We always automatically go back to focus tracking mode when
2151
        # the caret moves in the focused object.
2152
        #
2153 787
        if self.flatReviewContext:
2154 0
            self.toggleFlatReviewMode()
2155
2156 787
        self._presentTextAtNewCaretPosition(event)
2157
2158 1
    def onTextDeleted(self, event):
2159
        """Called whenever text is deleted from an object.
2160
2161
        Arguments:
2162
        - event: the Event
2163
        """
2164
2165
        # We don't always get focus: events for text areas, so if we
2166
        # see deleted text events for a focused text area, we silently
2167
        # set them to be the locus of focus..
2168
        #
2169 37
        if event and event.source and \
2170
           (event.source != orca_state.locusOfFocus) and \
2171
            event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
2172 0
            orca.setLocusOfFocus(event, event.source, False)
2173
2174
        # Ignore text deletions from non-focused objects, unless the
2175
        # currently focused object is the parent of the object from which
2176
        # text was deleted
2177
        #
2178 37
        if (event.source != orca_state.locusOfFocus) \
2179
            and (event.source.parent != orca_state.locusOfFocus):
2180 31
            return
2181
2182
        # We'll also ignore sliders because we get their output via
2183
        # their values changing.
2184
        #
2185 6
        if event.source.role == rolenames.ROLE_SLIDER:
2186 0
            return
2187
2188 6
        self.updateBraille(event.source)
2189
2190
        # The any_data member of the event object has the deleted text in
2191
        # it - If the last key pressed was a backspace or delete key,
2192
        # speak the deleted text.  [[[TODO: WDW - again, need to think
2193
        # about the ramifications of this when it comes to editors such
2194
        # as vi or emacs.
2195
        #
2196 6
        if (not orca_state.lastInputEvent) \
2197
            or \
2198
            (not isinstance(orca_state.lastInputEvent,
2199 6
                            input_event.KeyboardEvent)):
2200 0
            return
2201
2202 6
        string = orca_state.lastInputEvent.event_string
2203 6
        text = event.source.text
2204 6
        if string == "BackSpace":
2205
            # Speak the character that has just been deleted.
2206
            #
2207 0
            character = event.any_data
2208
2209 6
        elif string == "Delete":
2210
            # Speak the character to the right of the caret after
2211
            # the current right character has been deleted.
2212
            #
2213 0
            offset = text.caretOffset
2214 0
            [character, startOffset, endOffset] = \
2215
                event.source.text.getTextAtOffset(
2216
                    offset,
2217 0
                    atspi.Accessibility.TEXT_BOUNDARY_CHAR)
2218
2219
        else:
2220 6
            return
2221
2222 0
        if self.getLinkIndex(event.source, text.caretOffset) >= 0:
2223 0
            voice = self.voices[settings.HYPERLINK_VOICE]
2224 0
        elif character.isupper():
2225 0
            voice = self.voices[settings.UPPERCASE_VOICE]
2226
        else:
2227 0
            voice = self.voices[settings.DEFAULT_VOICE]
2228
2229
        # We won't interrupt what else might be being spoken
2230
        # right now because it is typically something else
2231
        # related to this event.
2232
        #
2233 0
        speech.speak(character, voice, False)
2234
2235 1
    def onTextInserted(self, event):
2236
        """Called whenever text is inserted into an object.
2237
2238
        Arguments:
2239
        - event: the Event
2240
        """
2241
2242
        # We don't always get focus: events for text areas, so if we
2243
        # see inserted text events for a focused text area, we silently
2244
        # set them to be the locus of focus..
2245
        #
2246 671
        if event and event.source and \
2247
           (event.source != orca_state.locusOfFocus) and \
2248
            event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
2249 3
            orca.setLocusOfFocus(event, event.source, False)
2250
2251
        # Ignore text insertions from non-focused objects, unless the
2252
        # currently focused object is the parent of the object from which
2253
        # text was inserted.
2254
        #
2255 671
        if (event.source != orca_state.locusOfFocus) \
2256
            and (event.source.parent != orca_state.locusOfFocus):
2257 43
            return
2258
2259
        # We'll also ignore sliders because we get their output via
2260
        # their values changing.
2261
        #
2262 628
        if event.source.role == rolenames.ROLE_SLIDER:
2263 0
            return
2264
2265 628
        self.updateBraille(event.source)
2266
2267 628
        text = event.any_data
2268
2269
        # If this is a spin button, then speak the text and return.
2270
        #
2271 628
        if event.source.role == rolenames.ROLE_SPIN_BUTTON:
2272 0
            speech.speak(text)
2273 0
            return
2274
2275
        # If the last input event was a keyboard event, check to see if
2276
        # the text for this event matches what the user typed. If it does,
2277
        # then don't speak it.
2278
        #
2279
        # Note that the text widgets sometimes compress their events,
2280
        # thus we might get a longer string from a single text inserted
2281
        # event, while we also get individual keyboard events for the
2282
        # characters used to type the string.  This is ugly.  We attempt
2283
        # to handle it here by only echoing text if we think it was the
2284
        # result of a command (e.g., a paste operation).
2285
        #
2286
        # Note that we have to special case the space character as it
2287
        # comes across as "space" in the keyboard event and " " in the
2288
        # text event.
2289
        #
2290 628
        if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
2291 628
            keyString = orca_state.lastInputEvent.event_string
2292 628
            wasAutoComplete = (event.source.role == rolenames.ROLE_TEXT and \
2293
                               event.source.text.getNSelections())
2294 628
            wasCommand = orca_state.lastInputEvent.modifiers \
2295
                         & (1 << atspi.Accessibility.MODIFIER_CONTROL \
2296
                            | 1 << atspi.Accessibility.MODIFIER_ALT \
2297
                            | 1 << atspi.Accessibility.MODIFIER_META \
2298
                            | 1 << atspi.Accessibility.MODIFIER_META2 \
2299
                            | 1 << atspi.Accessibility.MODIFIER_META3)
2300 628
            if (text == " " and keyString == "space") \
2301
                or (text == keyString):
2302 537
                pass
2303 91
            elif wasCommand or wasAutoComplete or \
2304
                   (event.source.role == rolenames.ROLE_PASSWORD_TEXT):
2305 3
                if text.isupper():
2306 0
                    speech.speak(text, self.voices[settings.UPPERCASE_VOICE])
2307
                else:
2308 3
                    speech.speak(text)
2309
2310 628
        if settings.enableEchoByWord \
2311
           and self.isWordDelimiter(text.decode("UTF-8")[-1:]):
2312 0
            self.echoPreviousWord(event.source)
2313
2314 1
    def onActiveDescendantChanged(self, event):
2315
        """Called when an object who manages its own descendants detects a
2316
        change in one of its children.
2317
2318
        Arguments:
2319
        - event: the Event
2320
        """
2321
2322 162
        if not event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
2323 2
            return
2324
2325
2326
        # There can be cases when the object that fires an
2327
        # active-descendant-changed event has no children. In this case,
2328
        # use the object that fired the event, otherwise, use the child.
2329
        #
2330 160
        child = event.any_data
2331 160
        if child:
2332 160
            orca.setLocusOfFocus(event, child)
2333
        else:
2334 0
            orca.setLocusOfFocus(event, event.source)
2335
2336
        # We'll tuck away the activeDescendant information for future
2337
        # reference since the AT-SPI gives us little help in finding
2338
        # this.
2339
        #
2340 149
        if orca_state.locusOfFocus \
2341
           and (orca_state.locusOfFocus != event.source):
2342 149
            event.source.activeDescendantInfo = \
2343
                [orca_state.locusOfFocus.parent,
2344
                 orca_state.locusOfFocus.index]
2345 0
        elif event.source.__dict__.has_key("activeDescendantInfo"):
2346 0
            del event.source.__dict__["activeDescendantInfo"]
2347
2348 1
    def onLinkSelected(self, event):
2349
        """Called when a hyperlink is selected in a text area.
2350
2351
        Arguments:
2352
        - event: the Event
2353
        """
2354
2355
        # [[[TODO: WDW - HACK one might think we could expect an
2356
        # application to keep its name, but it appears as though
2357
        # yelp has an identity problem and likes to start calling
2358
        # itself "yelp," but then changes its name to "Mozilla"
2359
        # on Fedora Core 4 after the user selects a link.  So, we'll
2360
        # just assume that link-selected events always come from the
2361
        # application with focus.]]]
2362
        #
2363
        #if orca_state.locusOfFocus \
2364
        #   and (orca_state.locusOfFocus.app == event.source.app):
2365
        #    orca.setLocusOfFocus(event, event.source)
2366 0
        orca.setLocusOfFocus(event, event.source)
2367
2368
2369 1
    def onStateChanged(self, event):
2370
        """Called whenever an object's state changes.
2371
2372
        Arguments:
2373
        - event: the Event
2374
        """
2375
2376
        # Do we care?
2377
        #
2378 4084
        if event.type == "object:state-changed:active":
2379 147
            if self.findCommandRun:
2380 0
                self.findCommandRun = False
2381 0
                self.find()
2382 0
                return
2383
2384 4084
        if event.type == "object:state-changed:focused":
2385 627
            iconified = False
2386 627
            try:
2387 627
                window = self.getTopLevel(event.source)
2388 621
                iconified = window.state.count( \
2389
                                     atspi.Accessibility.STATE_ICONIFIED)
2390 22
            except:
2391 22
                debug.println(debug.LEVEL_FINEST,
2392 22
                        "onStateChanged: could not get frame of focused item")
2393 627
            if not iconified:
2394 627
                if event.detail1:
2395 270
                    self.onFocus(event)
2396
                # We don't set locus of focus of None here because it
2397
                # wreaks havoc on the code that determines the context
2398
                # when you tab from widget to widget.  For example,
2399
                # tabbing between panels in the gtk-demo buttons demo.
2400
                #
2401
                #else:
2402
                #    orca.setLocusOfFocus(event, None)
2403 621
                return
2404
2405 3457
        if state_change_notifiers.has_key(event.source.role):
2406 1351
            notifiers = state_change_notifiers[event.source.role]
2407 1351
            found = False
2408 3486
            for state in notifiers:
2409 2604
                if state and event.type.endswith(state):
2410 469
                    found = True
2411 469
                    break
2412 1351
            if found:
2413 469
                orca.visualAppearanceChanged(event, event.source)
2414
2415
        # [[[TODO: WDW - HACK we'll handle this in the visual appearance
2416
        # changed handler.]]]
2417
        #
2418
        # The object with focus might become insensitive, so we need to
2419
        # flag that.  This typically occurs in wizard dialogs such as
2420
        # the account setup assistant in Evolution.
2421
        #
2422
        #if event.type.endswith("sensitive") \
2423
        #   and (event.detail1 == 0) \
2424
        #   and event.source == orca_state.locusOfFocus:
2425
        #    print "FOO INSENSITIVE"
2426
        #    #orca.setLocusOfFocus(event, None)
2427
2428 1
    def onSelectionChanged(self, event):
2429
        """Called when an object's selection changes.
2430
2431
        Arguments:
2432
        - event: the Event
2433
        """
2434
2435 395
        if not event or not event.source:
2436 0
            return
2437
2438
        # Avoid doing this with objects that manage their descendants
2439
        # because they'll issue a descendant changed event.
2440
        #
2441 395
        if event.source.state.count(
2442
                 atspi.Accessibility.STATE_MANAGES_DESCENDANTS):
2443 143
            return
2444
2445 252
        if event.source.role == rolenames.ROLE_COMBO_BOX:
2446 31
            orca.visualAppearanceChanged(event, event.source)
2447
2448
        # We treat selected children as the locus of focus. When the
2449
        # selection changed we want to update the locus of focus. If
2450
        # there is no selection, we default the locus of focus to the
2451
        # containing object.
2452
        #
2453 221
        elif (event.source != orca_state.locusOfFocus) and \
2454
            event.source.state.count(atspi.Accessibility.STATE_FOCUSED):
2455 41
            newFocus = event.source
2456 41
            if event.source.childCount:
2457 41
                selection = event.source.selection
2458 41
                if selection and selection.nSelectedChildren > 0:
2459 35
                    child = selection.getSelectedChild(0)
2460 35
                    if child:
2461 35
                        newFocus = atspi.Accessible.makeAccessible(child)
2462
2463 41
            orca.setLocusOfFocus(event, newFocus)
2464
2465 1
    def onValueChanged(self, event):
2466
        """Called whenever an object's value changes.  Currently, the
2467
        value changes for non-focused objects are ignored.
2468
2469
        Arguments:
2470
        - event: the Event
2471
        """
2472
2473
        # We'll let caret moved and text inserted events be used to
2474
        # manage spin buttons, since they basically are text areas.
2475
        #
2476 776
        if event.source.role == rolenames.ROLE_SPIN_BUTTON:
2477 12
            return
2478
2479
        # We'll also try to ignore those objects that keep telling
2480
        # us their value changed even though it hasn't.
2481
        #
2482 764
        if event.source.value and event.source.__dict__.has_key("oldValue") \
2483
           and (event.source.value.currentValue == event.source.oldValue):
2484 751
            return
2485
2486 13
        orca.visualAppearanceChanged(event, event.source)
2487 13
        event.source.oldValue = event.source.value.currentValue
2488
2489 1
    def onWindowActivated(self, event):
2490
        """Called whenever a toplevel window is activated.
2491
2492
        Arguments:
2493
        - event: the Event
2494
        """
2495
2496 194
        self.windowActivateTime = time.time()
2497 194
        orca.setLocusOfFocus(event, event.source)
2498
2499
        # We keep track of the active window to handle situations where
2500
        # we get window activated and window deactivated events out of
2501
        # order (see onWindowDeactivated).
2502
        #
2503
        # For example, events can be:
2504
        #
2505
        #    window:activate   (w1)
2506
        #    window:activate   (w2)
2507
        #    window:deactivate (w1)
2508
        #
2509
        # as well as:
2510
        #
2511
        #    window:activate   (w1)
2512
        #    window:deactivate (w1)
2513
        #    window:activate   (w2)
2514
        #
2515 62
        orca_state.activeWindow = event.source
2516
2517 1
    def onWindowDeactivated(self, event):
2518
        """Called whenever a toplevel window is deactivated.
2519
2520
        Arguments:
2521
        - event: the Event
2522
        """
2523
2524
        # If we receive a "window:deactivate" event for the object that
2525
        # currently has focus, then stop the current speech output.
2526
        # This is very useful for terminating long speech output from
2527
        # commands running in gnome-terminal.
2528
        #
2529 54
        if orca_state.locusOfFocus and \
2530
          (orca_state.locusOfFocus.app == event.source.app):
2531 43
            speech.stop()
2532
2533
        # Because window activated and deactivated events may be
2534
        # received in any order when switching from one application to
2535
        # another, locusOfFocus and activeWindow, we really only change
2536
        # the locusOfFocus and activeWindow when we are dealing with
2537
        # an event from the current activeWindow.
2538
        #
2539 54
        if event.source == orca_state.activeWindow:
2540 32
            orca.setLocusOfFocus(event, None)
2541 32
            orca_state.activeWindow = None
2542
2543 1
    def noOp(self, event):
2544
        """Just here to capture events.
2545
2546
        Arguments:
2547
        - event: the Event
2548 13253
        """
2549
        pass
2550
2551
    ########################################################################
2552
    #                                                                      #
2553
    # Utilities                                                            #
2554
    #                                                                      #
2555
    ########################################################################
2556
2557 1
    def isLayoutOnly(self, obj):
2558
        """Returns True if the given object is a table and is for layout
2559
        purposes only."""
2560
2561 6018
        layoutOnly = False
2562
2563 6018
        if obj and (obj.role == rolenames.ROLE_TABLE) and obj.attributes:
2564 0
            for attribute in obj.attributes:
2565 0
                if attribute == "layout-guess:true":
2566 0
                    layoutOnly = True
2567 0
                    break
2568 6018
        elif obj and (obj.role == rolenames.ROLE_PANEL):
2569 842
            text = self.getDisplayedText(obj)
2570 842
            label = self.getDisplayedLabel(obj)
2571 842
            if not ((label and len(label)) or (text and len(text))):
2572 683
                layoutOnly = True
2573
2574 6018
        if layoutOnly:
2575 683
            debug.println(debug.LEVEL_FINEST,
2576 683
                          "Object deemed to be for layout purposes only: " \
2577
                          + obj.toString("", True))
2578
2579 6018
        return layoutOnly
2580
2581 1
    def toggleTableCellReadMode(self, inputEvent=None):
2582
        """Toggles an indicator for whether we should just read the current
2583
        table cell or read the whole row."""
2584
2585 0
        line = _("Speak ")
2586 0
        settings.readTableCellRow = not settings.readTableCellRow
2587 0
        if settings.readTableCellRow:
2588 0
            line += _("row")
2589
        else:
2590 0
            line += _("cell")
2591
2592 0
        speech.speak(line)
2593
2594 0
        return True
2595
2596 1
    def textAttrsToDictionary(self, str):
2597
        """Converts a string of text attribute tokens of the form
2598
        <key>:<value>; into a dictionary of keys and values.
2599
        Text before the colon is the key and text afterwards is the
2600
        value. If there is a final semi-colon, then it's ignored.
2601
2602
        Arguments:
2603
        - str: the string of tokens containing <key>:<value>; pairs.
2604
2605
        Returns a list containing two items:
2606
        A list of the keys in the order they were extracted from the
2607
        text attribute string and a dictionary of key/value items.
2608
        """
2609
2610 0
        list = []
2611 0
        dictionary = {}
2612 0
        allTokens = str.split(";")
2613 0
        for i in range(0, len(allTokens)):
2614 0
            item = allTokens[i].split(":")
2615 0
            if len(item) == 2:
2616 0
                key = item[0].strip()
2617 0
                attribute = item[1].strip()
2618 0
                list.append(key)
2619 0
                dictionary[key] = attribute
2620
2621 0
        return [list, dictionary]
2622
2623 1
    def outputCharAttributes(self, keys, attributes):
2624
        """Speak each of the text attributes given dictionary.
2625
2626
        Arguments:
2627
        - attributes: a dictionary of text attributes to speak.
2628
        """
2629
2630 0
        for i in range(0, len(keys)):
2631 0
            key = keys[i]
2632 0
            if attributes.has_key(key):
2633 0
                attribute = attributes[key]
2634 0
                if attribute:
2635
                    # If it's the 'weight' attribute and greater than 400, just
2636
                    # speak it as bold, otherwise speak the weight.
2637
                    #
2638 0
                    if key == "weight" and int(attribute) > 400:
2639 0
                        line = _("bold")
2640 0
                    elif key == "left-margin" or key == "right-margin":
2641 0
                        line = key + " " + attribute + _(" pixels")
2642
                    else:
2643 0
                        line = key + " " + attribute
2644 0
                    speech.speak(line)
2645
2646 1
    def readCharAttributes(self, inputEvent=None):
2647
        """Reads the attributes associated with the current text character.
2648
        Calls outCharAttributes to speak a list of attributes. By default,
2649
        a certain set of attributes will be spoken. If this is not desired,
2650
        then individual application scripts should override this method to
2651
        only speak the subset required.
2652
        """
2653
2654 0
        if orca_state.locusOfFocus and orca_state.locusOfFocus.text:
2655 0
            caretOffset = orca_state.locusOfFocus.text.caretOffset
2656 0
            text = orca_state.locusOfFocus.text
2657
2658
            # Creates dictionaries of the default attributes, plus the set
2659
            # of attributes specific to the character at the caret offset.
2660
            # Combine these two dictionaries and then extract just the
2661
            # entries we are interested in.
2662
            #
2663 0
            defAttributes = text.getDefaultAttributes()
2664 0
            debug.println(debug.LEVEL_FINEST, \
2665 0
                "readCharAttributes: default text attributes: %s" % \
2666
                defAttributes)
2667 0
            [defUser, defDict] = self.textAttrsToDictionary(defAttributes)
2668 0
            allAttributes = defDict
2669
2670 0
            charAttributes = text.getAttributes(caretOffset)
2671 0
            if charAttributes[0]:
2672 0
                [charList, charDict] = \
2673
                    self.textAttrsToDictionary(charAttributes[0])
2674
2675
                # It looks like some applications like Evolution and Star
2676
                # Office don't implement getDefaultAttributes(). In that
2677
                # case, the best we can do is use the specific text
2678
                # attributes for this character returned by getAttributes().
2679
                #
2680 0
                if allAttributes:
2681 0
                    for key in charDict.keys():
2682 0
                        allAttributes[key] = charDict[key]
2683
                else:
2684 0
                    allAttributes = charDict
2685
2686
            # Get a dictionary of text attributes that the user cares about.
2687
            #
2688 0
            [userAttrList, userAttrDict] = \
2689
                self.textAttrsToDictionary(settings.enabledTextAttributes)
2690
2691
            # Create a dictionary of just the items we are interested in.
2692
            # Always include size and family-name. For the others, if the
2693
            # value is the default, then ignore it.
2694
            #
2695 0
            attributes = {}
2696 0
            for i in range(0, len(userAttrList)):
2697 0
                key = userAttrList[i]
2698 0
                if allAttributes.has_key(key):
2699 0
                    textAttr = allAttributes.get(key)
2700 0
                    userAttr = userAttrDict.get(key)
2701 0
                    if textAttr != userAttr or len(userAttr) == 0:
2702 0
                        attributes[key] = textAttr
2703
2704 0
            self.outputCharAttributes(userAttrList, attributes)
2705
2706 0
        return True
2707
2708 1
    def reportScriptInfo(self, inputEvent=None):
2709
        """Output useful information on the current script via speech
2710
        and braille.  This information will be helpful to script writers.
2711
        """
2712
2713 0
        string = "SCRIPT INFO: Script name='%s'" % self.name
2714 0
        if orca_state.locusOfFocus and orca_state.locusOfFocus.app:
2715
2716 0
            string += " Application name='%s'" \
2717
                      % orca_state.locusOfFocus.app.name
2718
2719 0
            try:
2720 0
                string += " Toolkit name='%s'" \
2721
                          % orca_state.locusOfFocus.app.toolkitName
2722 0
            except:
2723 0
                string += " Toolkit unknown"
2724
2725 0
            try:
2726 0
                string += " Version='%s'" \
2727
                          % orca_state.locusOfFocus.app.version
2728 0
            except:
2729 0
                string += " Version unknown"
2730
2731 0
            debug.println(debug.LEVEL_INFO, string)
2732 0
            speech.speak(string)
2733 0
            braille.displayMessage(string)
2734
2735 0
        return True
2736
2737 1
    def enterLearnMode(self, inputEvent=None):
2738
        """Turns learn mode on.  The user must press the escape key to exit
2739
        learn mode.
2740
2741
        Returns True to indicate the input event has been consumed.
2742
        """
2743
2744 0
        if settings.learnModeEnabled:
2745 0
            return True
2746
2747 0
        self.exitLearnModeKeyBinding = keybindings.KeyBinding(
2748
            "Escape",
2749 0
            0,
2750 0
            0,
2751 0
            self.inputEventHandlers["exitLearnModeHandler"])
2752 0
        self.keyBindings.add(self.exitLearnModeKeyBinding)
2753
2754 0
        speech.speak(
2755
            _("Entering learn mode.  Press any key to hear its function. " \
2756
              + "To exit learn mode, press the escape key."))
2757 0
        braille.displayMessage(_("Learn mode.  Press escape to exit."))
2758 0
        settings.learnModeEnabled = True
2759 0
        return True
2760
2761 1
    def exitLearnMode(self, inputEvent=None):
2762
        """Turns learn mode off.
2763
2764
        Returns True to indicate the input event has been consumed.
2765
        """
2766
2767 0
        self.keyBindings.remove(self.exitLearnModeKeyBinding)
2768
2769 0
        speech.speak(_("Exiting learn mode."))
2770 0
        braille.displayMessage(_("Exiting learn mode."))
2771 0
        self.whereAmI(None)
2772 0
        return True
2773
2774 1
    def pursueForFlatReview(self, obj):
2775
        """Determines if we should look any further at the object
2776
        for flat review."""
2777 498
        return obj.state.count(atspi.Accessibility.STATE_SHOWING)
2778
2779 1
    def getFlatReviewContext(self):
2780
        """Returns the flat review context, creating one if necessary."""
2781
2782 73
        if not self.flatReviewContext:
2783 1
            self.flatReviewContext = self.flatReviewContextClass(self)
2784 1
            self.justEnteredFlatReviewMode = True
2785
2786
            # Remember where the cursor currently was
2787
            # when the user was in focus tracking mode.  We'll try to
2788
            # keep the position the same as we move to characters above
2789
            # and below us.
2790
            #
2791 1
            self.targetCursorCell = braille.cursorCell
2792
2793 73
        return self.flatReviewContext
2794
2795 1
    def toggleFlatReviewMode(self, inputEvent=None):
2796
        """Toggles between flat review mode and focus tracking mode."""
2797
2798 0
        if self.flatReviewContext:
2799 0
            self.drawOutline(-1, 0, 0, 0)
2800 0
            self.flatReviewContext = None
2801 0
            self.updateBraille(orca_state.locusOfFocus)
2802
        else:
2803 0
            context = self.getFlatReviewContext()
2804 0
            [string, x, y, width, height] = \
2805
                     context.getCurrent(flat_review.Context.WORD)
2806 0
            self.drawOutline(x, y, width, height)
2807 0
            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
2808
2809 0
        return True
2810
2811 1
    def updateBrailleReview(self, targetCursorCell=0):
2812
        """Obtains the braille regions for the current flat review line
2813
        and displays them on the braille display.  If the targetCursorCell
2814
        is non-0, then an attempt will be made to postion the review cursor
2815
        at that cell.  Otherwise, we will pan in display-sized increments
2816
        to show the review cursor."""
2817
2818 23
        context = self.getFlatReviewContext()
2819
2820 23
        [regions, regionWithFocus] = context.getCurrentBrailleRegions()
2821 23
        if not regions:
2822 0
            regions = []
2823 0
            regionWithFocus = None
2824
2825 23
        line = braille.Line()
2826 23
        line.addRegions(regions)
2827 23
        braille.setLines([line])
2828 23
        braille.setFocus(regionWithFocus, False)
2829 23
        if regionWithFocus:
2830 23
            braille.panToOffset(regionWithFocus.brailleOffset \
2831
                                + regionWithFocus.cursorOffset)
2832
2833 23
        if self.justEnteredFlatReviewMode:
2834 1
            braille.refresh(True, self.targetCursorCell)
2835 1
            self.justEnteredFlatReviewMode = False
2836
        else:
2837 22
            braille.refresh(True, targetCursorCell)
2838
2839 1
    def _setFlatReviewContextToBeginningOfBrailleDisplay(self):
2840
        """Sets the character of interest to be the first character showing
2841
        at the beginning of the braille display."""
2842
2843 0
        context = self.getFlatReviewContext()
2844 0
        [regions, regionWithFocus] = context.getCurrentBrailleRegions()
2845 0
        for region in regions:
2846 0
            if ((region.brailleOffset + len(region.string.decode("UTF-8"))) \
2847
                   > braille._viewport[0]) \
2848
                and (isinstance(region, braille.ReviewText) \
2849
                     or isinstance(region, braille.ReviewComponent)):
2850 0
                position = max(region.brailleOffset, braille._viewport[0])
2851 0
                offset = position - region.brailleOffset
2852 0
                self.targetCursorCell = region.brailleOffset \
2853
                                        - braille._viewport[0]
2854 0
                [word, charOffset] = region.zone.getWordAtOffset(offset)
2855 0
                if word:
2856 0
                    self.flatReviewContext.setCurrent(
2857
                        word.zone.line.index,
2858 0
                        word.zone.index,
2859 0
                        word.index,
2860 0
                        charOffset)
2861
                else:
2862 0
                    self.flatReviewContext.setCurrent(
2863
                        region.zone.line.index,
2864 0
                        region.zone.index,
2865 0
                        0, # word index
2866 0
                        0) # character index
2867 0
                break
2868
2869 1
    def panBrailleLeft(self, inputEvent=None, panAmount=0):
2870
        """Pans the braille display to the left.  If panAmount is non-zero,
2871
        the display is panned by that many cells.  If it is 0, the display
2872
        is panned one full display width.  In flat review mode, panning
2873
        beyond the beginning will take you to the end of the previous line.
2874
2875
        In focus tracking mode, the cursor stays at its logical position.
2876
        In flat review mode, the review cursor moves to character
2877
        associated with cell 0."""
2878
2879 0
        if self.flatReviewContext:
2880 0
            if braille.beginningIsShowing:
2881 0
                self.flatReviewContext.goBegin(flat_review.Context.LINE)
2882 0
                self.reviewPreviousCharacter(inputEvent)
2883
            else:
2884 0
                braille.panLeft(panAmount)
2885
2886
            # This will update our target cursor cell
2887
            #
2888 0
            self._setFlatReviewContextToBeginningOfBrailleDisplay()
2889
2890 0
            [string, x, y, width, height] = \
2891
                self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
2892 0
            self.drawOutline(x, y, width, height)
2893
2894 0
            self.targetCursorCell = 1
2895 0
            self.updateBrailleReview(self.targetCursorCell)
2896 0
        elif braille.beginningIsShowing and orca_state.locusOfFocus \
2897
             and self.isTextArea(orca_state.locusOfFocus):
2898
            # If we're at the beginning of a line of a multiline text
2899
            # area, then force it's caret to the end of the previous
2900
            # line.  The assumption here is that we're currently
2901
            # viewing the line that has the caret -- which is a pretty
2902
            # good assumption for focus tacking mode.  When we set the
2903
            # caret position, we will get a caret event, which will
2904
            # then update the braille.
2905
            #
2906 0
            text = orca_state.locusOfFocus.text
2907 0
            [string, startOffset, endOffset] = text.getTextAtOffset(
2908
                text.caretOffset,
2909 0
                atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
2910 0
            if startOffset > 0:
2911 0
                text.setCaretOffset(startOffset - 1)
2912
        else:
2913 0
            braille.panLeft(panAmount)
2914 0
            braille.refresh(False)
2915
2916 0
        return True
2917
2918 1
    def panBrailleLeftOneChar(self, inputEvent=None):
2919
        """Nudges the braille display one character to the left.
2920
2921
        In focus tracking mode, the cursor stays at its logical position.
2922
        In flat review mode, the review cursor moves to character
2923
        associated with cell 0."""
2924
2925 0
        self.panBrailleLeft(inputEvent, 1)
2926
2927 1
    def panBrailleRight(self, inputEvent=None, panAmount=0):
2928
        """Pans the braille display to the right.  If panAmount is non-zero,
2929
        the display is panned by that many cells.  If it is 0, the display
2930
        is panned one full display width.  In flat review mode, panning
2931
        beyond the end will take you to the begininng of the next line.
2932
2933
        In focus tracking mode, the cursor stays at its logical position.
2934
        In flat review mode, the review cursor moves to character
2935
        associated with cell 0."""
2936
2937 0
        if self.flatReviewContext:
2938 0
            if braille.endIsShowing:
2939 0
                self.flatReviewContext.goEnd(flat_review.Context.LINE)
2940 0
                self.reviewNextCharacter(inputEvent)
2941
            else:
2942 0
                braille.panRight(panAmount)
2943
2944
            # This will update our target cursor cell
2945
            #
2946 0
            self._setFlatReviewContextToBeginningOfBrailleDisplay()
2947
2948 0
            [string, x, y, width, height] = \
2949
                self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
2950
2951 0
            self.drawOutline(x, y, width, height)
2952
2953 0
            self.targetCursorCell = 1
2954 0
            self.updateBrailleReview(self.targetCursorCell)
2955 0
        elif braille.endIsShowing and orca_state.locusOfFocus \
2956
             and self.isTextArea(orca_state.locusOfFocus):
2957
            # If we're at the end of a line of a multiline text area, then
2958
            # force it's caret to the beginning of the next line.  The
2959
            # assumption here is that we're currently viewing the line that
2960
            # has the caret -- which is a pretty good assumption for focus
2961
            # tacking mode.  When we set the caret position, we will get a
2962
            # caret event, which will then update the braille.
2963
            #
2964 0
            text = orca_state.locusOfFocus.text
2965 0
            [string, startOffset, endOffset] = text.getTextAtOffset(
2966
                text.caretOffset,
2967 0
                atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
2968 0
            if endOffset < text.characterCount:
2969 0
                text.setCaretOffset(endOffset)
2970
        else:
2971 0
            braille.panRight(panAmount)
2972 0
            braille.refresh(False)
2973
2974 0
        return True
2975
2976 1
    def panBrailleRightOneChar(self, inputEvent=None):
2977
        """Nudges the braille display one character to the right.
2978
2979
        In focus tracking mode, the cursor stays at its logical position.
2980
        In flat review mode, the review cursor moves to character
2981
        associated with cell 0."""
2982
2983 0
        self.panBrailleRight(inputEvent, 1)
2984
2985 1
    def goBrailleHome(self, inputEvent=None):
2986
        """Returns to the component with focus."""
2987
2988 0
        if self.flatReviewContext:
2989 0
            return self.toggleFlatReviewMode(inputEvent)
2990
        else:
2991 0
            return braille.returnToRegionWithFocus(inputEvent)
2992
2993 1
    def leftClickReviewItem(self, inputEvent=None):
2994
        """Performs a left mouse button click on the current item."""
2995
2996 0
        self.getFlatReviewContext().clickCurrent(1)
2997 0
        return True
2998
2999 1
    def rightClickReviewItem(self, inputEvent=None):
3000
        """Performs a right mouse button click on the current item."""
3001
3002 0
        self.getFlatReviewContext().clickCurrent(3)
3003 0
        return True
3004
3005 1
    def reviewCurrentLine(self, inputEvent):
3006
        """Presents the current flat review line via braille and speech."""
3007
3008 0
        clickCount = self.getClickCount(self.lastReviewCurrentEvent,
3009 0
                                           inputEvent)
3010 0
        self._reviewCurrentLine(inputEvent, clickCount)
3011 0
        self.lastReviewCurrentEvent = inputEvent
3012
3013 0
        return True
3014
3015 1
    def _reviewCurrentLine(self, inputEvent, clickCount=1):
3016
        """Presents the current flat review line via braille and speech.
3017
3018
        Arguments:
3019
        - inputEvent - the current input event.
3020
        - clickCount - number of times the user has "clicked" the current key.
3021
        """
3022
3023 11
        context = self.getFlatReviewContext()
3024
3025 11
        [string, x, y, width, height] = \
3026
                 context.getCurrent(flat_review.Context.LINE)
3027 11
        self.drawOutline(x, y, width, height)
3028
3029
        # Don't announce anything from speech if the user used
3030
        # the Braille display as an input device.
3031
        #
3032 11
        if not isinstance(inputEvent, input_event.BrailleEvent):
3033 11
            if (not string) or (not len(string)) or (string == "\n"):
3034 7
                speech.speak(_("blank"))
3035 4
            elif string.isspace():
3036 0
                speech.speak(_("white space"))
3037 4
            elif string.isupper() and (clickCount < 2 or clickCount > 3):
3038 0
                speech.speak(string, self.voices[settings.UPPERCASE_VOICE])
3039 4
            elif clickCount == 2:
3040 0
                self.spellCurrentItem(string)
3041 4
            elif clickCount == 3:
3042 0
                self.phoneticSpellCurrentItem(string)
3043
            else:
3044 4
                string = self.adjustForRepeats(string)
3045 4
                speech.speak(string)
3046
3047 11
        self.updateBrailleReview()
3048
3049 11
        return True
3050
3051 1
    def reviewPreviousLine(self, inputEvent):
3052
        """Moves the flat review context to the beginning of the
3053
        previous line."""
3054
3055 6
        context = self.getFlatReviewContext()
3056
3057 6
        moved = context.goPrevious(flat_review.Context.LINE,
3058 6
                                   flat_review.Context.WRAP_LINE)
3059
3060 6
        if moved:
3061 2
            self._reviewCurrentLine(inputEvent)
3062 2
            self.targetCursorCell = braille.cursorCell
3063
3064 6
        return True
3065
3066 1
    def reviewHome(self, inputEvent):
3067
        """Moves the flat review context to the top left of the current
3068
        window."""
3069
3070 0
        context = self.getFlatReviewContext()
3071
3072 0
        context.goBegin()
3073
3074 0
        self._reviewCurrentLine(inputEvent)
3075 0
        self.targetCursorCell = braille.cursorCell
3076
3077 0
        return True
3078
3079 1
    def reviewNextLine(self, inputEvent):
3080
        """Moves the flat review context to the beginning of the
3081
        next line.  Places the flat review cursor at the beginning
3082
        of the line."""
3083
3084 9
        context = self.getFlatReviewContext()
3085
3086 9
        moved = context.goNext(flat_review.Context.LINE,
3087 9
                               flat_review.Context.WRAP_LINE)
3088
3089 9
        if moved:
3090 9
            self._reviewCurrentLine(inputEvent)
3091 9
            self.targetCursorCell = braille.cursorCell
3092
3093 9
        return True
3094
3095 1
    def reviewBottomLeft(self, inputEvent):
3096
        """Moves the flat review context to the beginning of the
3097
        last line in the window.  Places the flat review cursor at
3098
        the beginning of the line."""
3099
3100 0
        context = self.getFlatReviewContext()
3101
3102 0
        context.goEnd(flat_review.Context.WINDOW)
3103 0
        context.goBegin(flat_review.Context.LINE)
3104 0
        self._reviewCurrentLine(inputEvent)
3105 0
        self.targetCursorCell = braille.cursorCell
3106
3107 0
        return True
3108
3109 1
    def reviewEnd(self, inputEvent):
3110
        """Moves the flat review context to the end of the
3111
        last line in the window.  Places the flat review cursor
3112
        at the end of the line."""
3113
3114 0
        context = self.getFlatReviewContext()
3115 0
        context.goEnd()
3116
3117 0
        self._reviewCurrentLine(inputEvent)
3118 0
        self.targetCursorCell = braille.cursorCell
3119
3120 0
        return True
3121
3122 1
    def reviewCurrentItem(self, inputEvent, targetCursorCell=0):
3123
        """Speak/Braille the current item to the user. A "double-click"
3124
        of this key will cause the word to be spelt. A "triple-click"
3125
        will cause the word to be phonetically spelt.
3126
        """
3127
3128 0
        clickCount = self.getClickCount(self.lastReviewCurrentEvent,
3129 0
                                           inputEvent)
3130 0
        self._reviewCurrentItem(inputEvent, targetCursorCell, clickCount)
3131 0
        self.lastReviewCurrentEvent = inputEvent
3132
3133 0
        return True
3134
3135 1
    def spellCurrentItem(self, string):
3136
        """Spell the current flat review word or line.
3137
3138
        Arguments:
3139
        - string: the string to spell.
3140
        """
3141
3142 0
        for (index, character) in enumerate(string):
3143 0
            if character.isupper():
3144 0
                speech.speak(character, self.voices[settings.UPPERCASE_VOICE])
3145
            else:
3146 0
                speech.speak(character)
3147
3148 1
    def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
3149
                           clickCount=1):
3150
        """Presents the current item to the user.
3151
3152
        Arguments:
3153
        - inputEvent - the current input event.
3154
        - targetCursorCell - if non-zero, the target braille cursor cell.
3155
        - clickCount - number of times the user has "clicked" the current key.
3156
        """
3157
3158 12
        context = self.getFlatReviewContext()
3159 12
        [string, x, y, width, height] = \
3160
                 context.getCurrent(flat_review.Context.WORD)
3161 12
        self.drawOutline(x, y, width, height)
3162
3163
        # Don't announce anything from speech if the user used
3164
        # the Braille display as an input device.
3165
        #
3166 12
        if not isinstance(inputEvent, input_event.BrailleEvent):
3167 12
            if (not string) or (not len(string)) or (string == "\n"):
3168 0
                speech.speak(_("blank"))
3169
            else:
3170 12
                [lineString, x, y, width, height] = \
3171
                         context.getCurrent(flat_review.Context.LINE)
3172 12
                if lineString == "\n":
3173 0
                    speech.speak(_("blank"))
3174 12
                elif string.isspace():
3175 0
                    speech.speak(_("white space"))
3176 12
                elif string.isupper() and (clickCount < 2 or clickCount > 3):
3177 0
                    speech.speak(string, self.voices[settings.UPPERCASE_VOICE])
3178 12
                elif clickCount == 2:
3179 0
                    self.spellCurrentItem(string)
3180 12
                elif clickCount == 3:
3181 0
                    self.phoneticSpellCurrentItem(string)
3182
                else:
3183 12
                    string = self.adjustForRepeats(string)
3184 12
                    speech.speak(string)
3185
3186 12
        self.updateBrailleReview(targetCursorCell)
3187
3188 12
        return True
3189
3190 1
    def reviewCurrentAccessible(self, inputEvent):
3191 0
        context = self.getFlatReviewContext()
3192 0
        [string, x, y, width, height] = \
3193
                 context.getCurrent(flat_review.Context.ZONE)
3194 0
        self.drawOutline(x, y, width, height)
3195
3196
        # Don't announce anything from speech if the user used
3197
        # the Braille display as an input device.
3198
        #
3199 0
        if not isinstance(inputEvent, input_event.BrailleEvent):
3200 0
            speech.speakUtterances(
3201
                self.speechGenerator.getSpeech(
3202
                    context.getCurrentAccessible(), False))
3203
3204 0
        return True
3205
3206 1
    def reviewPreviousItem(self, inputEvent):
3207
        """Moves the flat review context to the previous item.  Places
3208
        the flat review cursor at the beginning of the item."""
3209
3210 6
        context = self.getFlatReviewContext()
3211
3212 6
        moved = context.goPrevious(flat_review.Context.WORD,
3213 6
                                   flat_review.Context.WRAP_LINE)
3214
3215 6
        if moved:
3216 6
            self._reviewCurrentItem(inputEvent)
3217 6
            self.targetCursorCell = braille.cursorCell
3218
3219 6
        return True
3220
3221 1
    def reviewNextItem(self, inputEvent):
3222
        """Moves the flat review context to the next item.  Places
3223
        the flat review cursor at the beginning of the item."""
3224
3225 6
        context = self.getFlatReviewContext()
3226
3227 6
        moved = context.goNext(flat_review.Context.WORD,
3228 6
                               flat_review.Context.WRAP_LINE)
3229
3230 6
        if moved:
3231 6
            self._reviewCurrentItem(inputEvent)
3232 6
            self.targetCursorCell = braille.cursorCell
3233
3234 6
        return True
3235
3236 1
    def reviewCurrentCharacter(self, inputEvent):
3237
        """Presents the current flat review character via braille and speech.
3238
        """
3239
3240 0
        clickCount = self.getClickCount(self.lastReviewCurrentEvent,
3241 0
                                           inputEvent)
3242 0
        self._reviewCurrentCharacter(inputEvent, clickCount)
3243 0
        self.lastReviewCurrentEvent = inputEvent
3244
3245 0
        return True
3246
3247 1
    def _reviewCurrentCharacter(self, inputEvent, clickCount=1):
3248
        """Presents the current flat review character via braille and speech.
3249
3250
        Arguments:
3251
        - inputEvent - the current input event.
3252
        - clickCount - number of times the user has "clicked" the current key.
3253
        """
3254
3255 0
        context = self.getFlatReviewContext()
3256
3257 0
        [string, x, y, width, height] = \
3258
                 context.getCurrent(flat_review.Context.CHAR)
3259 0
        self.drawOutline(x, y, width, height)
3260
3261
        # Don't announce anything from speech if the user used
3262
        # the Braille display as an input device.
3263
        #
3264 0
        if not isinstance(inputEvent, input_event.BrailleEvent):
3265 0
            if (not string) or (not len(string)):
3266 0
                speech.speak(_("blank"))
3267
            else:
3268 0
                [lineString, x, y, width, height] = \
3269
                         context.getCurrent(flat_review.Context.LINE)
3270 0
                if lineString == "\n":
3271 0
                    speech.speak(_("blank"))
3272 0
                elif clickCount == 2:
3273 0
                    self.spellCurrentItem(string)
3274 0
                elif clickCount == 3:
3275 0
                    self.phoneticSpellCurrentItem(string)
3276 0
                elif string.isupper():
3277 0
                    speech.speak(string, self.voices[settings.UPPERCASE_VOICE])
3278
                else:
3279 0
                    speech.speak(string)
3280
3281 0
        self.updateBrailleReview()
3282
3283 0
        return True
3284
3285 1
    def reviewPreviousCharacter(self, inputEvent):
3286
        """Moves the flat review context to the previous character.  Places
3287
        the flat review cursor at character."""
3288
3289 0
        context = self.getFlatReviewContext()
3290
3291 0
        moved = context.goPrevious(flat_review.Context.CHAR,
3292 0
                                   flat_review.Context.WRAP_LINE)
3293
3294 0
        if moved:
3295 0
            self._reviewCurrentCharacter(inputEvent)
3296 0
            self.targetCursorCell = braille.cursorCell
3297
3298 0
        return True
3299
3300 1
    def reviewEndOfLine(self, inputEvent):
3301
        """Moves the flat review context to the end of the line.  Places
3302
        the flat review cursor at the end of the line."""
3303
3304 0
        context = self.getFlatReviewContext()
3305 0
        context.goEnd(flat_review.Context.LINE)
3306
3307 0
        self.reviewCurrentCharacter(inputEvent)
3308 0
        self.targetCursorCell = braille.cursorCell
3309
3310 0
        return True
3311
3312 1
    def reviewNextCharacter(self, inputEvent):
3313
        """Moves the flat review context to the next character.  Places
3314
        the flat review cursor at character."""
3315
3316 0
        context = self.getFlatReviewContext()
3317
3318 0
        moved = context.goNext(flat_review.Context.CHAR,
3319 0
                               flat_review.Context.WRAP_LINE)
3320
3321 0
        if moved:
3322 0
            self._reviewCurrentCharacter(inputEvent)
3323 0
            self.targetCursorCell = braille.cursorCell
3324
3325 0
        return True
3326
3327 1
    def reviewAbove(self, inputEvent):
3328
        """Moves the flat review context to the character most directly
3329
        above the current flat review cursor.  Places the flat review
3330
        cursor at character."""
3331
3332 0
        context = self.getFlatReviewContext()
3333
3334 0
        moved = context.goAbove(flat_review.Context.CHAR,
3335 0
                                flat_review.Context.WRAP_LINE)
3336
3337 0
        if moved:
3338 0
            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
3339
3340 0
        return True
3341
3342 1
    def reviewBelow(self, inputEvent):
3343
        """Moves the flat review context to the character most directly
3344
        below the current flat review cursor.  Places the flat review
3345
        cursor at character."""
3346
3347 0
        context = self.getFlatReviewContext()
3348
3349 0
        moved = context.goBelow(flat_review.Context.CHAR,
3350 0
                                flat_review.Context.WRAP_LINE)
3351
3352 0
        if moved:
3353 0
            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
3354
3355 0
        return True
3356
3357 1
    def showZones(self, inputEvent):
3358
        """Debug routine to paint rectangles around the discrete
3359
        interesting (e.g., text)  zones in the active window for
3360
        this application.
3361
        """
3362
3363 0
        flatReviewContext = self.getFlatReviewContext()
3364 0
        lines = flatReviewContext.lines
3365 0
        for line in lines:
3366 0
            string = ""
3367 0
            for zone in line.zones:
3368 0
                string += " '%s' [%s]" % (zone.string, zone.accessible.role)
3369 0
                self.drawOutline(zone.x, zone.y, zone.width, zone.height,
3370 0
                                 False)
3371 0
            debug.println(debug.LEVEL_OFF, string)
3372 0
        self.flatReviewContext = None
3373
3374 1
    def find(self, query=None):
3375
        """Searches for the specified query.  If no query is specified,
3376
        it searches for the query specified in the Orca Find dialog.
3377
3378
        Arguments:
3379
        - query: The search query to find.
3380
        """
3381
3382 0
        if not query:
3383 0
            query = find.getLastQuery()
3384 0
        if query:
3385 0
            context = self.getFlatReviewContext()
3386 0
            location = query.findQuery(context, self.justEnteredFlatReviewMode)
3387 0
            if not location:
3388 0
                braille.displayMessage(_("string not found"))
3389 0
                speech.speak(_("string not found"))
3390
            else:
3391 0
                context.setCurrent(location.lineIndex, location.zoneIndex, \
3392 0
                                   location.wordIndex, location.charIndex)
3393 0
                self.reviewCurrentItem(None)
3394 0
                self.targetCursorCell = braille.cursorCell
3395
3396 1
    def findNext(self, inputEvent):
3397
        """Searches forward for the next instance of the string
3398
        searched for via the Orca Find dialog.  Other than direction
3399
        and the starting point, the search options initially specified
3400
        (case sensitivity, window wrap, and full/partial match) are
3401
        preserved.
3402
        """
3403
3404 0
        lastQuery = find.getLastQuery()
3405 0
        if lastQuery:
3406 0
            lastQuery.searchBackwards = False
3407 0
            lastQuery.startAtTop = False
3408 0
            self.find(lastQuery)
3409
        else:
3410 0
            orca._showFindGUI()
3411
3412 1
    def findPrevious(self, inputEvent):
3413
        """Searches backwards for the next instance of the string
3414
        searched for via the Orca Find dialog.  Other than direction
3415
        and the starting point, the search options initially specified
3416
        (case sensitivity, window wrap, and full/partial match) are
3417
        preserved.
3418
        """
3419
3420 0
        lastQuery = find.getLastQuery()
3421 0
        if lastQuery:
3422 0
            lastQuery.searchBackwards = True
3423 0
            lastQuery.startAtTop = False
3424 0
            self.find(lastQuery)
3425
        else:
3426 0
            orca._showFindGUI()
3427
3428
########################################################################
3429
#                                                                      #
3430
# DEBUG support.                                                       #
3431
#                                                                      #
3432
########################################################################
3433
3434 1
    def printAppsHandler(self, script=None, inputEvent=None):
3435
        """Prints a list of all applications to stdout."""
3436 0
        self.printApps()
3437 0
        return True
3438
3439 1
    def printActiveAppHandler(self, script=None, inputEvent=None):
3440
        """Prints the currently active application."""
3441 0
        self.printActiveApp()
3442 0
        return True
3443
3444 1
    def printAncestryHandler(self, script=None, inputEvent=None):
3445
        """Prints the ancestry for the current locusOfFocus"""
3446 0
        self.printAncestry(orca_state.locusOfFocus)
3447 0
        return True 
3448
3449 1
    def printHierarchyHandler(self, script=None, inputEvent=None):
3450
        """Prints the application for the current locusOfFocus"""
3451 0
        if orca_state.locusOfFocus:
3452 0
            self.printHierarchy(orca_state.locusOfFocus.app,
3453 0
                                orca_state.locusOfFocus)
3454 0
        return True
3455
3456
# Routines that were previously in util.py, but that have now been moved
3457
# here so that they can be customized in application scripts if so desired.
3458
# 
3459
3460 1
    def isSameObject(self, obj1, obj2):
3461 958
        if (obj1 == obj2):
3462 0
            return True
3463 958
        elif (not obj1) or (not obj2):
3464 65
            return False
3465
3466 893
        try:
3467 893
            if obj1.name != obj2.name:
3468 793
                return False
3469
3470
            # When we're looking at children of objects that manage
3471
            # their descendants, we will often get different objects
3472
            # that point to the same logical child.  We want to be able
3473
            # to determine if two objects are in fact pointing to the 
3474
            # same child.
3475
            # If we cannot do so easily (i.e., object equivalence), we examine
3476
            # the hierarchy and the object index at each level.
3477
            #
3478 58
            parent1 = obj1
3479 58
            parent2 = obj2
3480 58
            while (parent1 and parent2 and \
3481
                    parent1.state.count(atspi.Accessibility.STATE_TRANSIENT) and \
3482
                    parent2.state.count(atspi.Accessibility.STATE_TRANSIENT)):
3483 28
                if parent1.index != parent2.index:
3484 28
                    return False
3485 0
                parent1 = parent1.parent
3486 0
                parent2 = parent2.parent
3487 30
            if parent1 and parent2 and parent1 == parent2:
3488 0
                return True
3489 42
        except:
3490 42
            pass
3491
3492
        # In java applications, TRANSIENT state is missing for tree items
3493
        # (fix for bug #352250)
3494
        #
3495 72
        try:
3496 72
            parent1 = obj1
3497 72
            parent2 = obj2
3498 72
            while parent1 and parent2 and \
3499
                    parent1.role == rolenames.ROLE_LABEL and \
3500
                    parent2.role == rolenames.ROLE_LABEL:
3501 0
                if parent1.index != parent2.index:
3502 0
                    return False
3503 0
                parent1 = parent1.parent
3504 0
                parent2 = parent2.parent
3505 72
            if parent1 and parent2 and parent1 == parent2:
3506 0
                return True
3507 0
        except:
3508 0
            pass
3509
3510 72
        return False
3511
3512 1
    def appendString(self, text, newText, delimiter=" "):
3513
        """Appends the newText to the given text with the delimiter in between
3514
        and returns the new string.  Edge cases, such as no initial text or
3515
        no newText, are handled gracefully."""
3516
3517 22982
        if (not newText) or (len(newText) == 0):
3518 13716
            return text
3519 9266
        elif text and len(text):
3520 3508
            return text + delimiter + newText
3521
        else:
3522 5758
            return newText
3523
3524 1
    def __hasLabelForRelation(self, label):
3525
        """Check if label has a LABEL_FOR relation
3526
3527
        Arguments:
3528
        - label: the label in question
3529
3530
        Returns TRUE if label has a LABEL_FOR relation.
3531
        """
3532 360
        if (not label) or (label.role != rolenames.ROLE_LABEL):
3533 0
            return False
3534
3535 360
        relations = label.relations
3536
3537 360
        for relation in relations:
3538 6
            if relation.getRelationType() \
3539
                   == atspi.Accessibility.RELATION_LABEL_FOR:
3540 6
                return True    
3541
3542 354
        return False
3543
3544
3545 1
    def __isLabeling(self, label, object):
3546
        """Check if label is connected via  LABEL_FOR relation with object
3547
3548
        Arguments:
3549
        - object: the object in question
3550
        - labeled: the label in question
3551
3552
        Returns TRUE if label has a relation LABEL_FOR for object.
3553
        """
3554
3555 6647
        if (not object) \
3556
           or (not label) \
3557
           or (label.role != rolenames.ROLE_LABEL):
3558 6053
            return False
3559
3560 594
        relations = label.relations
3561 594
        if not relations:
3562 484
            return False
3563
3564 216
        for relation in relations:
3565 110
            if relation.getRelationType() \
3566
                   == atspi.Accessibility.RELATION_LABEL_FOR:
3567
3568 216
                for i in range(0, relation.getNTargets()):
3569 110
                    target = atspi.Accessible.makeAccessible(\
3570
                                                    relation.getTarget(i))
3571 110
                    if target == object:
3572 4
                        return True
3573
3574 106
        return False
3575
3576 1
    def getUnicodeCurrencySymbols(self):
3577
        """Return a list of the unicode currency symbols, populating the list
3578
        if this is the first time that this routine has been called. 
3579
3580
        Returns a list of unicode currency symbols.
3581
        """
3582
3583 0
        if not self._unicodeCurrencySymbols:
3584 0
            self._unicodeCurrencySymbols = [ \
3585
                u'\u0024',     # dollar sign
3586
                u'\u00A2',     # cent sign
3587
                u'\u00A3',     # pound sign
3588
                u'\u00A4',     # currency sign
3589
                u'\u00A5',     # yen sign 
3590
                u'\u0192',     # latin small letter f with hook
3591
                u'\u060B',     # afghani sign
3592
                u'\u09F2',     # bengali rupee mark
3593
                u'\u09F3',     # bengali rupee sign
3594
                u'\u0AF1',     # gujarati rupee sign
3595
                u'\u0BF9',     # tamil rupee sign
3596
                u'\u0E3F',     # thai currency symbol baht
3597
                u'\u17DB',     # khmer currency symbol riel
3598
                u'\u2133',     # script capital m
3599
                u'\u5143',     # cjk unified ideograph-5143
3600
                u'\u5186',     # cjk unified ideograph-5186
3601
                u'\u5706',     # cjk unified ideograph-5706
3602
                u'\u5713',     # cjk unified ideograph-5713
3603
                u'\uFDFC',     # rial sign
3604
            ]
3605
3606
            # Add 20A0 (EURO-CURRENCY SIGN) to 20B5 (CEDI SIGN)
3607
            #
3608 0
            for ordChar in range(ord(u'\u20A0'), ord(u'\u20B5') + 1):
3609 0
                self._unicodeCurrencySymbols.append(unichr(ordChar))
3610
3611 0
        return self._unicodeCurrencySymbols
3612
3613 1
    def getDisplayedLabel(self, object):
3614
        """If there is an object labelling the given object, return the
3615
        text being displayed for the object labelling this object.
3616
        Otherwise, return None.
3617
3618
        Argument:
3619
        - object: the object in question
3620
3621
        Returns the string of the object labelling this object, or None
3622
        if there is nothing of interest here.
3623
        """
3624
3625
        # For some reason, some objects are labelled by the same thing
3626
        # more than once.  Go figure, but we need to check for this.
3627
        #
3628 15012
        label = None
3629 15012
        relations = object.relations
3630 15012
        allTargets = []
3631
3632 15431
        for relation in relations:
3633 419
            if relation.getRelationType() \
3634
                   == atspi.Accessibility.RELATION_LABELLED_BY:
3635
3636
                # The object can be labelled by more than one thing, so we just
3637
                # get all the labels (from unique objects) and append them
3638
                # together.  An example of such objects live in the "Basic"
3639
                # page of the gnome-accessibility-keyboard-properties app.
3640
                # The "Delay" and "Speed" objects are labelled both by
3641
                # their names and units.
3642
                #
3643 226
                for i in range(0, relation.getNTargets()):
3644 113
                    target = atspi.Accessible.makeAccessible(\
3645
                                                       relation.getTarget(i))
3646 113
                    if not target in allTargets:
3647 113
                        allTargets.append(target)
3648 113
                        label = self.appendString(label, self.getDisplayedText(target))
3649
3650
        # [[[TODO: HACK - we've discovered oddness in hierarchies such as
3651
        # the gedit Edit->Preferences dialog.  In this dialog, we have
3652
        # labeled groupings of objects.  The grouping is done via a FILLER
3653
        # with two children - one child is the overall label, and the
3654
        # other is the container for the grouped objects.  When we detect
3655
        # this, we add the label to the overall context.
3656
        #
3657
        # We are also looking for objects which have a PANEL or a FILLER as
3658
        # parent, and its parent has more children. Through these children,
3659
        # a potential label with LABEL_FOR relation may exists. We want to
3660
        # present this label.
3661
        # This case can be seen in FileChooserDemo application, in Open dialog
3662
        # window, the line with "Look In" label, a combobox and some
3663
        # presentation buttons.]]]
3664
        #
3665 15012
        if not label:
3666
3667 14899
            potentialLabel = None
3668 14899
            useLabel = False
3669 14899
            if ((object.role == rolenames.ROLE_FILLER) \
3670
                    or (object.role == rolenames.ROLE_PANEL)) \
3671
                and (object.childCount == 2):
3672
3673 1600
                potentialLabel = object.child(0)
3674 1600
                secondChild = object.child(1)
3675 1600
                useLabel = potentialLabel.role == rolenames.ROLE_LABEL \
3676
                        and ((secondChild.role == rolenames.ROLE_FILLER) \
3677
                                or (secondChild.role == rolenames.ROLE_PANEL)) \
3678
                        and not self.__hasLabelForRelation(potentialLabel)
3679
            else:
3680 13299
                parent = object.parent
3681 13299
                if parent and \
3682
                    ((parent.role == rolenames.ROLE_FILLER) \
3683
                            or (parent.role == rolenames.ROLE_PANEL)):
3684 9480
                    for i in range (0, parent.childCount):
3685 6647
                        try:
3686 6647
                            potentialLabel = parent.child(i)
3687 6647
                            useLabel = self.__isLabeling(potentialLabel, object)
3688 6647
                            if useLabel:
3689 4
                                break
3690 0
                        except:
3691 0
                            pass
3692
3693 14899
            if useLabel and potentialLabel:
3694 358
                label = potentialLabel.name
3695
3696 15012
        return label
3697
3698 1
    def __getDisplayedTextInComboBox(self, combo):
3699
3700
        """Returns the text being displayed in a combo box.  If nothing is
3701
        displayed, then None is returned.
3702
3703
        Arguments:
3704
        - combo: the combo box
3705
3706
        Returns the text in the combo box or an empty string if nothing is
3707
        displayed.
3708
        """
3709
3710 127
        displayedText = None
3711
3712
        # Find the text displayed in the combo box.  This is either:
3713
        #
3714
        # 1) The last text object that's a child of the combo box
3715
        # 2) The selected child of the combo box.
3716
        # 3) The contents of the text of the combo box itself when
3717
        #    treated as a text object.
3718
        #
3719
        # Preference is given to #1, if it exists.
3720
        #
3721
        # If the label of the combo box is the same as the utterance for
3722
        # the child object, then this utterance is only displayed once.
3723
        #
3724
        # [[[TODO: WDW - Combo boxes are complex beasts.  This algorithm
3725
        # needs serious work.  Logged as bugzilla bug 319745.]]]
3726
        #
3727 127
        textObj = None
3728 291
        for i in range(0, combo.childCount):
3729 164
            child = combo.child(i)
3730 164
            if child and child.role == rolenames.ROLE_TEXT:
3731 37
                textObj = child
3732
3733 127
        if textObj:
3734 37
            [displayedText, caretOffset, startOffset] = \
3735
                self.getTextLineAtCaret(textObj)
3736
            #print "TEXTOBJ", displayedText
3737
        else:
3738 90
            selectedItem = None
3739 90
            comboSelection = combo.selection
3740 90
            if comboSelection and comboSelection.nSelectedChildren > 0:
3741 86
                try:
3742 86
                    selectedItem = atspi.Accessible.makeAccessible(
3743
                        comboSelection.getSelectedChild(0))
3744 0
                except:
3745 0
                    pass
3746 90
            if selectedItem:
3747 86
                displayedText = self.getDisplayedText(selectedItem)
3748
                #print "SELECTEDITEM", displayedText
3749 4
            elif combo.name and len(combo.name):
3750
                # We give preference to the name over the text because
3751
                # the text for combo boxes seems to never change in
3752
                # some cases.  The main one where we see this is in
3753
                # the gaim "Join Chat" window.
3754
                #
3755 4
                displayedText = combo.name
3756
                #print "NAME", displayedText
3757 0
            elif combo.text:
3758 0
                [displayedText, caretOffset, startOffset] = \
3759
                    self.getTextLineAtCaret(combo)
3760
                #print "TEXT", displayedText
3761
3762 127
        return displayedText
3763
3764 1
    def getDisplayedText(self, obj):
3765
        """Returns the text being displayed for an object.
3766
3767
        Arguments:
3768
        - obj: the object
3769
3770
        Returns the text being displayed for an object or None if there isn't
3771
        any text being shown.
3772
        """
3773
3774 12947
        displayedText = None
3775
3776 12947
        if obj.role == rolenames.ROLE_COMBO_BOX:
3777 127
            return self.__getDisplayedTextInComboBox(obj)
3778
3779
        # The accessible text of an object is used to represent what is
3780
        # drawn on the screen.
3781
        #
3782 12820
        if obj.text:
3783 2496
            displayedText = obj.text.getText(0, -1)
3784
3785
            # [[[WDW - HACK to account for things such as Gecko that want
3786
            # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
3787
            # object that has the real accessible text for the label.  We
3788
            # detect this by the specfic case where the text for the
3789
            # current object is a single EMBEDDED_OBJECT_CHARACTER.  In
3790
            # this case, we look to the child for the real text.]]]
3791
            #
3792 2496
            unicodeText = displayedText.decode("UTF-8")
3793 2496
            if unicodeText \
3794
               and (len(unicodeText) == 1) \
3795
               and (unicodeText[0] == self.EMBEDDED_OBJECT_CHARACTER):
3796 0
                try:
3797 0
                    displayedText = self.getDisplayedText(obj.child(0))
3798 0
                except:
3799 0
                    debug.printException(debug.LEVEL_WARNING)
3800 2496
            elif unicodeText:
3801
                # [[[TODO: HACK - Welll.....we'll just plain ignore any
3802
                # text with EMBEDDED_OBJECT_CHARACTERs here.  We still need a
3803
                # general case to handle this stuff and expand objects
3804
                # with EMBEDDED_OBJECT_CHARACTERs.]]]
3805
                #
3806 33511
                for i in range(0, len(unicodeText)):
3807 31292
                    if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
3808 0
                        displayedText = None
3809 0
                        break
3810
3811 12819
        if (not displayedText) or (len(displayedText) == 0):
3812 10600
            displayedText = obj.name
3813
3814
        # [[[WDW - HACK because push buttons can have labels as their
3815
        # children.  An example of this is the Font: button on the General
3816
        # tab in the Editing Profile dialog in gnome-terminal.
3817
        #
3818 12818
        if ((not displayedText) or (len(displayedText) == 0)) \
3819
           and obj.role == rolenames.ROLE_PUSH_BUTTON:
3820 34
            for i in range(0, obj.childCount):
3821 0
                child = obj.child(i)
3822 0
                if child.role == rolenames.ROLE_LABEL:
3823 0
                    childText = self.getDisplayedText(child)
3824 0
                    if childText and len(childText):
3825 0
                        displayedText = self.appendString(displayedText, childText)
3826
3827 12818
        return displayedText
3828
3829 1
    def findFocusedObject(self, root):
3830
        """Returns the accessible that has focus under or including the
3831
        given root.
3832
3833
        TODO: This will currently traverse all children, whether they are
3834
        visible or not and/or whether they are children of parents that
3835
        manage their descendants.  At some point, this method should be
3836
        optimized to take such things into account.
3837
3838
        Arguments:
3839
        - root: the root object where to start searching
3840
3841
        Returns the object with the FOCUSED state or None if no object with
3842
        the FOCUSED state can be found.
3843
        """
3844
3845 0
        if root.state.count(atspi.Accessibility.STATE_FOCUSED):
3846 0
            return root
3847
3848 0
        for i in range(0, root.childCount):
3849 0
            try:
3850 0
                candidate = self.findFocusedObject(root.child(i))
3851 0
                if candidate:
3852 0
                    return candidate
3853 0
            except:
3854 0
                pass
3855
3856 0
        return None
3857
3858 1
    def getRealActiveDescendant(self, obj):
3859
        """Given an object that should be a child of an object that
3860
        manages its descendants, return the child that is the real
3861
        active descendant carrying useful information.
3862
3863
        Arguments:
3864
        - obj: an object that should be a child of an object that
3865
        manages its descendants.
3866
        """
3867
3868
        # If obj is a table cell and all of it's children are table cells
3869
        # (probably cell renderers), then return the first child which has
3870
        # a non zero length text string. If no such object is found, just
3871
        # fall through and use the default approach below. See bug #376791
3872
        # for more details.
3873
        #
3874 507
        if obj.role == rolenames.ROLE_TABLE_CELL and obj.childCount:
3875 2
            nonTableCellFound = False
3876 6
            for i in range (0, obj.childCount):
3877 4
                if obj.child(i).role != rolenames.ROLE_TABLE_CELL:
3878 0
                    nonTableCellFound = True
3879 2
            if not nonTableCellFound:
3880 4
                for i in range (0, obj.childCount):
3881 4
                    if obj.child(i).text:
3882 2
                        text = obj.child(i).text.getText(0, -1)
3883 2
                        if len(text) != 0:
3884 2
                            return obj.child(i)
3885
3886
        # [[[TODO: WDW - this is an odd hacky thing I've somewhat drawn
3887
        # from Gnopernicus.  The notion here is that we get an active
3888
        # descendant changed event, but that object tends to have children
3889
        # itself and we need to decide what to do.  Well...the idea here
3890
        # is that the last child (Gnopernicus chooses child(1)), tends to
3891
        # be the child with information.  The previous children tend to
3892
        # be non-text or just there for spacing or something.  You will
3893
        # see this in the various table demos of gtk-demo and you will
3894
        # also see this in the Contact Source Selector in Evolution.
3895
        #
3896
        # Just note that this is most likely not a really good solution
3897
        # for the general case.  That needs more thought.  But, this
3898
        # comment is here to remind us this is being done in poor taste
3899
        # and we need to eventually clean up our act.]]]
3900
        #
3901 505
        if obj and obj.childCount:
3902 0
            return obj.child(obj.childCount - 1)
3903
        else:
3904 505
            return obj
3905
3906 1
    def getClickCount(self, lastInputEvent, inputEvent):
3907
        """Return the count of the number of clicks a user has made to one
3908
        of the keys on the keyboard.
3909
3910
        Arguments:
3911
        - lastInputEvent: the last input event.
3912
        - inputEvent: the current input event.
3913
        """
3914
3915 0
        if not isinstance(lastInputEvent, input_event.KeyboardEvent) or \
3916
           not isinstance(inputEvent, input_event.KeyboardEvent):
3917 0
            orca_state.clickCount = 0
3918 0
            return orca_state.clickCount
3919
3920 0
        if (lastInputEvent.hw_code != inputEvent.hw_code) or \
3921
           (lastInputEvent.modifiers != inputEvent.modifiers):
3922 0
            orca_state.clickCount = 1
3923 0
            return orca_state.clickCount
3924
3925 0
        if (inputEvent.time - lastInputEvent.time) < settings.doubleClickTimeout:
3926 0
            orca_state.clickCount += 1
3927
        else:
3928 0
            orca_state.clickCount = 0
3929 0
        return orca_state.clickCount+1
3930
3931 1
    def isDesiredFocusedItem(self, obj, rolesList):
3932
        """Called to determine if the given object and it's hierarchy of
3933
           parent objects, each have the desired roles.
3934
3935
        Arguments:
3936
        - obj: the accessible object to check.
3937
        - rolesList: the list of desired roles for the components and the
3938
          hierarchy of its parents.
3939
3940
        Returns True if all roles match.
3941
        """
3942
3943 2661
        current = obj
3944 2736
        for i in range(0, len(rolesList)):
3945 2727
            if (current == None) or (current.role != rolesList[i]):
3946 2652
                return False
3947 75
            current = current.parent
3948
3949 9
        return True
3950
3951 1
    def speakMisspeltWord(self, allTokens, badWord):
3952
        """Called by various spell checking routine to speak the misspelt word,
3953
           plus the context that it is being used in.
3954
3955
        Arguments:
3956
        - allTokens: a list of all the words.
3957
        - badWord: the misspelt word.
3958
        """
3959
3960
        # Create an utterance to speak consisting of the misspelt
3961
        # word plus the context where it is used (upto five words
3962
        # to either side of it).
3963
        #
3964 0
        for i in range(0, len(allTokens)):
3965 0
            if allTokens[i].startswith(badWord):
3966 0
                min = i - 5
3967 0
                if min < 0:
3968 0
                    min = 0
3969 0
                max = i + 5
3970 0
                if max > (len(allTokens) - 1):
3971 0
                    max = len(allTokens) - 1
3972
3973 0
                utterances = [_("Misspelled word: "), badWord, \
3974
                              _(" Context is ")] + allTokens[min:max+1]
3975
3976
                # Turn the list of utterances into a string.
3977 0
                text = " ".join(utterances)
3978 0
                speech.speak(text)
3979
3980 1
    def textLines(self, obj):
3981
        """Creates a generator that can be used to iterate over each line
3982
        of a text object, starting at the caret offset.
3983
3984
        Arguments:
3985
        - obj: an Accessible that has a text specialization
3986
3987
        Returns an iterator that produces elements of the form:
3988
        [SayAllContext, acss], where SayAllContext has the text to be
3989
        spoken and acss is an ACSS instance for speaking the text.
3990
        """ 
3991 0
        if not obj:
3992 0
            return
3993
3994 0
        text = obj.text 
3995 0
        if not text:
3996 0
            return 
3997
3998 0
        length = text.characterCount
3999 0
        offset = text.caretOffset
4000
4001
        # Get the next line of text to read
4002
        #
4003 0
        done = False 
4004 0
        while not done:
4005 0
            lastEndOffset = -1
4006 0
            while offset < length:
4007 0
                [string, startOffset, endOffset] = text.getTextAtOffset(
4008
                    offset,
4009 0
                    atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
4010
4011
                # [[[WDW - HACK: well...gnome-terminal sometimes wants to
4012
                # give us outrageous values back from getTextAtOffset
4013
                # (see http://bugzilla.gnome.org/show_bug.cgi?id=343133),
4014
                # so we try to handle it.]]]
4015
                #
4016 0
                if startOffset < 0:
4017 0
                    break
4018
4019
                # [[[WDW - HACK: this is here because getTextAtOffset
4020
                # tends not to be implemented consistently across toolkits.
4021
                # Sometimes it behaves properly (i.e., giving us an endOffset
4022
                # that is the beginning of the next line), sometimes it
4023
                # doesn't (e.g., giving us an endOffset that is the end of
4024
                # the current line).  So...we hack.  The whole 'max' deal
4025
                # is to account for lines that might be a brazillion lines
4026
                # long.]]]
4027
                # 
4028 0
                if endOffset == lastEndOffset:
4029 0
                    offset = max(offset + 1, lastEndOffset + 1)
4030 0
                    lastEndOffset = endOffset
4031 0
                    continue
4032
4033 0
                lastEndOffset = endOffset
4034 0
                offset = endOffset
4035
4036 0
                string = self.adjustForRepeats(string)
4037 0
                if string.isupper():
4038 0
                    voice = settings.voices[settings.UPPERCASE_VOICE]
4039
                else:
4040 0
                    voice = settings.voices[settings.DEFAULT_VOICE]
4041
4042 0
                yield [speechserver.SayAllContext(obj, string,
4043 0
                                                  startOffset, endOffset),
4044
                       voice]
4045
4046 0
            moreLines = False
4047 0
            relations = obj.relations
4048 0
            for relation in relations:
4049 0
                if relation.getRelationType()  \
4050
                       == atspi.Accessibility.RELATION_FLOWS_TO:
4051 0
                    obj = atspi.Accessible.makeAccessible(relation.getTarget(0))
4052
4053 0
                    text = obj.text
4054 0
                    if not text:
4055 0
                        return
4056
4057 0
                    length = text.characterCount
4058 0
                    offset = 0
4059 0
                    moreLines = True
4060 0
                    break
4061 0
            if not moreLines:
4062 0
                done = True
4063
4064 1
    def _addRepeatSegment(self, segment, line, respectPunctuation=True):
4065
        """Add in the latest line segment, adjusting for repeat characters
4066
        and punctuation.
4067
4068
        Arguments:
4069
        - segment: the segment of repeated characters.
4070
        - line: the current built-up line to characters to speak.
4071
        - respectPunctuation: if False, ignore punctuation level.
4072
4073
        Returns: the current built-up line plus the new segment, after
4074
        adjusting for repeat character counts and punctuation.
4075
        """
4076
4077 540
        style = settings.verbalizePunctuationStyle
4078 540
        isPunctChar = True
4079 540
        try:
4080 540
            level, action = punctuation_settings.getPunctuationInfo(segment[0])
4081 526
        except:
4082 526
            isPunctChar = False
4083 540
        count = len(segment)
4084 540
        if (count >= settings.repeatCharacterLimit) \
4085
           and (not segment[0] in string.whitespace):
4086 0
            if (not respectPunctuation) \
4087
               or (isPunctChar and (style <= level)):
4088 0
                repeatChar = chnames.getCharacterName(segment[0])
4089 0
                line += _(" %d %s characters ") % (count, repeatChar)
4090
            else:
4091 0
                line += segment
4092
        else:
4093 540
            line += segment
4094
4095 540
        return line
4096
4097 1
    def adjustForRepeats(self, line):
4098
        """Adjust line to include repeat character counts.
4099
        As some people will want this and others might not,
4100
        there is a setting in settings.py that determines
4101
        whether this functionality is enabled.
4102
4103
        repeatCharacterLimit = <n>
4104
4105
        If <n> is 0, then there would be no repeat characters.
4106
        Otherwise <n> would be the number of same characters (or more)
4107
        in a row that cause the repeat character count output.
4108
        If the value is set to 1, 2 or 3 then it's treated as if it was
4109
        zero. In other words, no repeat character count is given.
4110
4111
        Arguments:
4112
        - line: the string to adjust for repeat character counts.
4113
4114
        Returns: a new line adjusted for repeat character counts (if enabled).
4115
        """
4116
4117 38
        line = line.decode("UTF-8")
4118
4119 38
        if (len(line) < 4) or (settings.repeatCharacterLimit < 4):
4120 12
            return line.encode("UTF-8")
4121
4122 26
        newLine = u''
4123 26
        segment = lastChar = line[0]
4124
4125 26
        multipleChars = False
4126 549
        for i in range(1, len(line)):
4127 523
            if line[i] == lastChar:
4128 9
                segment += line[i]
4129
            else:
4130 514
                multipleChars = True
4131 514
                newLine = self._addRepeatSegment(segment, newLine)
4132 514
                segment = line[i]
4133
4134 523
            lastChar = line[i]
4135
4136 26
        newLine = self._addRepeatSegment(segment, newLine, multipleChars)
4137
4138 26
        return newLine.encode("UTF-8")
4139
4140 1
    def adjustForPronunciation(self, line):
4141
        """Adjust the line to replace words in the pronunciation dictionary,
4142
        with what those words actually sound like.
4143
4144
        Arguments:
4145
        - line: the string to adjust for words in the pronunciation dictionary.
4146
4147
        Returns: a new line adjusted for words found in the pronunciation
4148
        dictionary.
4149
        """
4150
4151 0
        line = line.decode("UTF-8")
4152 0
        newLine = segment = u''
4153
4154 0
        for i in range(0, len(line)):
4155 0
            if self.isWordDelimiter(line[i]):
4156 0
                if len(segment) != 0:
4157 0
                    newLine = newLine + \
4158
                              pronunciation_dict.getPronunciation(segment)
4159 0
                newLine = newLine + line[i]
4160 0
                segment = u''
4161
            else:
4162 0
                segment += line[i]
4163
4164 0
        if len(segment) != 0:
4165 0
            newLine = newLine + pronunciation_dict.getPronunciation(segment)
4166
4167 0
        return newLine.encode("UTF-8")
4168
4169 1
    def getLinkIndex(self, obj, characterIndex):
4170
        """A brute force method to see if an offset is a link.  This
4171
        is provided because not all Accessible Hypertext implementations
4172
        properly support the getLinkIndex method.  Returns an index of
4173
        0 or greater of the characterIndex is on a hyperlink.
4174
4175
        Arguments:
4176
        -obj: the Accessible object with the Accessible Hypertext specialization
4177
        -characterIndex: the text position to check
4178
        """
4179
4180 60
        if not obj:
4181 0
            return -1
4182
4183 60
        text = obj.text
4184 60
        if not text:
4185 0
            return -1
4186
4187 60
        hypertext = obj.hypertext
4188 60
        if not hypertext:
4189 24
            return -1
4190
4191 36
        for i in range(0, hypertext.getNLinks()):
4192 0
            link = hypertext.getLink(i)
4193 0
            if (characterIndex >= link.startIndex) \
4194
               and (characterIndex <= link.endIndex):
4195 0
                return i
4196
4197 36
        return -1
4198
4199 1
    def isWordDelimiter(self, character):
4200
        """Returns True if the given character is a word delimiter.
4201
4202
        Arguments:
4203
        - character: the character in question
4204
4205
        Returns True if the given character is a word delimiter.
4206
        """
4207
4208 31
        if isinstance(character, unicode):
4209 31
            character = character.encode("UTF-8")
4210
4211 31
        return (character in string.whitespace) \
4212
               or (character in '!*+,-./:;<=>?@[\]^_{|}')
4213
4214 1
    def getFrame(self, obj):
4215
        """Returns the frame containing this object, or None if this object
4216
        is not inside a frame.
4217
4218
        Arguments:
4219
        - obj: the Accessible object
4220
        """
4221
4222 66
        debug.println(debug.LEVEL_FINEST,
4223 66
                      "Finding frame for source.name="
4224
                      + obj.accessibleNameToString())
4225
4226 462
        while obj \
4227
              and (obj != obj.parent) \
4228
              and (obj.role != rolenames.ROLE_FRAME):
4229 396
            obj = obj.parent
4230 396
            debug.println(debug.LEVEL_FINEST, "--> obj.name="
4231
                          + obj.accessibleNameToString())
4232
4233 66
        if obj and (obj.role == rolenames.ROLE_FRAME):
4234 66
            pass
4235
        else:
4236 0
            obj = None
4237
4238 66
        return obj
4239
4240 1
    def getTopLevel(self, obj):
4241
        """Returns the top-level object (frame, dialog ...) containing this
4242
        object, or None if this object is not inside a top-level object.
4243
4244
        Arguments:
4245
        - obj: the Accessible object
4246
        """
4247
4248 688
        debug.println(debug.LEVEL_FINEST,
4249 688
                      "Finding top-level object for source.name="
4250
                      + obj.accessibleNameToString())
4251
4252 4244
        while obj \
4253
              and obj.parent \
4254
              and (obj != obj.parent) \
4255
              and (obj.parent.role != rolenames.ROLE_APPLICATION):
4256 3562
            obj = obj.parent
4257 3562
            debug.println(debug.LEVEL_FINEST, "--> obj.name="
4258
                          + obj.accessibleNameToString())
4259
4260 682
        if obj and obj.parent and \
4261
           (obj.parent.role == rolenames.ROLE_APPLICATION):
4262 666
            pass
4263
        else:
4264 16
            obj = None
4265
4266 682
        return obj
4267
4268 1
    def getTopLevelName(self, obj):
4269
        """ Returns the name of the top-level object. See getTopLevel.
4270
        """
4271 61
        top = self.getTopLevel(obj)
4272 61
        if (not top) or (not top.name):
4273 0
            return ""
4274
        else:
4275 61
            return top.name
4276
4277 1
    def getTextLineAtCaret(self, obj):
4278
        """Gets the line of text where the caret is.
4279
4280
        Argument:
4281
        - obj: an Accessible object that implements the AccessibleText
4282
               interface
4283
4284
        Returns the [string, caretOffset, startOffset] for the line of text
4285
        where the caret is.
4286
        """
4287
4288
        # Get the the AccessibleText interrface
4289
        #
4290 1683
        text = obj.text
4291 1683
        if not text:
4292 0
            return ["", 0, 0]
4293
4294
        # The caret might be positioned at the very end of the text area.
4295
        # In these cases, calling text.getTextAtOffset on an offset that's
4296
        # not positioned to a character can yield unexpected results.  In
4297
        # particular, we'll see the Gecko toolkit return a start and end
4298
        # offset of (0, 0), and we'll see other implementations, such as
4299
        # gedit, return reasonable results (i.e., gedit will give us the
4300
        # last line).
4301
        #
4302
        # In order to accommodate the differing behavior of different
4303
        # AT-SPI implementations, we'll make sure we give getTextAtOffset
4304
        # the offset of an actual character.  Then, we'll do a little check
4305
        # to see if that character is a newline - if it is, we'll treat it
4306
        # as the line.
4307
        #
4308 1683
        if text.caretOffset == text.characterCount:
4309 1370
            caretOffset = max(0, text.caretOffset - 1)
4310 1370
            character = text.getText(caretOffset, 
4311 1370
                                     caretOffset + 1).decode("UTF-8")
4312
        else:
4313 313
            caretOffset = text.caretOffset
4314 313
            character = None
4315
4316 1683
        if (text.caretOffset == text.characterCount) \
4317
            and (character == "\n"):
4318 22
            content = ""
4319 22
            startOffset = caretOffset
4320
        else:
4321
            # Get the line containing the caret.  [[[TODO: HACK WDW - If
4322
            # there's only 1 character in the string, well, we get it.  We
4323
            # do this because Gecko's implementation of getTextAtOffset
4324
            # is broken if there is just one character in the string.]]]
4325
            #
4326 1661
            if (text.characterCount == 1):
4327 106
                string = text.getText(caretOffset, caretOffset + 1)
4328 106
                startOffset = caretOffset
4329 106
                endOffset = caretOffset + 1
4330
            else:
4331 1555
                [string, startOffset, endOffset] = text.getTextAtOffset(
4332
                    caretOffset, atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
4333
4334
            # Sometimes we get the trailing line-feed-- remove it
4335
            #
4336 1661
            content = string.decode("UTF-8")
4337 1661
            if content[-1:] == "\n":
4338 176
                content = content[:-1]
4339
4340 1683
        return [content.encode("UTF-8"), text.caretOffset, startOffset]
4341
4342 1
    def getNodeLevel(self, obj):
4343
        """Determines the node level of this object if it is in a tree
4344
        relation, with 0 being the top level node.  If this object is
4345
        not in a tree relation, then -1 will be returned.
4346
4347
        Arguments:
4348
        -obj: the Accessible object
4349
        """
4350
4351 1940
        if not obj:
4352 32
            return -1
4353
4354 1908
        nodes = []
4355 1908
        node = obj
4356 1908
        done = False
4357 4020
        while not done:
4358 2238
            relations = node.relations
4359 2112
            node = None
4360 2219
            for relation in relations:
4361 437
                if relation.getRelationType() \
4362
                       == atspi.Accessibility.RELATION_NODE_CHILD_OF:
4363 330
                    node = atspi.Accessible.makeAccessible(relation.getTarget(0))
4364 330
                    break
4365
4366
            # We want to avoid situations where something gives us an
4367
            # infinite cycle of nodes.  Bon Echo has been seen to do
4368
            # this (see bug 351847).
4369
            #
4370 2112
            if (len(nodes) > 100) or nodes.count(node):
4371 0
                debug.println(debug.LEVEL_WARNING,
4372 0
                              "Script.getNodeLevel detected a cycle!!!")
4373 0
                done = True
4374 2112
            elif node:
4375 330
                nodes.append(node)
4376 330
                debug.println(debug.LEVEL_FINEST,
4377 330
                              "Script.getNodeLevel %d" % len(nodes))
4378
            else:
4379 1782
                done = True
4380
4381 1782
        return len(nodes) - 1
4382
4383 1
    def getAcceleratorAndShortcut(self, obj):
4384
        """Gets the accelerator string (and possibly shortcut) for the given
4385
        object.
4386
4387
        Arguments:
4388
        - obj: the Accessible object
4389
4390
        A list containing the accelerator and shortcut for the given object,
4391
        where the first element is the accelerator and the second element is
4392
        the shortcut.
4393
        """
4394
4395 472
        action = obj.action
4396
4397 472
        if not action:
4398 32
            return ["", ""]
4399
4400
        # [[[TODO: WDW - assumes the first keybinding is all that we care about.
4401
        # Logged as bugzilla bug 319741.]]]
4402
        #
4403 440
        bindingStrings = action.getKeyBinding(0).split(';')
4404
4405
        # [[[TODO: WDW - assumes menu items have three bindings.  Logged as
4406
        # bugzilla bug 319741.]]]
4407
        #
4408 440
        if len(bindingStrings) == 3:
4409
            #mnemonic       = bindingStrings[0]
4410 368
            fullShortcut   = bindingStrings[1]
4411 368
            accelerator    = bindingStrings[2]
4412 72
        elif len(bindingStrings) > 0:
4413 72
            fullShortcut   = bindingStrings[0]
4414 72
            accelerator    = ""
4415
        else:
4416 0
            fullShortcut   = ""
4417 0
            accelerator    = ""
4418
4419 440
        fullShortcut = fullShortcut.replace("<","")
4420 440
        fullShortcut = fullShortcut.replace(">"," ")
4421 440
        fullShortcut = fullShortcut.replace(":"," ")
4422
4423
        # If the accelerator string includes a Space, make sure we speak it.
4424
        #
4425 440
        if accelerator.endswith(" "):
4426 0
            accelerator += "space"
4427 440
        accelerator  = accelerator.replace("<","")
4428 440
        accelerator  = accelerator.replace(">"," ")
4429
4430 440
        return [accelerator, fullShortcut]
4431
4432 1
    def getKnownApplications(self):
4433
        """Retrieves the list of currently running apps for the desktop
4434
        as a list of Accessible objects.
4435
        """
4436
4437 1
        debug.println(debug.LEVEL_FINEST,
4438 1
                      "Script.getKnownApplications...")
4439
4440 1
        apps = []
4441 1
        registry = atspi.Registry()
4442 13
        for i in range(0, registry.desktop.childCount):
4443 12
            try:
4444 12
                acc = registry.desktop.getChildAtIndex(i)
4445 12
                app = atspi.Accessible.makeAccessible(acc)
4446 12
                if app:
4447 12
                    apps.insert(0, app)
4448 0
            except:
4449 0
                debug.printException(debug.LEVEL_FINEST)
4450
4451 1
        debug.println(debug.LEVEL_FINEST,
4452 1
                      "...Script.getKnownApplications")
4453
4454 1
        return apps
4455
4456 1
    def getObjects(self, root, onlyShowing=True):
4457
        """Returns a list of all objects under the given root.  Objects
4458
        are returned in no particular order - this function does a simple
4459
        tree traversal, ignoring the children of objects which report the
4460
        MANAGES_DESCENDANTS state.
4461
4462
        Arguments:
4463
        - root:        the Accessible object to traverse
4464
        - onlyShowing: examine only those objects that are SHOWING
4465
4466
        Returns: a list of all objects under the specified object
4467
        """
4468
4469
        # The list of object we'll return
4470
        #
4471 227
        objlist = []
4472
4473
        # Start at the first child of the given object
4474
        #
4475 227
        if root.childCount <= 0:
4476 0
            return objlist
4477
4478 670
        for i in range(0, root.childCount):
4479 443
            debug.println(debug.LEVEL_FINEST,
4480 443
                          "Script.getObjects looking at child %d" % i)
4481 443
            child = root.child(i)
4482 443
            if child \
4483
               and ((not onlyShowing) or (onlyShowing and \
4484
                    (child.state.count(atspi.Accessibility.STATE_SHOWING)))):
4485 392
                objlist.append(child)
4486 392
                if (child.state.count( \
4487
                    atspi.Accessibility.STATE_MANAGES_DESCENDANTS) == 0) \
4488
                    and (child.childCount > 0):
4489 213
                    objlist.extend(self.getObjects(child))
4490
4491 227
        return objlist
4492
4493 1
    def findByRole(self, root, role, onlyShowing=True):
4494
        """Returns a list of all objects of a specific role beneath the
4495
        given root.  [[[TODO: MM - This is very inefficient - this should
4496
        do it's own traversal and not add objects to the list that aren't
4497
        of the specified role.  Instead it uses the traversal from
4498
        getObjects and then deletes objects from the list that aren't of
4499
        the specified role.  Logged as bugzilla bug 319740.]]]
4500
4501
        Arguments:
4502
        - root the Accessible object to traverse
4503
        - role the string describing the Accessible role of the object
4504
        - onlyShowing: examine only those objects that are SHOWING
4505
4506
        Returns a list of descendants of the root that are of the given role.
4507
        """
4508
4509 14
        objlist = []
4510 14
        allobjs = self.getObjects(root, onlyShowing)
4511 406
        for o in allobjs:
4512 392
            if o.role == role:
4513 77
                objlist.append(o)
4514 14
        return objlist
4515
4516 1
    def findUnrelatedLabels(self, root):
4517
        """Returns a list containing all the unrelated (i.e., have no
4518
        relations to anything and are not a fundamental element of a
4519
        more atomic component like a combo box) labels under the given
4520
        root.  Note that the labels must also be showing on the display.
4521
4522
        Arguments:
4523
        - root the Accessible object to traverse
4524
4525
        Returns a list of unrelated labels under the given root.
4526
        """
4527
4528
        # Find all the labels in the dialog
4529
        #
4530 14
        allLabels = self.findByRole(root, rolenames.ROLE_LABEL)
4531
4532
        # add the names of only those labels which are not associated with
4533
        # other objects (i.e., empty relation sets).
4534
        #
4535
        # [[[WDW - HACK: In addition, do not grab free labels whose
4536
        # parents are push buttons because push buttons can have labels as
4537
        # children.]]]
4538
        #
4539
        # [[[WDW - HACK: panels with labelled borders will have a child
4540
        # label that does not have its relation set.  So...we check to see
4541
        # if the panel's name is the same as the label's name.  If so, we
4542
        # ignore the label.]]]
4543
        #
4544 14
        unrelatedLabels = []
4545
4546 91
        for label in allLabels:
4547 77
            relations = label.relations
4548 77
            if len(relations) == 0:
4549 42
                parent = label.parent
4550 42
                if parent and (parent.role == rolenames.ROLE_PUSH_BUTTON):
4551 12
                    pass
4552 30
                elif parent and (parent.role == rolenames.ROLE_PANEL) \
4553
                   and (parent.name == label.name):
4554 0
                    pass
4555 30
                elif label.state.count(atspi.Accessibility.STATE_SHOWING):
4556 30
                    unrelatedLabels.append(label)
4557
4558
        # Now sort the labels based on their geographic position, top to
4559
        # bottom, left to right.  This is a very inefficient sort, but the
4560
        # assumption here is that there will not be a lot of labels to
4561
        # worry about.
4562
        #
4563 14
        sortedLabels = []
4564 44
        for label in unrelatedLabels:
4565 30
            index = 0
4566 50
            for sortedLabel in sortedLabels:
4567 26
                if (label.extents.y > sortedLabel.extents.y) \
4568
                   or ((label.extents.y == sortedLabel.extents.y) \
4569
                       and (label.extents.x > sortedLabel.extents.x)):
4570 20
                    index += 1
4571
                else:
4572 6
                    break
4573 30
            sortedLabels.insert(index, label)
4574
4575 14
        return sortedLabels
4576
4577 1
    def phoneticSpellCurrentItem(self, string):
4578
        """Phonetically spell the current flat review word or line.
4579
4580
        Arguments:
4581
        - string: the string to phonetically spell.
4582
        """
4583
4584 0
        for (index, character) in enumerate(string.decode("UTF-8")):
4585 0
            if character.isupper():
4586 0
                voice = settings.voices[settings.UPPERCASE_VOICE]
4587 0
                character = character.lower()
4588
            else:
4589 0
                voice =  settings.voices[settings.DEFAULT_VOICE]
4590 0
            string = phonnames.getPhoneticName(character)
4591 0
            speech.speak(string, voice)
4592
4593 1
    def printAncestry(self, child):
4594
       """Prints a hierarchical view of a child's ancestry."""
4595
4596 0
       if not child:
4597 0
           return
4598
4599 0
       ancestorList = [child]
4600 0
       parent = child.parent
4601 0
       while parent and (parent.parent != parent):
4602 0
          ancestorList.insert(0, parent)
4603 0
          parent = parent.parent
4604
4605 0
       indent = ""
4606 0
       for ancestor in ancestorList:
4607 0
          print ancestor.toString(indent + "+-", False)
4608 0
          indent += "  "
4609
4610 1
    def printHierarchy(self, root, ooi, indent="", 
4611
                       onlyShowing=True, omitManaged=True):
4612
        """Prints the accessible hierarchy of all children
4613
4614
        Arguments:
4615
        -indent:      Indentation string
4616
        -root:        Accessible where to start
4617
        -ooi:         Accessible object of interest
4618
        -onlyShowing: If True, only show children painted on the screen
4619
        -omitManaged: If True, omit children that are managed descendants
4620
        """
4621
4622 0
        if not root:
4623 0
            return
4624
4625 0
        if root == ooi:
4626 0
           print root.toString(indent + "(*)", False)
4627
        else:
4628 0
           print root.toString(indent + "+-", False)
4629
4630 0
        rootManagesDescendants = root.state.count(\
4631
            atspi.Accessibility.STATE_MANAGES_DESCENDANTS)
4632
4633 0
        for i in range(0, root.childCount):
4634 0
            child = root.child(i)
4635 0
            if child == root:
4636 0
                print indent + "  " + "WARNING CHILD == PARENT!!!"
4637 0
            elif not child:
4638 0
                print indent + "  " + "WARNING CHILD IS NONE!!!"
4639 0
            elif child.parent != root:
4640 0
                print indent + "  " + "WARNING CHILD.PARENT != PARENT!!!"
4641
            else:
4642 0
                paint = (not onlyShowing) or (onlyShowing and \
4643
                         child.state.count(atspi.Accessibility.STATE_SHOWING))
4644 0
                paint = paint \
4645
                        and ((not omitManaged) \
4646
                             or (omitManaged and not rootManagesDescendants))
4647
4648 0
                if paint:
4649 0
                   self.printHierarchy(child,
4650 0
                                       ooi,
4651 0
                                       indent + "  ",
4652 0
                                       onlyShowing,
4653 0
                                       omitManaged)
4654
4655 1
    def printApps(self):
4656
        """Prints a list of all applications to stdout."""
4657
4658 0
        level = debug.LEVEL_OFF
4659
4660 0
        apps = self.getKnownApplications()
4661 0
        debug.println(level, "There are %d Accessible applications" % len(apps))
4662 0
        for app in apps:
4663 0
            debug.printDetails(level, "  App: ", app, False)
4664 0
            for i in range(0, app.childCount):
4665 0
                child = app.child(i)
4666 0
                debug.printDetails(level, "    Window: ", child, False)
4667 0
                if child.parent != app:
4668 0
                    debug.println(level,
4669 0
                                  "      WARNING: child's parent is not app!!!")
4670
4671 0
        return True
4672
4673 1
    def printActiveApp(self):
4674
        """Prints the active application."""
4675
4676 0
        level = debug.LEVEL_OFF
4677
4678 0
        window = self.findActiveWindow()
4679 0
        if not window:
4680 0
            debug.println(level, "Active application: None")
4681
        else:
4682 0
            app = window.app
4683 0
            if not app:
4684 0
                debug.println(level, "Active application: None")
4685
            else:
4686 0
                debug.println(level, "Active application: %s" % app.name)
4687
4688 1
    def isInActiveApp(self, obj):
4689
        """Returns True if the given object is from the same application that
4690
        currently has keyboard focus.
4691
4692
        Arguments:
4693
        - obj: an Accessible object
4694
        """
4695
4696 0
        if not obj:
4697 0
            return False
4698
        else:
4699 0
            return orca_state.locusOfFocus \
4700
                   and (orca_state.locusOfFocus.app == obj.app)
4701
4702 1
    def findActiveWindow(self):
4703
        """Traverses the list of known apps looking for one who has an
4704
        immediate child (i.e., a window) whose state includes the active state.
4705
4706
        Returns the Python Accessible of the window that's active or None if
4707
        no windows are active.
4708
        """
4709
4710 1
        window = None
4711 1
        apps = self.getKnownApplications()
4712 13
        for app in apps:
4713 16
            for i in range(0, app.childCount):
4714 4
                try:
4715 4
                    state = app.child(i).state
4716 4
                    if state.count(atspi.Accessibility.STATE_ACTIVE) > 0:
4717 0
                        window = app.child(i)
4718 0
                        break
4719 0
                except:
4720 0
                    debug.printException(debug.LEVEL_FINEST)
4721
4722 1
        return window
4723
4724 1
    def saveOldAppSettings(self):
4725
        """Save a copy of all the existing application specific settings
4726
        (as specified by the settings.userCustomizableSettings dictionary)."""
4727
4728 1
        return orca_prefs.readPreferences()
4729
4730 1
    def restoreOldAppSettings(self, prefsDict):
4731
        """Restore a copy of all the previous saved application settings.
4732
4733
        Arguments:
4734
        - prefsDict: the dictionary containing the old application settings.
4735
        """
4736
4737 8299
        for key in settings.userCustomizableSettings:
4738 8106
            if prefsDict.has_key(key):
4739 8106
                settings.__dict__[key] = prefsDict[key]
4740
4741
    ########################################################################
4742
    #                                                                      #
4743
    # METHODS FOR DRAWING RECTANGLES AROUND OBJECTS ON THE SCREEN          #
4744
    #                                                                      #
4745
    ########################################################################
4746
4747 1
    def drawOutline(self, x, y, width, height, erasePrevious=True):
4748
        """Draws a rectangular outline around the accessible, erasing the
4749
        last drawn rectangle in the process."""
4750
4751 45
        if not self._display:
4752 1
            try:
4753 1
                self._display = gtk.gdk.display_get_default()
4754 0
            except:
4755 0
                debug.printException(debug.LEVEL_FINEST)
4756 0
                self._display = gtk.gdk.display(":0")
4757
4758 1
            if not self._display:
4759 0
                debug.println(debug.LEVEL_SEVERE,
4760 0
                              "Script.drawOutline could not open display.")
4761 0
                return
4762
4763 45
        screen = self._display.get_default_screen()
4764 45
        root_window = screen.get_root_window()
4765 45
        graphics_context = root_window.new_gc()
4766 45
        graphics_context.set_subwindow(gtk.gdk.INCLUDE_INFERIORS)
4767 45
        graphics_context.set_function(gtk.gdk.INVERT)
4768 45
        graphics_context.set_line_attributes(3,                  # width
4769 45
                                             gtk.gdk.LINE_SOLID, # style
4770 45
                                             gtk.gdk.CAP_BUTT,   # end style
4771 45
                                             gtk.gdk.JOIN_MITER) # join style
4772
4773
        # Erase the old rectangle.
4774
        #
4775 45
        if self._visibleRectangle and erasePrevious:
4776 22
            self.drawOutline(self._visibleRectangle[0], 
4777 22
                             self._visibleRectangle[1],
4778 22
                             self._visibleRectangle[2], 
4779 22
                             self._visibleRectangle[3], 
4780 22
                             False)
4781 22
            self._visibleRectangle = None
4782
4783
        # We'll use an invalid x value to indicate nothing should be
4784
        # drawn.
4785
        #
4786 45
        if x < 0:
4787 0
            self._visibleRectangle = None
4788 0
            return
4789
4790
        # The +1 and -2 stuff here is an attempt to stay within the
4791
        # bounding box of the object.
4792
        #
4793 45
        root_window.draw_rectangle(graphics_context,
4794 45
                                   False, # Fill
4795 45
                                   x + 1,
4796 45
                                   y + 1,
4797 45
                                   max(1, width - 2),
4798 45
                                   max(1, height - 2))
4799
4800 45
        self._visibleRectangle = [x, y, width, height]
4801
4802 1
    def outlineAccessible(self, accessible, erasePrevious=True):
4803
        """Draws a rectangular outline around the accessible, erasing the
4804
        last drawn rectangle in the process."""
4805
4806 0
        if accessible:
4807 0
            component = accessible.component
4808 0
            if component:
4809 0
                visibleRectangle = component.getExtents(0) # coord type = screen
4810 0
                self.drawOutline(visibleRectangle.x, visibleRectangle.y,
4811 0
                            visibleRectangle.width, visibleRectangle.height,
4812 0
                            erasePrevious)
4813
        else:
4814 0
            self.drawOutline(-1, 0, 0, 0, erasePrevious)
4815
4816 1
    def isTextSelected(self, obj, startOffset, endOffset):
4817
        """Returns an indication of whether the text is selected by
4818
        comparing the text offset with the various selected regions of
4819
        text for this accessible object.
4820
4821
        Arguments:
4822
        - obj: the Accessible object.
4823
        - startOffset: text start offset.
4824
        - endOffset: text end offset.
4825
4826
        Returns an indication of whether the text is selected.
4827
        """
4828
4829 82
        if not obj or not obj.text:
4830 0
            return False
4831
4832 82
        text = obj.text
4833 82
        for i in range(0, text.getNSelections()):
4834 0
            [startSelOffset, endSelOffset] = text.getSelection(i)
4835 0
            if (startOffset >= startSelOffset) \
4836
               and (endOffset <= endSelOffset):
4837 0
                return True
4838
4839 82
        return False
4840
4841 1
    def speakTextSelectionState(self, obj, startOffset, endOffset):
4842
        """Speak "selected" if the text was just selected, "unselected" if
4843
        it was just unselected.
4844
4845
        Arguments:
4846
        - obj: the Accessible object.
4847
        - startOffset: text start offset.
4848
        - endOffset: text end offset.
4849
        """
4850
4851 82
        if not obj or not obj.text:
4852 0
            return
4853
4854
        # Handle special cases.
4855
        #
4856
        # Shift-Page-Down:    speak "page selected from cursor position".
4857
        # Shift-Page-Up:      speak "page selected to cursor position".
4858
        #
4859
        # Control-Shift-Down: speak "line selected down from cursor position".
4860
        # Control-Shift-Up:   speak "line selected up from cursor position".
4861
        #
4862
        # Control-Shift-Home: speak "document selected to cursor position".
4863
        # Control-Shift-End:  speak "document selected from cursor position".
4864
        #
4865 82
        if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
4866 82
            eventStr = orca_state.lastInputEvent.event_string
4867 82
            mods = orca_state.lastInputEvent.modifiers
4868
        else:
4869 0
            eventStr = None
4870 0
            mods = 0
4871
4872 82
        isControlKey = mods & (1 << atspi.Accessibility.MODIFIER_CONTROL)
4873 82
        isShiftKey = mods & (1 << atspi.Accessibility.MODIFIER_SHIFT)
4874
4875 82
        specialCaseFound = False
4876 82
        if (eventStr == "Page_Down") and isShiftKey and not isControlKey:
4877 0
            specialCaseFound = True
4878 0
            line = _("page selected from cursor position")
4879
4880 82
        elif (eventStr == "Page_Up") and isShiftKey and not isControlKey:
4881 0
            specialCaseFound = True
4882 0
            line = _("page selected to cursor position")
4883
4884 82
        elif (eventStr == "Down") and isShiftKey and isControlKey:
4885 0
            specialCaseFound = True
4886 0
            line = _("line selected down from cursor position")
4887
4888 82
        elif (eventStr == "Up") and isShiftKey and isControlKey:
4889 0
            specialCaseFound = True
4890 0
            line = _("line selected up from cursor position")
4891
4892 82
        elif (eventStr == "Home") and isShiftKey and isControlKey:
4893 0
            specialCaseFound = True
4894 0
            line = _("document selected to cursor position")
4895
4896 82
        elif (eventStr == "End") and isShiftKey and isControlKey:
4897 0
            specialCaseFound = True
4898 0
            line = _("document selected from cursor position")
4899
4900 82
        if specialCaseFound:
4901 0
            speech.speak(line, None, False)
4902 0
            return
4903
4904 82
        try:
4905
            # If we are selecting by word, then there possibly will be 
4906
            # whitespace characters on either end of the text. We adjust 
4907
            # the startOffset and endOffset to exclude them.
4908
            #
4909 82
            try:
4910 82
                str = obj.text.getText(startOffset, endOffset).decode("UTF-8")
4911 0
            except:
4912 0
                str = u''
4913 82
            n = len(str)
4914
4915
            # Don't strip whitespace if string length is one (might be a 
4916
            # space).
4917
            #
4918 82
            if n > 1:
4919 24
                while endOffset > startOffset:
4920 24
                    if self.isWordDelimiter(str[n-1]):
4921 17
                        n -= 1
4922 17
                        endOffset -= 1
4923
                    else:
4924 7
                        break
4925 7
                n = 0
4926 7
                while startOffset < endOffset:
4927 7
                    if self.isWordDelimiter(str[n]):
4928 0
                        n += 1
4929 0
                        startOffset += 1
4930
                    else:
4931 7
                        break
4932 0
        except:
4933 0
            debug.printException(debug.LEVEL_FINEST)
4934
4935 82
        if self.isTextSelected(obj, startOffset, endOffset):
4936 0
            speech.speak(_("selected"), None, False)
4937
        else:
4938 82
            if obj.__dict__.has_key("lastSelections"):
4939 70
                for i in range(0, len(obj.lastSelections)):
4940 0
                    startSelOffset = obj.lastSelections[0][0]
4941 0
                    endSelOffset = obj.lastSelections[0][1]
4942 0
                    if (startOffset >= startSelOffset) \
4943
                        and (endOffset <= endSelOffset):
4944 0
                        speech.speak(_("unselected"), None, False)
4945 0
                        break
4946
4947
        # Save away the current text cursor position and list of text
4948
        # selections for next time.
4949
        #
4950 82
        obj.lastCursorPosition = obj.text.caretOffset
4951 82
        obj.lastSelections = []
4952 82
        for i in range(0, obj.text.getNSelections()):
4953 0
            obj.lastSelections.append(obj.text.getSelection(i))
4954
4955
# Dictionary that defines the state changes we care about for various
4956
# objects.  The key represents the role and the value represents a list
4957
# of states that we care about.
4958
#
4959 1
state_change_notifiers = {}
4960
4961 1
state_change_notifiers[rolenames.ROLE_CHECK_MENU_ITEM] = ("checked",
4962
                                                        None)
4963 1
state_change_notifiers[rolenames.ROLE_CHECK_BOX]     = ("checked",
4964
                                                        None)
4965 1
state_change_notifiers[rolenames.ROLE_PANEL]         = ("showing",
4966
                                                        None)
4967 1
state_change_notifiers[rolenames.ROLE_LABEL]         = ("showing",
4968
                                                        None)
4969 1
state_change_notifiers[rolenames.ROLE_TOGGLE_BUTTON] = ("checked",
4970
                                                        None)
4971 1
state_change_notifiers[rolenames.ROLE_TABLE_CELL]    = ("checked",
4972
                                                        "expanded",
4973
                                                        None)