Coverage Report - orca.default

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