1# Orca
2#
3# Copyright 2004-2009 Sun Microsystems Inc.
4# Copyright 2010 Joanmarie Diggs
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the
18# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
19# Boston MA  02110-1301 USA.
20
21"""The default Script for presenting information to the user using
22both speech and Braille.  This is based primarily on the de-facto
23standard implementation of the AT-SPI, which is the GAIL support
24for GTK."""
25
26__id__        = "$Id$"
27__version__   = "$Revision$"
28__date__      = "$Date$"
29__copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \
30                "Copyright (c) 2010 Joanmarie Diggs"
31__license__   = "LGPL"
32
33import pyatspi
34import re
35import time
36
37import gi
38gi.require_version('Atspi', '2.0')
39from gi.repository import Atspi
40import orca.braille as braille
41import orca.cmdnames as cmdnames
42import orca.debug as debug
43import orca.eventsynthesizer as eventsynthesizer
44import orca.find as find
45import orca.flat_review as flat_review
46import orca.guilabels as guilabels
47import orca.input_event as input_event
48import orca.keybindings as keybindings
49import orca.messages as messages
50import orca.orca as orca
51import orca.orca_gui_commandlist as commandlist
52import orca.orca_state as orca_state
53import orca.phonnames as phonnames
54import orca.script as script
55import orca.settings as settings
56import orca.settings_manager as settings_manager
57import orca.sound as sound
58import orca.speech as speech
59import orca.speechserver as speechserver
60import orca.mouse_review as mouse_review
61import orca.notification_messages as notification_messages
62
63_settingsManager = settings_manager.getManager()
64
65########################################################################
66#                                                                      #
67# The Default script class.                                            #
68#                                                                      #
69########################################################################
70
71class Script(script.Script):
72
73    EMBEDDED_OBJECT_CHARACTER = '\ufffc'
74    NO_BREAK_SPACE_CHARACTER  = '\u00a0'
75
76    # generatorCache
77    #
78    DISPLAYED_LABEL = 'displayedLabel'
79    DISPLAYED_TEXT = 'displayedText'
80    KEY_BINDING = 'keyBinding'
81    NESTING_LEVEL = 'nestingLevel'
82    NODE_LEVEL = 'nodeLevel'
83    REAL_ACTIVE_DESCENDANT = 'realActiveDescendant'
84
85    def __init__(self, app):
86        """Creates a new script for the given application.
87
88        Arguments:
89        - app: the application to create a script for.
90        """
91        script.Script.__init__(self, app)
92
93        self.flatReviewContext  = None
94        self.windowActivateTime = None
95        self.targetCursorCell = None
96
97        self.justEnteredFlatReviewMode = False
98
99        self.digits = '0123456789'
100        self.whitespace = ' \t\n\r\v\f'
101
102        # A dictionary of non-standardly-named text attributes and their
103        # Atk equivalents.
104        #
105        self.attributeNamesDict = {}
106
107        # Keep track of the last time we issued a mouse routing command
108        # so that we can guess if a change resulted from our moving the
109        # pointer.
110        #
111        self.lastMouseRoutingTime = None
112
113        # The last location of the mouse, which we might want if routing
114        # the pointer elsewhere.
115        #
116        self.oldMouseCoordinates = [0, 0]
117
118        # Used to copy/append the current flat review contents to the
119        # clipboard.
120        #
121        self.currentReviewContents = ""
122
123        self._lastWordCheckedForSpelling = ""
124
125        self._inSayAll = False
126        self._sayAllIsInterrupted = False
127        self._sayAllContexts = []
128        self.grab_ids = []
129
130        if app:
131            app.setCacheMask(pyatspi.cache.DEFAULT ^ pyatspi.cache.NAME ^ pyatspi.cache.DESCRIPTION)
132
133    def setupInputEventHandlers(self):
134        """Defines InputEventHandler fields for this script that can be
135        called by the key and braille bindings."""
136
137        self.inputEventHandlers["routePointerToItemHandler"] = \
138            input_event.InputEventHandler(
139                Script.routePointerToItem,
140                cmdnames.ROUTE_POINTER_TO_ITEM)
141
142        self.inputEventHandlers["leftClickReviewItemHandler"] = \
143            input_event.InputEventHandler(
144                Script.leftClickReviewItem,
145                cmdnames.LEFT_CLICK_REVIEW_ITEM)
146
147        self.inputEventHandlers["rightClickReviewItemHandler"] = \
148             input_event.InputEventHandler(
149                Script.rightClickReviewItem,
150                cmdnames.RIGHT_CLICK_REVIEW_ITEM)
151
152        self.inputEventHandlers["sayAllHandler"] = \
153            input_event.InputEventHandler(
154                Script.sayAll,
155                cmdnames.SAY_ALL)
156
157        self.inputEventHandlers["flatReviewSayAllHandler"] = \
158            input_event.InputEventHandler(
159                Script.flatReviewSayAll,
160                cmdnames.SAY_ALL_FLAT_REVIEW)
161
162        self.inputEventHandlers["whereAmIBasicHandler"] = \
163            input_event.InputEventHandler(
164                Script.whereAmIBasic,
165                cmdnames.WHERE_AM_I_BASIC)
166
167        self.inputEventHandlers["whereAmIDetailedHandler"] = \
168            input_event.InputEventHandler(
169                Script.whereAmIDetailed,
170                cmdnames.WHERE_AM_I_DETAILED)
171
172        self.inputEventHandlers["whereAmILinkHandler"] = \
173            input_event.InputEventHandler(
174                Script.whereAmILink,
175                cmdnames.WHERE_AM_I_LINK)
176
177        self.inputEventHandlers["whereAmISelectionHandler"] = \
178            input_event.InputEventHandler(
179                Script.whereAmISelection,
180                cmdnames.WHERE_AM_I_SELECTION)
181
182        self.inputEventHandlers["getTitleHandler"] = \
183            input_event.InputEventHandler(
184                Script.presentTitle,
185                cmdnames.PRESENT_TITLE)
186
187        self.inputEventHandlers["getStatusBarHandler"] = \
188            input_event.InputEventHandler(
189                Script.presentStatusBar,
190                cmdnames.PRESENT_STATUS_BAR)
191
192        self.inputEventHandlers["findHandler"] = \
193            input_event.InputEventHandler(
194                orca.showFindGUI,
195                cmdnames.SHOW_FIND_GUI)
196
197        self.inputEventHandlers["findNextHandler"] = \
198            input_event.InputEventHandler(
199                Script.findNext,
200                cmdnames.FIND_NEXT)
201
202        self.inputEventHandlers["findPreviousHandler"] = \
203            input_event.InputEventHandler(
204                Script.findPrevious,
205                cmdnames.FIND_PREVIOUS)
206
207        self.inputEventHandlers["toggleFlatReviewModeHandler"] = \
208            input_event.InputEventHandler(
209                Script.toggleFlatReviewMode,
210                cmdnames.TOGGLE_FLAT_REVIEW)
211
212        self.inputEventHandlers["reviewPreviousLineHandler"] = \
213            input_event.InputEventHandler(
214                Script.reviewPreviousLine,
215                cmdnames.REVIEW_PREVIOUS_LINE)
216
217        self.inputEventHandlers["reviewHomeHandler"] = \
218            input_event.InputEventHandler(
219                Script.reviewHome,
220                cmdnames.REVIEW_HOME)
221
222        self.inputEventHandlers["reviewCurrentLineHandler"] = \
223            input_event.InputEventHandler(
224                Script.reviewCurrentLine,
225                cmdnames.REVIEW_CURRENT_LINE)
226
227        self.inputEventHandlers["reviewSpellCurrentLineHandler"] = \
228            input_event.InputEventHandler(
229                Script.reviewSpellCurrentLine,
230                cmdnames.REVIEW_SPELL_CURRENT_LINE)
231
232        self.inputEventHandlers["reviewPhoneticCurrentLineHandler"] = \
233            input_event.InputEventHandler(
234                Script.reviewPhoneticCurrentLine,
235                cmdnames.REVIEW_PHONETIC_CURRENT_LINE)
236
237        self.inputEventHandlers["reviewNextLineHandler"] = \
238            input_event.InputEventHandler(
239                Script.reviewNextLine,
240                cmdnames.REVIEW_NEXT_LINE)
241
242        self.inputEventHandlers["reviewEndHandler"] = \
243            input_event.InputEventHandler(
244                Script.reviewEnd,
245                cmdnames.REVIEW_END)
246
247        self.inputEventHandlers["reviewPreviousItemHandler"] = \
248            input_event.InputEventHandler(
249                Script.reviewPreviousItem,
250                cmdnames.REVIEW_PREVIOUS_ITEM)
251
252        self.inputEventHandlers["reviewAboveHandler"] = \
253            input_event.InputEventHandler(
254                Script.reviewAbove,
255                cmdnames.REVIEW_ABOVE)
256
257        self.inputEventHandlers["reviewCurrentItemHandler"] = \
258            input_event.InputEventHandler(
259                Script.reviewCurrentItem,
260                cmdnames.REVIEW_CURRENT_ITEM)
261
262        self.inputEventHandlers["reviewSpellCurrentItemHandler"] = \
263            input_event.InputEventHandler(
264                Script.reviewSpellCurrentItem,
265                cmdnames.REVIEW_SPELL_CURRENT_ITEM)
266
267        self.inputEventHandlers["reviewPhoneticCurrentItemHandler"] = \
268            input_event.InputEventHandler(
269                Script.reviewPhoneticCurrentItem,
270                cmdnames.REVIEW_PHONETIC_CURRENT_ITEM)
271
272        self.inputEventHandlers["reviewNextItemHandler"] = \
273            input_event.InputEventHandler(
274                Script.reviewNextItem,
275                cmdnames.REVIEW_NEXT_ITEM)
276
277        self.inputEventHandlers["reviewCurrentAccessibleHandler"] = \
278            input_event.InputEventHandler(
279                Script.reviewCurrentAccessible,
280                cmdnames.REVIEW_CURRENT_ACCESSIBLE)
281
282        self.inputEventHandlers["reviewBelowHandler"] = \
283            input_event.InputEventHandler(
284                Script.reviewBelow,
285                cmdnames.REVIEW_BELOW)
286
287        self.inputEventHandlers["reviewPreviousCharacterHandler"] = \
288            input_event.InputEventHandler(
289                Script.reviewPreviousCharacter,
290                cmdnames.REVIEW_PREVIOUS_CHARACTER)
291
292        self.inputEventHandlers["reviewEndOfLineHandler"] = \
293            input_event.InputEventHandler(
294                Script.reviewEndOfLine,
295                cmdnames.REVIEW_END_OF_LINE)
296
297        self.inputEventHandlers["reviewBottomLeftHandler"] = \
298            input_event.InputEventHandler(
299                Script.reviewBottomLeft,
300                cmdnames.REVIEW_BOTTOM_LEFT)
301
302        self.inputEventHandlers["reviewCurrentCharacterHandler"] = \
303            input_event.InputEventHandler(
304                Script.reviewCurrentCharacter,
305                cmdnames.REVIEW_CURRENT_CHARACTER)
306
307        self.inputEventHandlers["reviewSpellCurrentCharacterHandler"] = \
308            input_event.InputEventHandler(
309                Script.reviewSpellCurrentCharacter,
310                cmdnames.REVIEW_SPELL_CURRENT_CHARACTER)
311
312        self.inputEventHandlers["reviewUnicodeCurrentCharacterHandler"] = \
313            input_event.InputEventHandler(
314                Script.reviewUnicodeCurrentCharacter,
315                cmdnames.REVIEW_UNICODE_CURRENT_CHARACTER)
316
317        self.inputEventHandlers["reviewNextCharacterHandler"] = \
318            input_event.InputEventHandler(
319                Script.reviewNextCharacter,
320                cmdnames.REVIEW_NEXT_CHARACTER)
321
322        self.inputEventHandlers["flatReviewCopyHandler"] = \
323            input_event.InputEventHandler(
324                Script.flatReviewCopy,
325                cmdnames.FLAT_REVIEW_COPY)
326
327        self.inputEventHandlers["flatReviewAppendHandler"] = \
328            input_event.InputEventHandler(
329                Script.flatReviewAppend,
330                cmdnames.FLAT_REVIEW_APPEND)
331
332        self.inputEventHandlers["toggleTableCellReadModeHandler"] = \
333            input_event.InputEventHandler(
334                Script.toggleTableCellReadMode,
335                cmdnames.TOGGLE_TABLE_CELL_READ_MODE)
336
337        self.inputEventHandlers["readCharAttributesHandler"] = \
338            input_event.InputEventHandler(
339                Script.readCharAttributes,
340                cmdnames.READ_CHAR_ATTRIBUTES)
341
342        self.inputEventHandlers["panBrailleLeftHandler"] = \
343            input_event.InputEventHandler(
344                Script.panBrailleLeft,
345                cmdnames.PAN_BRAILLE_LEFT,
346                False) # Do not enable learn mode for this action
347
348        self.inputEventHandlers["panBrailleRightHandler"] = \
349            input_event.InputEventHandler(
350                Script.panBrailleRight,
351                cmdnames.PAN_BRAILLE_RIGHT,
352                False) # Do not enable learn mode for this action
353
354        self.inputEventHandlers["goBrailleHomeHandler"] = \
355            input_event.InputEventHandler(
356                Script.goBrailleHome,
357                cmdnames.GO_BRAILLE_HOME)
358
359        self.inputEventHandlers["contractedBrailleHandler"] = \
360            input_event.InputEventHandler(
361                Script.setContractedBraille,
362                cmdnames.SET_CONTRACTED_BRAILLE)
363
364        self.inputEventHandlers["processRoutingKeyHandler"] = \
365            input_event.InputEventHandler(
366                Script.processRoutingKey,
367                cmdnames.PROCESS_ROUTING_KEY)
368
369        self.inputEventHandlers["processBrailleCutBeginHandler"] = \
370            input_event.InputEventHandler(
371                Script.processBrailleCutBegin,
372                cmdnames.PROCESS_BRAILLE_CUT_BEGIN)
373
374        self.inputEventHandlers["processBrailleCutLineHandler"] = \
375            input_event.InputEventHandler(
376                Script.processBrailleCutLine,
377                cmdnames.PROCESS_BRAILLE_CUT_LINE)
378
379        self.inputEventHandlers["enterLearnModeHandler"] = \
380            input_event.InputEventHandler(
381                Script.enterLearnMode,
382                cmdnames.ENTER_LEARN_MODE)
383
384        self.inputEventHandlers["decreaseSpeechRateHandler"] = \
385            input_event.InputEventHandler(
386                speech.decreaseSpeechRate,
387                cmdnames.DECREASE_SPEECH_RATE)
388
389        self.inputEventHandlers["increaseSpeechRateHandler"] = \
390            input_event.InputEventHandler(
391                speech.increaseSpeechRate,
392                cmdnames.INCREASE_SPEECH_RATE)
393
394        self.inputEventHandlers["decreaseSpeechPitchHandler"] = \
395            input_event.InputEventHandler(
396                speech.decreaseSpeechPitch,
397                cmdnames.DECREASE_SPEECH_PITCH)
398
399        self.inputEventHandlers["increaseSpeechPitchHandler"] = \
400            input_event.InputEventHandler(
401                speech.increaseSpeechPitch,
402                cmdnames.INCREASE_SPEECH_PITCH)
403
404        self.inputEventHandlers["decreaseSpeechVolumeHandler"] = \
405            input_event.InputEventHandler(
406                speech.decreaseSpeechVolume,
407                cmdnames.DECREASE_SPEECH_VOLUME)
408
409        self.inputEventHandlers["increaseSpeechVolumeHandler"] = \
410            input_event.InputEventHandler(
411                speech.increaseSpeechVolume,
412                cmdnames.INCREASE_SPEECH_VOLUME)
413
414        self.inputEventHandlers["shutdownHandler"] = \
415            input_event.InputEventHandler(
416                orca.quitOrca,
417                cmdnames.QUIT_ORCA)
418
419        self.inputEventHandlers["preferencesSettingsHandler"] = \
420            input_event.InputEventHandler(
421                orca.showPreferencesGUI,
422                cmdnames.SHOW_PREFERENCES_GUI)
423
424        self.inputEventHandlers["appPreferencesSettingsHandler"] = \
425            input_event.InputEventHandler(
426                orca.showAppPreferencesGUI,
427                cmdnames.SHOW_APP_PREFERENCES_GUI)
428
429        self.inputEventHandlers["toggleSilenceSpeechHandler"] = \
430            input_event.InputEventHandler(
431                Script.toggleSilenceSpeech,
432                cmdnames.TOGGLE_SPEECH)
433
434        self.inputEventHandlers["toggleSpeechVerbosityHandler"] = \
435            input_event.InputEventHandler(
436                Script.toggleSpeechVerbosity,
437                cmdnames.TOGGLE_SPEECH_VERBOSITY)
438
439        self.inputEventHandlers[ \
440          "toggleSpeakingIndentationJustificationHandler"] = \
441            input_event.InputEventHandler(
442                Script.toggleSpeakingIndentationJustification,
443                cmdnames.TOGGLE_SPOKEN_INDENTATION_AND_JUSTIFICATION)
444
445        self.inputEventHandlers[ \
446          "changeNumberStyleHandler"] = \
447            input_event.InputEventHandler(
448                Script.changeNumberStyle,
449                cmdnames.CHANGE_NUMBER_STYLE)
450
451        self.inputEventHandlers["cycleSpeakingPunctuationLevelHandler"] = \
452            input_event.InputEventHandler(
453                Script.cycleSpeakingPunctuationLevel,
454                cmdnames.CYCLE_PUNCTUATION_LEVEL)
455
456        self.inputEventHandlers["cycleSettingsProfileHandler"] = \
457            input_event.InputEventHandler(
458                Script.cycleSettingsProfile,
459                cmdnames.CYCLE_SETTINGS_PROFILE)
460
461        self.inputEventHandlers["cycleCapitalizationStyleHandler"] = \
462            input_event.InputEventHandler(
463                Script.cycleCapitalizationStyle,
464                cmdnames.CYCLE_CAPITALIZATION_STYLE)
465
466        self.inputEventHandlers["cycleKeyEchoHandler"] = \
467            input_event.InputEventHandler(
468                Script.cycleKeyEcho,
469                cmdnames.CYCLE_KEY_ECHO)
470
471        self.inputEventHandlers["cycleDebugLevelHandler"] = \
472            input_event.InputEventHandler(
473                Script.cycleDebugLevel,
474                cmdnames.CYCLE_DEBUG_LEVEL)
475
476        self.inputEventHandlers["goToPrevBookmark"] = \
477            input_event.InputEventHandler(
478                Script.goToPrevBookmark,
479                cmdnames.BOOKMARK_GO_TO_PREVIOUS)
480
481        self.inputEventHandlers["goToBookmark"] = \
482            input_event.InputEventHandler(
483                Script.goToBookmark,
484                cmdnames.BOOKMARK_GO_TO)
485
486        self.inputEventHandlers["goToNextBookmark"] = \
487            input_event.InputEventHandler(
488                Script.goToNextBookmark,
489                cmdnames.BOOKMARK_GO_TO_NEXT)
490
491        self.inputEventHandlers["addBookmark"] = \
492            input_event.InputEventHandler(
493                Script.addBookmark,
494                cmdnames.BOOKMARK_ADD)
495
496        self.inputEventHandlers["saveBookmarks"] = \
497            input_event.InputEventHandler(
498                Script.saveBookmarks,
499                cmdnames.BOOKMARK_SAVE)
500
501        self.inputEventHandlers["toggleMouseReviewHandler"] = \
502            input_event.InputEventHandler(
503                mouse_review.reviewer.toggle,
504                cmdnames.MOUSE_REVIEW_TOGGLE)
505
506        self.inputEventHandlers["presentTimeHandler"] = \
507            input_event.InputEventHandler(
508                Script.presentTime,
509                cmdnames.PRESENT_CURRENT_TIME)
510
511        self.inputEventHandlers["presentDateHandler"] = \
512            input_event.InputEventHandler(
513                Script.presentDate,
514                cmdnames.PRESENT_CURRENT_DATE)
515
516        self.inputEventHandlers["bypassNextCommandHandler"] = \
517            input_event.InputEventHandler(
518                Script.bypassNextCommand,
519                cmdnames.BYPASS_NEXT_COMMAND)
520
521        self.inputEventHandlers["presentSizeAndPositionHandler"] = \
522            input_event.InputEventHandler(
523                Script.presentSizeAndPosition,
524                cmdnames.PRESENT_SIZE_AND_POSITION)
525
526        self.inputEventHandlers.update(notification_messages.inputEventHandlers)
527
528    def getInputEventHandlerKey(self, inputEventHandler):
529        """Returns the name of the key that contains an inputEventHadler
530        passed as argument
531        """
532
533        for keyName, handler in self.inputEventHandlers.items():
534            if handler == inputEventHandler:
535                return keyName
536
537        return None
538
539    def getListeners(self):
540        """Sets up the AT-SPI event listeners for this script.
541        """
542        listeners = script.Script.getListeners(self)
543        listeners["focus:"]                                 = \
544            self.onFocus
545        #listeners["keyboard:modifiers"]                     = \
546        #    self.noOp
547        listeners["document:reload"]                        = \
548            self.onDocumentReload
549        listeners["document:load-complete"]                 = \
550            self.onDocumentLoadComplete
551        listeners["document:load-stopped"]                  = \
552            self.onDocumentLoadStopped
553        listeners["mouse:button"]                           = \
554            self.onMouseButton
555        listeners["object:property-change:accessible-name"] = \
556            self.onNameChanged
557        listeners["object:property-change:accessible-description"] = \
558            self.onDescriptionChanged
559        listeners["object:text-caret-moved"]                = \
560            self.onCaretMoved
561        listeners["object:text-changed:delete"]             = \
562            self.onTextDeleted
563        listeners["object:text-changed:insert"]             = \
564            self.onTextInserted
565        listeners["object:active-descendant-changed"]       = \
566            self.onActiveDescendantChanged
567        listeners["object:children-changed:add"]            = \
568            self.onChildrenAdded
569        listeners["object:children-changed:remove"]         = \
570            self.onChildrenRemoved
571        listeners["object:state-changed:active"]            = \
572            self.onActiveChanged
573        listeners["object:state-changed:busy"]              = \
574            self.onBusyChanged
575        listeners["object:state-changed:focused"]           = \
576            self.onFocusedChanged
577        listeners["object:state-changed:showing"]           = \
578            self.onShowingChanged
579        listeners["object:state-changed:checked"]           = \
580            self.onCheckedChanged
581        listeners["object:state-changed:pressed"]           = \
582            self.onPressedChanged
583        listeners["object:state-changed:indeterminate"]     = \
584            self.onIndeterminateChanged
585        listeners["object:state-changed:expanded"]          = \
586            self.onExpandedChanged
587        listeners["object:state-changed:selected"]          = \
588            self.onSelectedChanged
589        listeners["object:state-changed:sensitive"]         = \
590            self.onSensitiveChanged
591        listeners["object:text-attributes-changed"]         = \
592            self.onTextAttributesChanged
593        listeners["object:text-selection-changed"]          = \
594            self.onTextSelectionChanged
595        listeners["object:selection-changed"]               = \
596            self.onSelectionChanged
597        listeners["object:property-change:accessible-value"] = \
598            self.onValueChanged
599        listeners["object:value-changed"]                   = \
600            self.onValueChanged
601        listeners["object:column-reordered"]                = \
602            self.onColumnReordered
603        listeners["object:row-reordered"]                   = \
604            self.onRowReordered
605        listeners["window:activate"]                        = \
606            self.onWindowActivated
607        listeners["window:deactivate"]                      = \
608            self.onWindowDeactivated
609        listeners["window:create"]                          = \
610            self.onWindowCreated
611        listeners["window:destroy"]                          = \
612            self.onWindowDestroyed
613
614        return listeners
615
616    def __getDesktopBindings(self):
617        """Returns an instance of keybindings.KeyBindings that use the
618        numeric keypad for focus tracking and flat review.
619        """
620
621        import orca.desktop_keyboardmap as desktop_keyboardmap
622        keyBindings = keybindings.KeyBindings()
623        keyBindings.load(desktop_keyboardmap.keymap, self.inputEventHandlers)
624        return keyBindings
625
626    def __getLaptopBindings(self):
627        """Returns an instance of keybindings.KeyBindings that use the
628        the main keyboard keys for focus tracking and flat review.
629        """
630
631        import orca.laptop_keyboardmap as laptop_keyboardmap
632        keyBindings = keybindings.KeyBindings()
633        keyBindings.load(laptop_keyboardmap.keymap, self.inputEventHandlers)
634        return keyBindings
635
636    def getKeyBindings(self):
637        """Defines the key bindings for this script.
638
639        Returns an instance of keybindings.KeyBindings.
640        """
641
642        keyBindings = script.Script.getKeyBindings(self)
643
644        bindings = self.getDefaultKeyBindings()
645        for keyBinding in bindings.keyBindings:
646            keyBindings.add(keyBinding)
647
648        bindings = self.getToolkitKeyBindings()
649        for keyBinding in bindings.keyBindings:
650            keyBindings.add(keyBinding)
651
652        bindings = self.getAppKeyBindings()
653        for keyBinding in bindings.keyBindings:
654            keyBindings.add(keyBinding)
655
656        try:
657            keyBindings = _settingsManager.overrideKeyBindings(self, keyBindings)
658        except:
659            msg = 'ERROR: Exception when overriding keybindings in %s' % self
660            debug.println(debug.LEVEL_WARNING, msg, True)
661            debug.printException(debug.LEVEL_WARNING)
662
663        return keyBindings
664
665    def getDefaultKeyBindings(self):
666        """Returns the default script's keybindings, i.e. without any of
667        the toolkit or application specific commands added."""
668
669        keyBindings = keybindings.KeyBindings()
670
671        layout = _settingsManager.getSetting('keyboardLayout')
672        if layout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP:
673            for keyBinding in self.__getDesktopBindings().keyBindings:
674                keyBindings.add(keyBinding)
675        else:
676            for keyBinding in self.__getLaptopBindings().keyBindings:
677                keyBindings.add(keyBinding)
678
679        import orca.common_keyboardmap as common_keyboardmap
680        keyBindings.load(common_keyboardmap.keymap, self.inputEventHandlers)
681
682        return keyBindings
683
684    def getBrailleBindings(self):
685        """Defines the braille bindings for this script.
686
687        Returns a dictionary where the keys are BrlTTY commands and the
688        values are InputEventHandler instances.
689        """
690
691        msg = 'DEFAULT: Getting braille bindings.'
692        debug.println(debug.LEVEL_INFO, msg, True)
693
694        brailleBindings = script.Script.getBrailleBindings(self)
695        try:
696            brailleBindings[braille.brlapi.KEY_CMD_HWINLT]     = \
697                self.inputEventHandlers["panBrailleLeftHandler"]
698            brailleBindings[braille.brlapi.KEY_CMD_FWINLT]     = \
699                self.inputEventHandlers["panBrailleLeftHandler"]
700            brailleBindings[braille.brlapi.KEY_CMD_FWINLTSKIP] = \
701                self.inputEventHandlers["panBrailleLeftHandler"]
702            brailleBindings[braille.brlapi.KEY_CMD_HWINRT]     = \
703                self.inputEventHandlers["panBrailleRightHandler"]
704            brailleBindings[braille.brlapi.KEY_CMD_FWINRT]     = \
705                self.inputEventHandlers["panBrailleRightHandler"]
706            brailleBindings[braille.brlapi.KEY_CMD_FWINRTSKIP] = \
707                self.inputEventHandlers["panBrailleRightHandler"]
708            brailleBindings[braille.brlapi.KEY_CMD_LNUP]       = \
709                self.inputEventHandlers["reviewAboveHandler"]
710            brailleBindings[braille.brlapi.KEY_CMD_LNDN]       = \
711                self.inputEventHandlers["reviewBelowHandler"]
712            brailleBindings[braille.brlapi.KEY_CMD_FREEZE]     = \
713                self.inputEventHandlers["toggleFlatReviewModeHandler"]
714            brailleBindings[braille.brlapi.KEY_CMD_TOP_LEFT]   = \
715                self.inputEventHandlers["reviewHomeHandler"]
716            brailleBindings[braille.brlapi.KEY_CMD_BOT_LEFT]   = \
717                self.inputEventHandlers["reviewBottomLeftHandler"]
718            brailleBindings[braille.brlapi.KEY_CMD_HOME]       = \
719                self.inputEventHandlers["goBrailleHomeHandler"]
720            brailleBindings[braille.brlapi.KEY_CMD_SIXDOTS]     = \
721                self.inputEventHandlers["contractedBrailleHandler"]
722            brailleBindings[braille.brlapi.KEY_CMD_ROUTE]     = \
723                self.inputEventHandlers["processRoutingKeyHandler"]
724            brailleBindings[braille.brlapi.KEY_CMD_CUTBEGIN]   = \
725                self.inputEventHandlers["processBrailleCutBeginHandler"]
726            brailleBindings[braille.brlapi.KEY_CMD_CUTLINE]   = \
727                self.inputEventHandlers["processBrailleCutLineHandler"]
728        except AttributeError:
729            msg = 'DEFAULT: Braille bindings unavailable in %s' % self
730            debug.println(debug.LEVEL_INFO, msg, True)
731        except:
732            msg = 'ERROR: Exception getting braille bindings in %s' % self
733            debug.println(debug.LEVEL_INFO, msg, True)
734            debug.printException(debug.LEVEL_CONFIGURATION)
735
736        msg = 'DEFAULT: Finished getting braille bindings.'
737        debug.println(debug.LEVEL_INFO, msg, True)
738
739        return brailleBindings
740
741    def deactivate(self):
742        """Called when this script is deactivated."""
743
744        self._inSayAll = False
745        self._sayAllIsInterrupted = False
746        self.pointOfReference = {}
747
748        self.removeKeyGrabs()
749
750    def getEnabledKeyBindings(self):
751        """ Returns the key bindings that are currently active. """
752        return self.getKeyBindings().getBoundBindings()
753
754    def addKeyGrabs(self):
755        """ Sets up the key grabs currently needed by this script. """
756        if orca_state.device is None:
757            return
758        msg = "INFO: adding key grabs"
759        debug.println(debug.LEVEL_INFO, msg, True)
760        bound = self.getEnabledKeyBindings()
761        for b in bound:
762            for id in orca.addKeyGrab(b):
763                self.grab_ids.append(id)
764
765    def removeKeyGrabs(self):
766        """ Removes this script's AT-SPI key grabs. """
767        msg = "INFO: removing key grabs"
768        debug.println(debug.LEVEL_INFO, msg, True)
769        for id in self.grab_ids:
770            orca.removeKeyGrab(id)
771        self.grab_ids = []
772
773    def refreshKeyGrabs(self):
774        """ Refreshes the enabled key grabs for this script. """
775        # TODO: Should probably avoid removing key grabs and re-adding them.
776        # Otherwise, a key could conceivably leak through while the script is
777        # in the process of updating the bindings.
778        self.removeKeyGrabs()
779        self.addKeyGrabs()
780
781    def registerEventListeners(self):
782        super().registerEventListeners()
783        self.utilities.connectToClipboard()
784
785    def deregisterEventListeners(self):
786        super().deregisterEventListeners()
787        self.utilities.disconnectFromClipboard()
788
789    def _saveFocusedObjectInfo(self, obj):
790        """Saves some basic information about obj. Note that this method is
791        intended to be called primarily (if not only) by locusOfFocusChanged().
792        It is expected that accessible event callbacks will update the point
793        of reference data specific to that event. The goal here is to weed
794        out duplicate events."""
795
796        if not obj:
797            return
798
799        try:
800            role = obj.getRole()
801            state = obj.getState()
802            name = obj.name
803            description = obj.description
804        except:
805            return
806
807        # We want to save the name because some apps and toolkits emit name
808        # changes after the focus or selection has changed, even though the
809        # name has not.
810        names = self.pointOfReference.get('names', {})
811        names[hash(obj)] = name
812        if orca_state.activeWindow:
813            try:
814                names[hash(orca_state.activeWindow)] = orca_state.activeWindow.name
815            except:
816                msg = "ERROR: Exception getting name for %s" % orca_state.activeWindow
817                debug.println(debug.LEVEL_INFO, msg, True)
818
819        self.pointOfReference['names'] = names
820
821        descriptions = self.pointOfReference.get('descriptions', {})
822        descriptions[hash(obj)] = description
823        self.pointOfReference['descriptions'] = descriptions
824
825        # We want to save the offset for text objects because some apps and
826        # toolkits emit caret-moved events immediately after a text object
827        # gains focus, even though the caret has not actually moved.
828        try:
829            text = obj.queryText()
830            caretOffset = text.caretOffset
831        except:
832            pass
833        else:
834            self._saveLastCursorPosition(obj, max(0, caretOffset))
835            self.utilities.updateCachedTextSelection(obj)
836
837        # We want to save the current row and column of a newly focused
838        # or selected table cell so that on subsequent cell focus/selection
839        # we only present the changed location.
840        row, column = self.utilities.coordinatesForCell(obj)
841        self.pointOfReference['lastColumn'] = column
842        self.pointOfReference['lastRow'] = row
843
844        self.pointOfReference['checkedChange'] = \
845            hash(obj), state.contains(pyatspi.STATE_CHECKED)
846        self.pointOfReference['selectedChange'] = \
847            hash(obj), state.contains(pyatspi.STATE_SELECTED)
848
849    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
850        """Called when the visual object with focus changes.
851
852        Arguments:
853        - event: if not None, the Event that caused the change
854        - oldLocusOfFocus: Accessible that is the old locus of focus
855        - newLocusOfFocus: Accessible that is the new locus of focus
856        """
857
858        self.utilities.presentFocusChangeReason()
859
860        if not newLocusOfFocus:
861            orca_state.noFocusTimeStamp = time.time()
862            return
863
864        if newLocusOfFocus.getState().contains(pyatspi.STATE_DEFUNCT):
865            return
866
867        if self.utilities.isSameObject(oldLocusOfFocus, newLocusOfFocus):
868            return
869
870        try:
871            if self.findCommandRun:
872                # Then the Orca Find dialog has just given up focus
873                # to the original window.  We don't want to speak
874                # the window title, current line, etc.
875                return
876        except:
877            pass
878
879        if self.flatReviewContext:
880            self.toggleFlatReviewMode()
881
882        topLevel = self.utilities.topLevelObject(newLocusOfFocus)
883        if orca_state.activeWindow != topLevel:
884            orca_state.activeWindow = topLevel
885            self.windowActivateTime = time.time()
886
887        self.updateBraille(newLocusOfFocus)
888
889        shouldNotInterrupt = \
890           self.windowActivateTime and time.time() - self.windowActivateTime < 1
891
892        utterances = self.speechGenerator.generateSpeech(
893            newLocusOfFocus,
894            priorObj=oldLocusOfFocus)
895
896        speech.speak(utterances, interrupt=not shouldNotInterrupt)
897        orca.emitRegionChanged(newLocusOfFocus)
898        self._saveFocusedObjectInfo(newLocusOfFocus)
899
900    def activate(self):
901        """Called when this script is activated."""
902
903        msg = 'DEFAULT: activating script for %s' % self.app
904        debug.println(debug.LEVEL_INFO, msg, True)
905
906        _settingsManager.loadAppSettings(self)
907        braille.checkBrailleSetting()
908        braille.setupKeyRanges(self.brailleBindings.keys())
909        speech.checkSpeechSetting()
910        speech.updatePunctuationLevel()
911        speech.updateCapitalizationStyle()
912
913        # Gtk 4 requrns "GTK", while older versions return "gtk"
914        # TODO: move this to a toolkit-specific script
915        if self.app is not None and self.app.toolkitName == "GTK" and self.app.toolkitVersion > "4":
916            orca.setKeyHandling(True)
917        else:
918            orca.setKeyHandling(False)
919
920        self.addKeyGrabs()
921
922        msg = 'DEFAULT: Script for %s activated' % self.app
923        debug.println(debug.LEVEL_INFO, msg, True)
924
925    def updateBraille(self, obj, **args):
926        """Updates the braille display to show the give object.
927
928        Arguments:
929        - obj: the Accessible
930        """
931
932        if not _settingsManager.getSetting('enableBraille') \
933           and not _settingsManager.getSetting('enableBrailleMonitor'):
934            debug.println(debug.LEVEL_INFO, "BRAILLE: update disabled", True)
935            return
936
937        if not obj:
938            return
939
940        result, focusedRegion = self.brailleGenerator.generateBraille(obj, **args)
941        if not result:
942            return
943
944        self.clearBraille()
945        line = self.getNewBrailleLine()
946        braille.addLine(line)
947        self.addBrailleRegionsToLine(result, line)
948
949        extraRegion = args.get('extraRegion')
950        if extraRegion:
951            self.addBrailleRegionToLine(extraRegion, line)
952            self.setBrailleFocus(extraRegion)
953        else:
954            self.setBrailleFocus(focusedRegion)
955
956        self.refreshBraille(True)
957
958    ########################################################################
959    #                                                                      #
960    # INPUT EVENT HANDLERS (AKA ORCA COMMANDS)                             #
961    #                                                                      #
962    ########################################################################
963
964    def bypassNextCommand(self, inputEvent=None):
965        """Causes the next keyboard command to be ignored by Orca
966        and passed along to the current application.
967
968        Returns True to indicate the input event has been consumed.
969        """
970
971        self.presentMessage(messages.BYPASS_MODE_ENABLED)
972        orca_state.bypassNextCommand = True
973        self.removeKeyGrabs()
974        return True
975
976    def enterLearnMode(self, inputEvent=None):
977        """Turns learn mode on.  The user must press the escape key to exit
978        learn mode.
979
980        Returns True to indicate the input event has been consumed.
981        """
982
983        if orca_state.learnModeEnabled:
984            return True
985
986        self.presentMessage(messages.VERSION)
987        self.speakMessage(messages.LEARN_MODE_START_SPEECH)
988        self.displayBrailleMessage(messages.LEARN_MODE_START_BRAILLE)
989        orca_state.learnModeEnabled = True
990        if orca_state.device is not None:
991            Atspi.Device.grab_keyboard(orca_state.device)
992        return True
993
994    def exitLearnMode(self, inputEvent=None):
995        """Turns learn mode off.
996
997        Returns True to indicate the input event has been consumed.
998        """
999
1000        if not orca_state.learnModeEnabled:
1001            return False
1002
1003        if isinstance(inputEvent, input_event.KeyboardEvent) \
1004           and not inputEvent.event_string == 'Escape':
1005            return False
1006
1007        self.presentMessage(messages.LEARN_MODE_STOP)
1008        orca_state.learnModeEnabled = False
1009        if orca_state.device is not None:
1010            Atspi.Device.ungrab_keyboard(orca_state.device)
1011        return True
1012
1013    def showHelp(self, inputEvent=None):
1014        return orca.helpForOrca()
1015
1016    def listNotifications(self, inputEvent=None):
1017        if inputEvent is None:
1018            inputEvent = orca_state.lastNonModifierKeyEvent
1019
1020        return notification_messages.listNotificationMessages(self, inputEvent)
1021
1022    def listOrcaShortcuts(self, inputEvent=None):
1023        """Shows a simple gui listing Orca's bound commands."""
1024
1025        if inputEvent is None:
1026            inputEvent = orca_state.lastNonModifierKeyEvent
1027
1028        if not inputEvent or inputEvent.event_string == "F2":
1029            bound = self.getDefaultKeyBindings().getBoundBindings()
1030            title = messages.shortcutsFoundOrca(len(bound))
1031        else:
1032            try:
1033                appName = self.app.name
1034            except AttributeError:
1035                appName = messages.APPLICATION_NO_NAME
1036
1037            bound = self.getAppKeyBindings().getBoundBindings()
1038            bound.extend(self.getToolkitKeyBindings().getBoundBindings())
1039            title = messages.shortcutsFoundApp(len(bound), appName)
1040
1041        if not bound:
1042            self.presentMessage(title)
1043            return True
1044
1045        self.exitLearnMode()
1046
1047        rows = [(kb.handler.function,
1048                 kb.handler.description,
1049                 kb.asString()) for kb in bound]
1050        sorted(rows, key=lambda cmd: cmd[2])
1051
1052        header1 = guilabels.KB_HEADER_FUNCTION
1053        header2 = guilabels.KB_HEADER_KEY_BINDING
1054        commandlist.showUI(title, ("", header1, header2), rows, False)
1055        return True
1056
1057    def findNext(self, inputEvent):
1058        """Searches forward for the next instance of the string
1059        searched for via the Orca Find dialog.  Other than direction
1060        and the starting point, the search options initially specified
1061        (case sensitivity, window wrap, and full/partial match) are
1062        preserved.
1063        """
1064
1065        lastQuery = find.getLastQuery()
1066        if lastQuery:
1067            lastQuery.searchBackwards = False
1068            lastQuery.startAtTop = False
1069            self.find(lastQuery)
1070        else:
1071            orca.showFindGUI()
1072
1073    def findPrevious(self, inputEvent):
1074        """Searches backwards for the next instance of the string
1075        searched for via the Orca Find dialog.  Other than direction
1076        and the starting point, the search options initially specified
1077        (case sensitivity, window wrap, and full/or partial match) are
1078        preserved.
1079        """
1080
1081        lastQuery = find.getLastQuery()
1082        if lastQuery:
1083            lastQuery.searchBackwards = True
1084            lastQuery.startAtTop = False
1085            self.find(lastQuery)
1086        else:
1087            orca.showFindGUI()
1088
1089    def addBookmark(self, inputEvent):
1090        """ Add an in-page accessible object bookmark for this key.
1091        Delegates to Bookmark.addBookmark """
1092        bookmarks = self.getBookmarks()
1093        bookmarks.addBookmark(inputEvent)
1094
1095    def goToBookmark(self, inputEvent):
1096        """ Go to the bookmark indexed by inputEvent.hw_code.  Delegates to
1097        Bookmark.goToBookmark """
1098        bookmarks = self.getBookmarks()
1099        bookmarks.goToBookmark(inputEvent)
1100
1101    def goToNextBookmark(self, inputEvent):
1102        """ Go to the next bookmark location.  If no bookmark has yet to be
1103        selected, the first bookmark will be used.  Delegates to
1104        Bookmark.goToNextBookmark """
1105        bookmarks = self.getBookmarks()
1106        bookmarks.goToNextBookmark(inputEvent)
1107
1108    def goToPrevBookmark(self, inputEvent):
1109        """ Go to the previous bookmark location.  If no bookmark has yet to
1110        be selected, the first bookmark will be used.  Delegates to
1111        Bookmark.goToPrevBookmark """
1112        bookmarks = self.getBookmarks()
1113        bookmarks.goToPrevBookmark(inputEvent)
1114
1115    def saveBookmarks(self, inputEvent):
1116        """ Save the bookmarks for this script. Delegates to
1117        Bookmark.saveBookmarks """
1118        bookmarks = self.getBookmarks()
1119        bookmarks.saveBookmarks(inputEvent)
1120
1121    def panBrailleLeft(self, inputEvent=None, panAmount=0):
1122        """Pans the braille display to the left.  If panAmount is non-zero,
1123        the display is panned by that many cells.  If it is 0, the display
1124        is panned one full display width.  In flat review mode, panning
1125        beyond the beginning will take you to the end of the previous line.
1126
1127        In focus tracking mode, the cursor stays at its logical position.
1128        In flat review mode, the review cursor moves to character
1129        associated with cell 0."""
1130
1131        if self.flatReviewContext:
1132            if self.isBrailleBeginningShowing():
1133                self.flatReviewContext.goBegin(flat_review.Context.LINE)
1134                self.reviewPreviousCharacter(inputEvent)
1135            else:
1136                self.panBrailleInDirection(panAmount, panToLeft=True)
1137
1138            self._setFlatReviewContextToBeginningOfBrailleDisplay()
1139            self.targetCursorCell = 1
1140            self.updateBrailleReview(self.targetCursorCell)
1141        elif self.isBrailleBeginningShowing() and orca_state.locusOfFocus \
1142             and self.utilities.isTextArea(orca_state.locusOfFocus):
1143
1144            # If we're at the beginning of a line of a multiline text
1145            # area, then force it's caret to the end of the previous
1146            # line.  The assumption here is that we're currently
1147            # viewing the line that has the caret -- which is a pretty
1148            # good assumption for focus tacking mode.  When we set the
1149            # caret position, we will get a caret event, which will
1150            # then update the braille.
1151            #
1152            text = orca_state.locusOfFocus.queryText()
1153            [lineString, startOffset, endOffset] = text.getTextAtOffset(
1154                text.caretOffset,
1155                pyatspi.TEXT_BOUNDARY_LINE_START)
1156            movedCaret = False
1157            if startOffset > 0:
1158                movedCaret = text.setCaretOffset(startOffset - 1)
1159
1160            # If we didn't move the caret and we're in a terminal, we
1161            # jump into flat review to review the text.  See
1162            # http://bugzilla.gnome.org/show_bug.cgi?id=482294.
1163            #
1164            if (not movedCaret) \
1165               and (orca_state.locusOfFocus.getRole() \
1166                    == pyatspi.ROLE_TERMINAL):
1167                context = self.getFlatReviewContext()
1168                context.goBegin(flat_review.Context.LINE)
1169                self.reviewPreviousCharacter(inputEvent)
1170        else:
1171            self.panBrailleInDirection(panAmount, panToLeft=True)
1172            # We might be panning through a flashed message.
1173            #
1174            braille.resetFlashTimer()
1175            self.refreshBraille(False, stopFlash=False)
1176
1177        return True
1178
1179    def panBrailleLeftOneChar(self, inputEvent=None):
1180        """Nudges the braille display one character to the left.
1181
1182        In focus tracking mode, the cursor stays at its logical position.
1183        In flat review mode, the review cursor moves to character
1184        associated with cell 0."""
1185
1186        self.panBrailleLeft(inputEvent, 1)
1187
1188    def panBrailleRight(self, inputEvent=None, panAmount=0):
1189        """Pans the braille display to the right.  If panAmount is non-zero,
1190        the display is panned by that many cells.  If it is 0, the display
1191        is panned one full display width.  In flat review mode, panning
1192        beyond the end will take you to the beginning of the next line.
1193
1194        In focus tracking mode, the cursor stays at its logical position.
1195        In flat review mode, the review cursor moves to character
1196        associated with cell 0."""
1197
1198        if self.flatReviewContext:
1199            if self.isBrailleEndShowing():
1200                self.flatReviewContext.goEnd(flat_review.Context.LINE)
1201                # Reviewing the next character also updates the braille output and refreshes the display.
1202                self.reviewNextCharacter(inputEvent)
1203                return
1204            self.panBrailleInDirection(panAmount, panToLeft=False)
1205            self._setFlatReviewContextToBeginningOfBrailleDisplay()
1206            self.targetCursorCell = 1
1207            self.updateBrailleReview(self.targetCursorCell)
1208        elif self.isBrailleEndShowing() and orca_state.locusOfFocus \
1209             and self.utilities.isTextArea(orca_state.locusOfFocus):
1210            # If we're at the end of a line of a multiline text area, then
1211            # force it's caret to the beginning of the next line.  The
1212            # assumption here is that we're currently viewing the line that
1213            # has the caret -- which is a pretty good assumption for focus
1214            # tacking mode.  When we set the caret position, we will get a
1215            # caret event, which will then update the braille.
1216            #
1217            text = orca_state.locusOfFocus.queryText()
1218            [lineString, startOffset, endOffset] = text.getTextAtOffset(
1219                text.caretOffset,
1220                pyatspi.TEXT_BOUNDARY_LINE_START)
1221            if endOffset < text.characterCount:
1222                text.setCaretOffset(endOffset)
1223        else:
1224            self.panBrailleInDirection(panAmount, panToLeft=False)
1225            # We might be panning through a flashed message.
1226            #
1227            braille.resetFlashTimer()
1228            self.refreshBraille(False, stopFlash=False)
1229
1230        return True
1231
1232    def panBrailleRightOneChar(self, inputEvent=None):
1233        """Nudges the braille display one character to the right.
1234
1235        In focus tracking mode, the cursor stays at its logical position.
1236        In flat review mode, the review cursor moves to character
1237        associated with cell 0."""
1238
1239        self.panBrailleRight(inputEvent, 1)
1240
1241    def goBrailleHome(self, inputEvent=None):
1242        """Returns to the component with focus."""
1243
1244        if self.flatReviewContext:
1245            return self.toggleFlatReviewMode(inputEvent)
1246        else:
1247            return braille.returnToRegionWithFocus(inputEvent)
1248
1249    def setContractedBraille(self, inputEvent=None):
1250        """Toggles contracted braille."""
1251
1252        self._setContractedBraille(inputEvent)
1253        return True
1254
1255    def processRoutingKey(self, inputEvent=None):
1256        """Processes a cursor routing key."""
1257
1258        braille.processRoutingKey(inputEvent)
1259        return True
1260
1261    def processBrailleCutBegin(self, inputEvent=None):
1262        """Clears the selection and moves the caret offset in the currently
1263        active text area.
1264        """
1265
1266        obj, caretOffset = self.getBrailleCaretContext(inputEvent)
1267
1268        if caretOffset >= 0:
1269            self.utilities.clearTextSelection(obj)
1270            self.utilities.setCaretOffset(obj, caretOffset)
1271
1272        return True
1273
1274    def processBrailleCutLine(self, inputEvent=None):
1275        """Extends the text selection in the currently active text
1276        area and also copies the selected text to the system clipboard."""
1277
1278        obj, caretOffset = self.getBrailleCaretContext(inputEvent)
1279
1280        if caretOffset >= 0:
1281            self.utilities.adjustTextSelection(obj, caretOffset)
1282            texti = obj.queryText()
1283            startOffset, endOffset = texti.getSelection(0)
1284            self.utilities.setClipboardText(texti.getText(startOffset, endOffset))
1285
1286        return True
1287
1288    def routePointerToItem(self, inputEvent=None):
1289        """Moves the mouse pointer to the current item."""
1290
1291        # Store the original location for scripts which want to restore
1292        # it later.
1293        #
1294        self.oldMouseCoordinates = self.utilities.absoluteMouseCoordinates()
1295        self.lastMouseRoutingTime = time.time()
1296        if self.flatReviewContext:
1297            self.flatReviewContext.routeToCurrent()
1298            return True
1299
1300        if eventsynthesizer.routeToCharacter(orca_state.locusOfFocus):
1301            return True
1302
1303        if eventsynthesizer.routeToObject(orca_state.locusOfFocus):
1304            return True
1305
1306        full = messages.LOCATION_NOT_FOUND_FULL
1307        brief = messages.LOCATION_NOT_FOUND_BRIEF
1308        self.presentMessage(full, brief)
1309        return False
1310
1311    def presentStatusBar(self, inputEvent):
1312        """Speaks and brailles the contents of the status bar and/or default
1313        button of the window with focus.
1314        """
1315
1316        obj = orca_state.locusOfFocus
1317        self.updateBraille(obj)
1318
1319        frame, dialog = self.utilities.frameAndDialog(obj)
1320        if frame:
1321            start = time.time()
1322            statusbar = self.utilities.statusBar(frame)
1323            end = time.time()
1324            msg = "DEFAULT: Time searching for status bar: %.4f" % (end - start)
1325            debug.println(debug.LEVEL_INFO, msg, True)
1326            if statusbar:
1327                self.pointOfReference['statusBarItems'] = None
1328                self.presentObject(statusbar)
1329                self.pointOfReference['statusBarItems'] = None
1330            else:
1331                full = messages.STATUS_BAR_NOT_FOUND_FULL
1332                brief = messages.STATUS_BAR_NOT_FOUND_BRIEF
1333                self.presentMessage(full, brief)
1334
1335            infobar = self.utilities.infoBar(frame)
1336            if infobar:
1337                speech.speak(self.speechGenerator.generateSpeech(infobar))
1338
1339        window = dialog or frame
1340        if window:
1341            speech.speak(self.speechGenerator.generateDefaultButton(window))
1342
1343    def presentTitle(self, inputEvent):
1344        """Speaks and brailles the title of the window with focus."""
1345
1346        obj = orca_state.locusOfFocus
1347        if self.utilities.isDead(obj):
1348            obj = orca_state.activeWindow
1349
1350        if not obj or self.utilities.isDead(obj):
1351            self.presentMessage(messages.LOCATION_NOT_FOUND_FULL)
1352            return True
1353
1354        title = self.speechGenerator.generateTitle(obj)
1355        for (string, voice) in title:
1356            self.presentMessage(string, voice=voice)
1357
1358    def readCharAttributes(self, inputEvent=None):
1359        """Reads the attributes associated with the current text character.
1360        Calls outCharAttributes to speak a list of attributes. By default,
1361        a certain set of attributes will be spoken. If this is not desired,
1362        then individual application scripts should override this method to
1363        only speak the subset required.
1364        """
1365
1366        attrs, start, end = self.utilities.textAttributes(orca_state.locusOfFocus, None, True)
1367
1368        # Get a dictionary of text attributes that the user cares about.
1369        [userAttrList, userAttrDict] = self.utilities.stringToKeysAndDict(
1370            _settingsManager.getSetting('enabledSpokenTextAttributes'))
1371
1372        nullValues = ['0', '0mm', 'none', 'false']
1373        for key in userAttrList:
1374            # Convert the standard key into the non-standard implementor variant.
1375            appKey = self.utilities.getAppNameForAttribute(key)
1376            value = attrs.get(appKey)
1377            ignoreIfValue = userAttrDict.get(key)
1378            if value in nullValues and ignoreIfValue in nullValues:
1379                continue
1380
1381            if value and value != ignoreIfValue:
1382                self.speakMessage(self.utilities.localizeTextAttribute(key, value))
1383
1384        return True
1385
1386    def leftClickReviewItem(self, inputEvent=None):
1387        """Performs a left mouse button click on the current item."""
1388
1389        if self.flatReviewContext:
1390            if self.flatReviewContext.clickCurrent(1):
1391                return True
1392
1393            obj = self.flatReviewContext.getCurrentAccessible()
1394            if eventsynthesizer.clickActionOn(obj):
1395                return True
1396            if eventsynthesizer.pressActionOn(obj):
1397                return True
1398            if eventsynthesizer.grabFocusOn(obj):
1399                return True
1400            return False
1401
1402        if self.utilities.queryNonEmptyText(orca_state.locusOfFocus):
1403            if eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 1):
1404                return True
1405
1406        if eventsynthesizer.clickObject(orca_state.locusOfFocus, 1):
1407            return True
1408
1409        full = messages.LOCATION_NOT_FOUND_FULL
1410        brief = messages.LOCATION_NOT_FOUND_BRIEF
1411        self.presentMessage(full, brief)
1412        return False
1413
1414    def rightClickReviewItem(self, inputEvent=None):
1415        """Performs a right mouse button click on the current item."""
1416
1417        if self.flatReviewContext:
1418            self.flatReviewContext.clickCurrent(3)
1419            return True
1420
1421        if eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 3):
1422            return True
1423
1424        if eventsynthesizer.clickObject(orca_state.locusOfFocus, 3):
1425            return True
1426
1427        full = messages.LOCATION_NOT_FOUND_FULL
1428        brief = messages.LOCATION_NOT_FOUND_BRIEF
1429        self.presentMessage(full, brief)
1430        return False
1431
1432    def spellCurrentItem(self, itemString):
1433        """Spell the current flat review word or line.
1434
1435        Arguments:
1436        - itemString: the string to spell.
1437        """
1438
1439        for character in itemString:
1440            self.speakCharacter(character)
1441
1442    def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
1443                           speechType=1):
1444        """Presents the current item to the user.
1445
1446        Arguments:
1447        - inputEvent - the current input event.
1448        - targetCursorCell - if non-zero, the target braille cursor cell.
1449        - speechType - the desired presentation: speak (1), spell (2), or
1450                       phonetic (3).
1451        """
1452
1453        context = self.getFlatReviewContext()
1454        [wordString, x, y, width, height] = \
1455                 context.getCurrent(flat_review.Context.WORD)
1456
1457        voice = self.speechGenerator.voice(string=wordString)
1458
1459        # Don't announce anything from speech if the user used
1460        # the Braille display as an input device.
1461        #
1462        if not isinstance(inputEvent, input_event.BrailleEvent):
1463            if (not wordString) \
1464               or (not len(wordString)) \
1465               or (wordString == "\n"):
1466                speech.speak(messages.BLANK)
1467            else:
1468                [lineString, x, y, width, height] = \
1469                         context.getCurrent(flat_review.Context.LINE)
1470                if lineString == "\n":
1471                    speech.speak(messages.BLANK)
1472                elif wordString.isspace():
1473                    speech.speak(messages.WHITE_SPACE)
1474                elif wordString.isupper() and speechType == 1:
1475                    speech.speak(wordString, voice)
1476                elif speechType == 2:
1477                    self.spellCurrentItem(wordString)
1478                elif speechType == 3:
1479                    self.phoneticSpellCurrentItem(wordString)
1480                elif speechType == 1:
1481                    wordString = self.utilities.adjustForRepeats(wordString)
1482                    speech.speak(wordString, voice)
1483
1484        self.updateBrailleReview(targetCursorCell)
1485        self.currentReviewContents = wordString
1486
1487        return True
1488
1489    def reviewCurrentAccessible(self, inputEvent):
1490        context = self.getFlatReviewContext()
1491        [zoneString, x, y, width, height] = \
1492                 context.getCurrent(flat_review.Context.ZONE)
1493
1494        # Don't announce anything from speech if the user used
1495        # the Braille display as an input device.
1496        #
1497        if not isinstance(inputEvent, input_event.BrailleEvent):
1498            utterances = self.speechGenerator.generateSpeech(
1499                    context.getCurrentAccessible())
1500            utterances.extend(self.tutorialGenerator.getTutorial(
1501                    context.getCurrentAccessible(), False))
1502            speech.speak(utterances)
1503        return True
1504
1505    def reviewPreviousItem(self, inputEvent):
1506        """Moves the flat review context to the previous item.  Places
1507        the flat review cursor at the beginning of the item."""
1508
1509        context = self.getFlatReviewContext()
1510
1511        moved = context.goPrevious(flat_review.Context.WORD,
1512                                   flat_review.Context.WRAP_LINE)
1513
1514        if moved:
1515            self._reviewCurrentItem(inputEvent)
1516            self.targetCursorCell = self.getBrailleCursorCell()
1517
1518        return True
1519
1520    def reviewNextItem(self, inputEvent):
1521        """Moves the flat review context to the next item.  Places
1522        the flat review cursor at the beginning of the item."""
1523
1524        context = self.getFlatReviewContext()
1525
1526        moved = context.goNext(flat_review.Context.WORD,
1527                               flat_review.Context.WRAP_LINE)
1528
1529        if moved:
1530            self._reviewCurrentItem(inputEvent)
1531            self.targetCursorCell = self.getBrailleCursorCell()
1532
1533        return True
1534
1535    def reviewCurrentCharacter(self, inputEvent):
1536        """Brailles and speaks the current flat review character."""
1537
1538        self._reviewCurrentCharacter(inputEvent, 1)
1539
1540        return True
1541
1542    def reviewSpellCurrentCharacter(self, inputEvent):
1543        """Brailles and 'spells' (phonetically) the current flat review
1544        character.
1545        """
1546
1547        self._reviewCurrentCharacter(inputEvent, 2)
1548
1549        return True
1550
1551    def reviewUnicodeCurrentCharacter(self, inputEvent):
1552        """Brailles and speaks unicode information about the current flat
1553        review character.
1554        """
1555
1556        self._reviewCurrentCharacter(inputEvent, 3)
1557
1558        return True
1559
1560    def _reviewCurrentCharacter(self, inputEvent, speechType=1):
1561        """Presents the current flat review character via braille and speech.
1562
1563        Arguments:
1564        - inputEvent - the current input event.
1565        - speechType - the desired presentation:
1566                       speak (1),
1567                       phonetic (2)
1568                       unicode value information (3)
1569        """
1570
1571        context = self.getFlatReviewContext()
1572
1573        [charString, x, y, width, height] = \
1574                 context.getCurrent(flat_review.Context.CHAR)
1575
1576        # Don't announce anything from speech if the user used
1577        # the Braille display as an input device.
1578        #
1579        if not isinstance(inputEvent, input_event.BrailleEvent):
1580            if (not charString) or (not len(charString)):
1581                speech.speak(messages.BLANK)
1582            else:
1583                [lineString, x, y, width, height] = \
1584                         context.getCurrent(flat_review.Context.LINE)
1585                if lineString == "\n" and speechType != 3:
1586                    speech.speak(messages.BLANK)
1587                elif speechType == 3:
1588                    self.speakUnicodeCharacter(charString)
1589                elif speechType == 2:
1590                    self.phoneticSpellCurrentItem(charString)
1591                else:
1592                    self.speakCharacter(charString)
1593
1594        self.updateBrailleReview()
1595        self.currentReviewContents = charString
1596
1597        return True
1598
1599    def reviewPreviousCharacter(self, inputEvent):
1600        """Moves the flat review context to the previous character.  Places
1601        the flat review cursor at character."""
1602
1603        context = self.getFlatReviewContext()
1604
1605        moved = context.goPrevious(flat_review.Context.CHAR,
1606                                   flat_review.Context.WRAP_LINE)
1607
1608        if moved:
1609            self._reviewCurrentCharacter(inputEvent)
1610            self.targetCursorCell = self.getBrailleCursorCell()
1611
1612        return True
1613
1614    def reviewEndOfLine(self, inputEvent):
1615        """Moves the flat review context to the end of the line.  Places
1616        the flat review cursor at the end of the line."""
1617
1618        context = self.getFlatReviewContext()
1619        context.goEnd(flat_review.Context.LINE)
1620
1621        self.reviewCurrentCharacter(inputEvent)
1622        self.targetCursorCell = self.getBrailleCursorCell()
1623
1624        return True
1625
1626    def reviewNextCharacter(self, inputEvent):
1627        """Moves the flat review context to the next character.  Places
1628        the flat review cursor at character."""
1629
1630        context = self.getFlatReviewContext()
1631
1632        moved = context.goNext(flat_review.Context.CHAR,
1633                               flat_review.Context.WRAP_LINE)
1634
1635        if moved:
1636            self._reviewCurrentCharacter(inputEvent)
1637            self.targetCursorCell = self.getBrailleCursorCell()
1638
1639        return True
1640
1641    def reviewAbove(self, inputEvent):
1642        """Moves the flat review context to the character most directly
1643        above the current flat review cursor.  Places the flat review
1644        cursor at character."""
1645
1646        context = self.getFlatReviewContext()
1647
1648        moved = context.goAbove(flat_review.Context.CHAR,
1649                                flat_review.Context.WRAP_LINE)
1650
1651        if moved:
1652            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
1653
1654        return True
1655
1656    def reviewBelow(self, inputEvent):
1657        """Moves the flat review context to the character most directly
1658        below the current flat review cursor.  Places the flat review
1659        cursor at character."""
1660
1661        context = self.getFlatReviewContext()
1662
1663        moved = context.goBelow(flat_review.Context.CHAR,
1664                                flat_review.Context.WRAP_LINE)
1665
1666        if moved:
1667            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
1668
1669        return True
1670
1671    def reviewCurrentLine(self, inputEvent):
1672        """Brailles and speaks the current flat review line."""
1673
1674        self._reviewCurrentLine(inputEvent, 1)
1675
1676        return True
1677
1678    def reviewSpellCurrentLine(self, inputEvent):
1679        """Brailles and spells the current flat review line."""
1680
1681        self._reviewCurrentLine(inputEvent, 2)
1682
1683        return True
1684
1685    def reviewPhoneticCurrentLine(self, inputEvent):
1686        """Brailles and phonetically spells the current flat review line."""
1687
1688        self._reviewCurrentLine(inputEvent, 3)
1689
1690        return True
1691
1692    def _reviewCurrentLine(self, inputEvent, speechType=1):
1693        """Presents the current flat review line via braille and speech.
1694
1695        Arguments:
1696        - inputEvent - the current input event.
1697        - speechType - the desired presentation: speak (1), spell (2), or
1698                       phonetic (3)
1699        """
1700
1701        context = self.getFlatReviewContext()
1702
1703        [lineString, x, y, width, height] = \
1704                 context.getCurrent(flat_review.Context.LINE)
1705
1706        voice = self.speechGenerator.voice(string=lineString)
1707
1708        # Don't announce anything from speech if the user used
1709        # the Braille display as an input device.
1710        #
1711        if not isinstance(inputEvent, input_event.BrailleEvent):
1712            if (not lineString) \
1713               or (not len(lineString)) \
1714               or (lineString == "\n"):
1715                speech.speak(messages.BLANK)
1716            elif lineString.isspace():
1717                speech.speak(messages.WHITE_SPACE)
1718            elif lineString.isupper() and (speechType < 2 or speechType > 3):
1719                speech.speak(lineString, voice)
1720            elif speechType == 2:
1721                self.spellCurrentItem(lineString)
1722            elif speechType == 3:
1723                self.phoneticSpellCurrentItem(lineString)
1724            else:
1725                lineString = self.utilities.adjustForRepeats(lineString)
1726                speech.speak(lineString, voice)
1727
1728        self.updateBrailleReview()
1729        self.currentReviewContents = lineString
1730
1731        return True
1732
1733    def reviewPreviousLine(self, inputEvent):
1734        """Moves the flat review context to the beginning of the
1735        previous line."""
1736
1737        context = self.getFlatReviewContext()
1738
1739        moved = context.goPrevious(flat_review.Context.LINE,
1740                                   flat_review.Context.WRAP_LINE)
1741
1742        if moved:
1743            self._reviewCurrentLine(inputEvent)
1744            self.targetCursorCell = self.getBrailleCursorCell()
1745
1746        return True
1747
1748    def reviewHome(self, inputEvent):
1749        """Moves the flat review context to the top left of the current
1750        window."""
1751
1752        context = self.getFlatReviewContext()
1753
1754        context.goBegin()
1755
1756        self._reviewCurrentLine(inputEvent)
1757        self.targetCursorCell = self.getBrailleCursorCell()
1758
1759        return True
1760
1761    def reviewNextLine(self, inputEvent):
1762        """Moves the flat review context to the beginning of the
1763        next line.  Places the flat review cursor at the beginning
1764        of the line."""
1765
1766        context = self.getFlatReviewContext()
1767
1768        moved = context.goNext(flat_review.Context.LINE,
1769                               flat_review.Context.WRAP_LINE)
1770
1771        if moved:
1772            self._reviewCurrentLine(inputEvent)
1773            self.targetCursorCell = self.getBrailleCursorCell()
1774
1775        return True
1776
1777    def reviewBottomLeft(self, inputEvent):
1778        """Moves the flat review context to the beginning of the
1779        last line in the window.  Places the flat review cursor at
1780        the beginning of the line."""
1781
1782        context = self.getFlatReviewContext()
1783
1784        context.goEnd(flat_review.Context.WINDOW)
1785        context.goBegin(flat_review.Context.LINE)
1786        self._reviewCurrentLine(inputEvent)
1787        self.targetCursorCell = self.getBrailleCursorCell()
1788
1789        return True
1790
1791    def reviewEnd(self, inputEvent):
1792        """Moves the flat review context to the end of the
1793        last line in the window.  Places the flat review cursor
1794        at the end of the line."""
1795
1796        context = self.getFlatReviewContext()
1797        context.goEnd()
1798
1799        self._reviewCurrentLine(inputEvent)
1800        self.targetCursorCell = self.getBrailleCursorCell()
1801
1802        return True
1803
1804    def reviewCurrentItem(self, inputEvent, targetCursorCell=0):
1805        """Brailles and speaks the current item to the user."""
1806
1807        self._reviewCurrentItem(inputEvent, targetCursorCell, 1)
1808
1809        return True
1810
1811    def reviewSpellCurrentItem(self, inputEvent, targetCursorCell=0):
1812        """Brailles and spells the current item to the user."""
1813
1814        self._reviewCurrentItem(inputEvent, targetCursorCell, 2)
1815
1816        return True
1817
1818    def reviewPhoneticCurrentItem(self, inputEvent, targetCursorCell=0):
1819        """Brailles and phonetically spells the current item to the user."""
1820
1821        self._reviewCurrentItem(inputEvent, targetCursorCell, 3)
1822
1823        return True
1824
1825    def flatReviewCopy(self, inputEvent):
1826        """Copies the contents of the item under flat review to and places
1827        them in the clipboard."""
1828
1829        if self.flatReviewContext:
1830            self.utilities.setClipboardText(self.currentReviewContents.rstrip("\n"))
1831            self.presentMessage(messages.FLAT_REVIEW_COPIED)
1832        else:
1833            self.presentMessage(messages.FLAT_REVIEW_NOT_IN)
1834
1835        return True
1836
1837    def flatReviewAppend(self, inputEvent):
1838        """Appends the contents of the item under flat review to
1839        the clipboard."""
1840
1841        if self.flatReviewContext:
1842            self.utilities.appendTextToClipboard(self.currentReviewContents.rstrip("\n"))
1843            self.presentMessage(messages.FLAT_REVIEW_APPENDED)
1844        else:
1845            self.presentMessage(messages.FLAT_REVIEW_NOT_IN)
1846
1847        return True
1848
1849    def flatReviewSayAll(self, inputEvent):
1850        context = self.getFlatReviewContext()
1851        context.goBegin()
1852
1853        while True:
1854            [string, x, y, width, height] = context.getCurrent(flat_review.Context.LINE)
1855            if string is not None:
1856                speech.speak(string)
1857            moved = context.goNext(flat_review.Context.LINE, flat_review.Context.WRAP_LINE)
1858            if not moved:
1859                break
1860
1861        return True
1862
1863    def sayAll(self, inputEvent, obj=None, offset=None):
1864        obj = obj or orca_state.locusOfFocus
1865        if not obj or self.utilities.isDead(obj):
1866            self.presentMessage(messages.LOCATION_NOT_FOUND_FULL)
1867            return True
1868
1869        try:
1870            text = obj.queryText()
1871        except NotImplementedError:
1872            utterances = self.speechGenerator.generateSpeech(obj)
1873            utterances.extend(self.tutorialGenerator.getTutorial(obj, False))
1874            speech.speak(utterances)
1875        except AttributeError:
1876            pass
1877        else:
1878            if offset is None:
1879                offset = text.caretOffset
1880            speech.sayAll(self.textLines(obj, offset),
1881                          self.__sayAllProgressCallback)
1882
1883        return True
1884
1885    def toggleFlatReviewMode(self, inputEvent=None):
1886        """Toggles between flat review mode and focus tracking mode."""
1887
1888        verbosity = _settingsManager.getSetting('speechVerbosityLevel')
1889        if self.flatReviewContext:
1890            if inputEvent and verbosity != settings.VERBOSITY_LEVEL_BRIEF:
1891                self.presentMessage(messages.FLAT_REVIEW_STOP)
1892            self.flatReviewContext = None
1893            self.updateBraille(orca_state.locusOfFocus)
1894        else:
1895            if inputEvent and verbosity != settings.VERBOSITY_LEVEL_BRIEF:
1896                self.presentMessage(messages.FLAT_REVIEW_START)
1897            context = self.getFlatReviewContext()
1898            [wordString, x, y, width, height] = \
1899                     context.getCurrent(flat_review.Context.WORD)
1900            self._reviewCurrentItem(inputEvent, self.targetCursorCell)
1901
1902        return True
1903
1904    def toggleSilenceSpeech(self, inputEvent=None):
1905        """Toggle the silencing of speech.
1906
1907        Returns True to indicate the input event has been consumed.
1908        """
1909
1910        self.presentationInterrupt()
1911        if _settingsManager.getSetting('silenceSpeech'):
1912            _settingsManager.setSetting('silenceSpeech', False)
1913            self.presentMessage(messages.SPEECH_ENABLED)
1914        elif not _settingsManager.getSetting('enableSpeech'):
1915            _settingsManager.setSetting('enableSpeech', True)
1916            speech.init()
1917            self.presentMessage(messages.SPEECH_ENABLED)
1918        else:
1919            self.presentMessage(messages.SPEECH_DISABLED)
1920            _settingsManager.setSetting('silenceSpeech', True)
1921        return True
1922
1923    def toggleSpeechVerbosity(self, inputEvent=None):
1924        """Toggles speech verbosity level between verbose and brief."""
1925
1926        value = _settingsManager.getSetting('speechVerbosityLevel')
1927        if value == settings.VERBOSITY_LEVEL_BRIEF:
1928            self.presentMessage(messages.SPEECH_VERBOSITY_VERBOSE)
1929            _settingsManager.setSetting(
1930                'speechVerbosityLevel', settings.VERBOSITY_LEVEL_VERBOSE)
1931        else:
1932            self.presentMessage(messages.SPEECH_VERBOSITY_BRIEF)
1933            _settingsManager.setSetting(
1934                'speechVerbosityLevel', settings.VERBOSITY_LEVEL_BRIEF)
1935
1936        return True
1937
1938    def toggleSpeakingIndentationJustification(self, inputEvent=None):
1939        """Toggles the speaking of indentation and justification."""
1940
1941        value = _settingsManager.getSetting('enableSpeechIndentation')
1942        _settingsManager.setSetting('enableSpeechIndentation', not value)
1943        if _settingsManager.getSetting('enableSpeechIndentation'):
1944            full = messages.INDENTATION_JUSTIFICATION_ON_FULL
1945            brief = messages.INDENTATION_JUSTIFICATION_ON_BRIEF
1946        else:
1947            full = messages.INDENTATION_JUSTIFICATION_OFF_FULL
1948            brief = messages.INDENTATION_JUSTIFICATION_OFF_BRIEF
1949        self.presentMessage(full, brief)
1950
1951        return True
1952
1953    def cycleSpeakingPunctuationLevel(self, inputEvent=None):
1954        """ Cycle through the punctuation levels for speech. """
1955
1956        currentLevel = _settingsManager.getSetting('verbalizePunctuationStyle')
1957        if currentLevel == settings.PUNCTUATION_STYLE_NONE:
1958            newLevel = settings.PUNCTUATION_STYLE_SOME
1959            full = messages.PUNCTUATION_SOME_FULL
1960            brief = messages.PUNCTUATION_SOME_BRIEF
1961        elif currentLevel == settings.PUNCTUATION_STYLE_SOME:
1962            newLevel = settings.PUNCTUATION_STYLE_MOST
1963            full = messages.PUNCTUATION_MOST_FULL
1964            brief = messages.PUNCTUATION_MOST_BRIEF
1965        elif currentLevel == settings.PUNCTUATION_STYLE_MOST:
1966            newLevel = settings.PUNCTUATION_STYLE_ALL
1967            full = messages.PUNCTUATION_ALL_FULL
1968            brief = messages.PUNCTUATION_ALL_BRIEF
1969        else:
1970            newLevel = settings.PUNCTUATION_STYLE_NONE
1971            full = messages.PUNCTUATION_NONE_FULL
1972            brief = messages.PUNCTUATION_NONE_BRIEF
1973
1974        _settingsManager.setSetting('verbalizePunctuationStyle', newLevel)
1975        self.presentMessage(full, brief)
1976        speech.updatePunctuationLevel()
1977        return True
1978
1979    def cycleSettingsProfile(self, inputEvent=None):
1980        """Cycle through the user's existing settings profiles."""
1981
1982        profiles = _settingsManager.availableProfiles()
1983        if not (profiles and profiles[0]):
1984            self.presentMessage(messages.PROFILE_NOT_FOUND)
1985            return True
1986
1987        isMatch = lambda x: x[1] == _settingsManager.getProfile()
1988        current = list(filter(isMatch, profiles))[0]
1989        try:
1990            name, profileID = profiles[profiles.index(current) + 1]
1991        except IndexError:
1992            name, profileID = profiles[0]
1993
1994        _settingsManager.setProfile(profileID, updateLocale=True)
1995
1996        braille.checkBrailleSetting()
1997
1998        speech.shutdown()
1999        speech.init()
2000
2001        # TODO: This is another "too close to code freeze" hack to cause the
2002        # command names to be presented in the correct language.
2003        self.setupInputEventHandlers()
2004
2005        self.presentMessage(messages.PROFILE_CHANGED % name, name)
2006        return True
2007
2008    def cycleCapitalizationStyle(self, inputEvent=None):
2009        """ Cycle through the speech-dispatcher capitalization styles. """
2010
2011        currentStyle = _settingsManager.getSetting('capitalizationStyle')
2012        if currentStyle == settings.CAPITALIZATION_STYLE_NONE:
2013            newStyle = settings.CAPITALIZATION_STYLE_SPELL
2014            full = messages.CAPITALIZATION_SPELL_FULL
2015            brief = messages.CAPITALIZATION_SPELL_BRIEF
2016        elif currentStyle == settings.CAPITALIZATION_STYLE_SPELL:
2017            newStyle = settings.CAPITALIZATION_STYLE_ICON
2018            full = messages.CAPITALIZATION_ICON_FULL
2019            brief = messages.CAPITALIZATION_ICON_BRIEF
2020        else:
2021            newStyle = settings.CAPITALIZATION_STYLE_NONE
2022            full = messages.CAPITALIZATION_NONE_FULL
2023            brief = messages.CAPITALIZATION_NONE_BRIEF
2024
2025        _settingsManager.setSetting('capitalizationStyle', newStyle)
2026        self.presentMessage(full, brief)
2027        speech.updateCapitalizationStyle()
2028        return True
2029
2030    def cycleKeyEcho(self, inputEvent=None):
2031        (newKey, newWord, newSentence) = (False, False, False)
2032        key = _settingsManager.getSetting('enableKeyEcho')
2033        word = _settingsManager.getSetting('enableEchoByWord')
2034        sentence = _settingsManager.getSetting('enableEchoBySentence')
2035
2036        if (key, word, sentence) == (False, False, False):
2037            (newKey, newWord, newSentence) = (True, False, False)
2038            full = messages.KEY_ECHO_KEY_FULL
2039            brief = messages.KEY_ECHO_KEY_BRIEF
2040        elif (key, word, sentence) == (True, False, False):
2041            (newKey, newWord, newSentence) = (False, True, False)
2042            full = messages.KEY_ECHO_WORD_FULL
2043            brief = messages.KEY_ECHO_WORD_BRIEF
2044        elif (key, word, sentence) == (False, True, False):
2045            (newKey, newWord, newSentence) = (False, False, True)
2046            full = messages.KEY_ECHO_SENTENCE_FULL
2047            brief = messages.KEY_ECHO_SENTENCE_BRIEF
2048        elif (key, word, sentence) == (False, False, True):
2049            (newKey, newWord, newSentence) = (True, True, False)
2050            full = messages.KEY_ECHO_KEY_AND_WORD_FULL
2051            brief = messages.KEY_ECHO_KEY_AND_WORD_BRIEF
2052        elif (key, word, sentence) == (True, True, False):
2053            (newKey, newWord, newSentence) = (False, True, True)
2054            full = messages.KEY_ECHO_WORD_AND_SENTENCE_FULL
2055            brief = messages.KEY_ECHO_WORD_AND_SENTENCE_BRIEF
2056        else:
2057            (newKey, newWord, newSentence) = (False, False, False)
2058            full = messages.KEY_ECHO_NONE_FULL
2059            brief = messages.KEY_ECHO_NONE_BRIEF
2060
2061        _settingsManager.setSetting('enableKeyEcho', newKey)
2062        _settingsManager.setSetting('enableEchoByWord', newWord)
2063        _settingsManager.setSetting('enableEchoBySentence', newSentence)
2064        self.presentMessage(full, brief)
2065        return True
2066
2067    def changeNumberStyle(self, inputEvent=None):
2068        """Changes spoken number style between digits and words."""
2069
2070        speakDigits = _settingsManager.getSetting('speakNumbersAsDigits')
2071        if speakDigits:
2072            brief = messages.NUMBER_STYLE_WORDS_BRIEF
2073            full = messages.NUMBER_STYLE_WORDS_FULL
2074        else:
2075            brief = messages.NUMBER_STYLE_DIGITS_BRIEF
2076            full = messages.NUMBER_STYLE_DIGITS_FULL
2077
2078        _settingsManager.setSetting('speakNumbersAsDigits', not speakDigits)
2079        self.presentMessage(full, brief)
2080        return True
2081
2082    def toggleTableCellReadMode(self, inputEvent=None):
2083        """Toggles an indicator for whether we should just read the current
2084        table cell or read the whole row."""
2085
2086        table = self.utilities.getTable(orca_state.locusOfFocus)
2087        if not table:
2088            self.presentMessage(messages.TABLE_NOT_IN_A)
2089            return True
2090
2091        if not self.utilities.getDocumentForObject(table):
2092            settingName = 'readFullRowInGUITable'
2093        elif self.utilities.isSpreadSheetTable(table):
2094            settingName = 'readFullRowInSpreadSheet'
2095        else:
2096            settingName = 'readFullRowInDocumentTable'
2097
2098        speakRow = _settingsManager.getSetting(settingName)
2099        _settingsManager.setSetting(settingName, not speakRow)
2100
2101        if not speakRow:
2102            line = messages.TABLE_MODE_ROW
2103        else:
2104            line = messages.TABLE_MODE_CELL
2105
2106        self.presentMessage(line)
2107
2108        return True
2109
2110    def doWhereAmI(self, inputEvent, basicOnly):
2111        """Peforms the whereAmI operation.
2112
2113        Arguments:
2114        - inputEvent:     The original inputEvent
2115        """
2116
2117        if self.spellcheck and self.spellcheck.isActive():
2118            self.spellcheck.presentErrorDetails(not basicOnly)
2119
2120        obj = orca_state.locusOfFocus
2121        if self.utilities.isDead(obj):
2122            obj = orca_state.activeWindow
2123
2124        if not obj or self.utilities.isDead(obj):
2125            self.presentMessage(messages.LOCATION_NOT_FOUND_FULL)
2126            return True
2127
2128        self.updateBraille(obj)
2129
2130        if basicOnly:
2131            formatType = 'basicWhereAmI'
2132        else:
2133            formatType = 'detailedWhereAmI'
2134        speech.speak(self.speechGenerator.generateSpeech(
2135            self.utilities.realActiveAncestor(obj),
2136            alreadyFocused=True,
2137            formatType=formatType,
2138            forceMnemonic=True,
2139            forceList=True,
2140            forceTutorial=True))
2141
2142        return True
2143
2144    def whereAmIBasic(self, inputEvent):
2145        """Speaks basic information about the current object of interest.
2146        """
2147
2148        self.doWhereAmI(inputEvent, True)
2149
2150    def whereAmIDetailed(self, inputEvent):
2151        """Speaks detailed/custom information about the current object of
2152        interest.
2153        """
2154
2155        self.doWhereAmI(inputEvent, False)
2156
2157    def cycleDebugLevel(self, inputEvent=None):
2158        levels = [debug.LEVEL_ALL, "all",
2159                  debug.LEVEL_FINEST, "finest",
2160                  debug.LEVEL_FINER, "finer",
2161                  debug.LEVEL_FINE, "fine",
2162                  debug.LEVEL_CONFIGURATION, "configuration",
2163                  debug.LEVEL_INFO, "info",
2164                  debug.LEVEL_WARNING, "warning",
2165                  debug.LEVEL_SEVERE, "severe",
2166                  debug.LEVEL_OFF, "off"]
2167
2168        try:
2169            levelIndex = levels.index(debug.debugLevel) + 2
2170        except:
2171            levelIndex = 0
2172        else:
2173            if levelIndex >= len(levels):
2174                levelIndex = 0
2175
2176        debug.debugLevel = levels[levelIndex]
2177        briefMessage = levels[levelIndex + 1]
2178        fullMessage =  "Debug level %s." % briefMessage
2179        self.presentMessage(fullMessage, briefMessage)
2180
2181        return True
2182
2183    def whereAmILink(self, inputEvent=None, link=None):
2184        link = link or orca_state.locusOfFocus
2185        if not self.utilities.isLink(link):
2186            self.presentMessage(messages.NOT_ON_A_LINK)
2187        else:
2188            speech.speak(self.speechGenerator.generateLinkInfo(link))
2189        return True
2190
2191    def _whereAmISelectedText(self, inputEvent, obj):
2192        text, startOffset, endOffset = self.utilities.allSelectedText(obj)
2193        if self.utilities.shouldVerbalizeAllPunctuation(obj):
2194            text = self.utilities.verbalizeAllPunctuation(text)
2195
2196        if not text:
2197            msg = messages.NO_SELECTED_TEXT
2198        else:
2199            msg = messages.SELECTED_TEXT_IS % text
2200        self.speakMessage(msg)
2201        return True
2202
2203    def whereAmISelection(self, inputEvent=None, obj=None):
2204        obj = obj or orca_state.locusOfFocus
2205        if not obj:
2206            return True
2207
2208        container = self.utilities.getSelectionContainer(obj)
2209        if not container:
2210            msg = "INFO: Selection container not found for %s" % obj
2211            debug.println(debug.LEVEL_INFO, msg, True)
2212            return self._whereAmISelectedText(inputEvent, obj)
2213
2214        count = self.utilities.selectedChildCount(container)
2215        childCount = self.utilities.selectableChildCount(container)
2216        self.presentMessage(messages.selectedItemsCount(count, childCount))
2217        if not count:
2218            return True
2219
2220        utterances = self.speechGenerator.generateSelectedItems(container)
2221        speech.speak(utterances)
2222        return True
2223
2224    ########################################################################
2225    #                                                                      #
2226    # AT-SPI OBJECT EVENT HANDLERS                                         #
2227    #                                                                      #
2228    ########################################################################
2229
2230    def noOp(self, event):
2231        """Just here to capture events.
2232
2233        Arguments:
2234        - event: the Event
2235        """
2236        pass
2237
2238    def onActiveChanged(self, event):
2239        """Callback for object:state-changed:active accessibility events."""
2240
2241        frames = [pyatspi.ROLE_FRAME,
2242                  pyatspi.ROLE_DIALOG,
2243                  pyatspi.ROLE_FILE_CHOOSER,
2244                  pyatspi.ROLE_COLOR_CHOOSER]
2245
2246        if event.source.getRole() in frames:
2247            if event.detail1 and not self.utilities.canBeActiveWindow(event.source):
2248                return
2249
2250            sourceIsActiveWindow = self.utilities.isSameObject(
2251                event.source, orca_state.activeWindow)
2252
2253            if sourceIsActiveWindow and not event.detail1:
2254                if self.utilities.inMenu():
2255                    msg = "DEFAULT: Ignoring event. In menu."
2256                    debug.println(debug.LEVEL_INFO, msg, True)
2257                    return
2258
2259                if not self.utilities.eventIsUserTriggered(event):
2260                    msg = "DEFAULT: Not clearing state. Event is not user triggered."
2261                    debug.println(debug.LEVEL_INFO, msg, True)
2262                    return
2263
2264                msg = "DEFAULT: Event is for active window. Clearing state."
2265                debug.println(debug.LEVEL_INFO, msg, True)
2266                orca_state.activeWindow = None
2267                return
2268
2269            if not sourceIsActiveWindow and event.detail1:
2270                msg = "DEFAULT: Updating active window to event source."
2271                debug.println(debug.LEVEL_INFO, msg, True)
2272                self.windowActivateTime = time.time()
2273                orca.setLocusOfFocus(event, event.source)
2274                orca_state.activeWindow = event.source
2275
2276        if self.findCommandRun:
2277            self.findCommandRun = False
2278            self.find()
2279
2280    def onActiveDescendantChanged(self, event):
2281        """Callback for object:active-descendant-changed accessibility events."""
2282
2283        if not event.any_data:
2284            return
2285
2286        if not event.source.getState().contains(pyatspi.STATE_FOCUSED) \
2287           and not event.any_data.getState().contains(pyatspi.STATE_FOCUSED):
2288            msg = "DEFAULT: Ignoring event. Neither source nor child have focused state."
2289            debug.println(debug.LEVEL_INFO, msg, True)
2290            return
2291
2292        if self.stopSpeechOnActiveDescendantChanged(event):
2293            self.presentationInterrupt()
2294
2295        orca.setLocusOfFocus(event, event.any_data)
2296
2297    def onBusyChanged(self, event):
2298        """Callback for object:state-changed:busy accessibility events."""
2299        pass
2300
2301    def onCheckedChanged(self, event):
2302        """Callback for object:state-changed:checked accessibility events."""
2303
2304        obj = event.source
2305        if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
2306            return
2307
2308        state = obj.getState()
2309        if state.contains(pyatspi.STATE_EXPANDABLE):
2310            return
2311
2312        # Radio buttons normally change their state when you arrow to them,
2313        # so we handle the announcement of their state changes in the focus
2314        # handling code.  However, we do need to handle radio buttons where
2315        # the user needs to press the space key to select them.
2316        if obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
2317            eventString, mods = self.utilities.lastKeyAndModifiers()
2318            if not eventString in [" ", "space"]:
2319                return
2320
2321        oldObj, oldState = self.pointOfReference.get('checkedChange', (None, 0))
2322        if hash(oldObj) == hash(obj) and oldState == event.detail1:
2323            return
2324
2325        self.updateBraille(obj)
2326        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
2327        self.pointOfReference['checkedChange'] = hash(obj), event.detail1
2328
2329    def onChildrenAdded(self, event):
2330        """Callback for object:children-changed:add accessibility events."""
2331
2332        pass
2333
2334    def onChildrenRemoved(self, event):
2335        """Callback for object:children-changed:remove accessibility events."""
2336
2337        pass
2338
2339    def onCaretMoved(self, event):
2340        """Callback for object:text-caret-moved accessibility events."""
2341
2342        obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1))
2343        if offset == event.detail1 and obj == event.source:
2344            msg = "DEFAULT: Event is for last saved cursor position"
2345            debug.println(debug.LEVEL_INFO, msg, True)
2346            return
2347
2348        state = event.source.getState()
2349        if not state.contains(pyatspi.STATE_SHOWING):
2350            msg = "DEFAULT: Event source is not showing"
2351            debug.println(debug.LEVEL_INFO, msg, True)
2352            if not self.utilities.presentEventFromNonShowingObject(event):
2353                return
2354
2355        if event.source != orca_state.locusOfFocus \
2356           and state.contains(pyatspi.STATE_FOCUSED):
2357            topLevelObject = self.utilities.topLevelObject(event.source)
2358            if self.utilities.isSameObject(orca_state.activeWindow, topLevelObject):
2359                msg = "DEFAULT: Updating locusOfFocus from %s to %s" % \
2360                      (orca_state.locusOfFocus, event.source)
2361                debug.println(debug.LEVEL_INFO, msg, True)
2362                orca.setLocusOfFocus(event, event.source, False)
2363            else:
2364                msg = "DEFAULT: Source window (%s) is not active window(%s)" \
2365                      % (topLevelObject, orca_state.activeWindow)
2366                debug.println(debug.LEVEL_INFO, msg, True)
2367
2368        if event.source != orca_state.locusOfFocus:
2369            msg = "DEFAULT: Event source (%s) is not locusOfFocus (%s)" \
2370                  % (event.source, orca_state.locusOfFocus)
2371            debug.println(debug.LEVEL_INFO, msg, True)
2372            return
2373
2374        if self.flatReviewContext:
2375            self.toggleFlatReviewMode()
2376
2377        text = event.source.queryText()
2378        try:
2379            caretOffset = text.caretOffset
2380        except:
2381            msg = "DEFAULT: Exception getting caretOffset for %s" % event.source
2382            debug.println(debug.LEVEL_INFO, msg, True)
2383            return
2384
2385        self._saveLastCursorPosition(event.source, text.caretOffset)
2386        if text.getNSelections() > 0:
2387            msg = "DEFAULT: Event source has text selections"
2388            debug.println(debug.LEVEL_INFO, msg, True)
2389            self.utilities.handleTextSelectionChange(event.source)
2390            return
2391        else:
2392            start, end, string = self.utilities.getCachedTextSelection(obj)
2393            if string and self.utilities.handleTextSelectionChange(obj):
2394                msg = "DEFAULT: Event handled as text selection change"
2395                debug.println(debug.LEVEL_INFO, msg, True)
2396                return
2397
2398        msg = "DEFAULT: Presenting text at new caret position"
2399        debug.println(debug.LEVEL_INFO, msg, True)
2400        self._presentTextAtNewCaretPosition(event)
2401
2402    def onDescriptionChanged(self, event):
2403        """Callback for object:property-change:accessible-description events."""
2404
2405        obj = event.source
2406        descriptions = self.pointOfReference.get('description', {})
2407        oldDescription = descriptions.get(hash(obj))
2408        if oldDescription == event.any_data:
2409            msg = "DEFAULT: Old description (%s) is the same as new one" % oldDescription
2410            debug.println(debug.LEVEL_INFO, msg, True)
2411            return
2412
2413        if obj != orca_state.locusOfFocus:
2414            msg = "DEFAULT: Event is for object other than the locusOfFocus"
2415            debug.println(debug.LEVEL_INFO, msg, True)
2416            return
2417
2418        descriptions[hash(obj)] = event.any_data
2419        self.pointOfReference['descriptions'] = descriptions
2420        if event.any_data:
2421            self.presentMessage(event.any_data)
2422
2423    def onDocumentReload(self, event):
2424        """Callback for document:reload accessibility events."""
2425
2426        pass
2427
2428    def onDocumentLoadComplete(self, event):
2429        """Callback for document:load-complete accessibility events."""
2430
2431        pass
2432
2433    def onDocumentLoadStopped(self, event):
2434        """Callback for document:load-stopped accessibility events."""
2435
2436        pass
2437
2438    def onExpandedChanged(self, event):
2439        """Callback for object:state-changed:expanded accessibility events."""
2440
2441        if not self.utilities.isPresentableExpandedChangedEvent(event):
2442            return
2443
2444        obj = event.source
2445        oldObj, oldState = self.pointOfReference.get('expandedChange', (None, 0))
2446        if hash(oldObj) == hash(obj) and oldState == event.detail1:
2447            return
2448
2449        self.updateBraille(obj)
2450        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
2451        self.pointOfReference['expandedChange'] = hash(obj), event.detail1
2452
2453        details = self.utilities.detailsContentForObject(obj)
2454        for detail in details:
2455            self.speakMessage(detail, interrupt=False)
2456
2457    def onIndeterminateChanged(self, event):
2458        """Callback for object:state-changed:indeterminate accessibility events."""
2459
2460        # If this state is cleared, the new state will become checked or unchecked
2461        # and we should get object:state-changed:checked events for those cases.
2462        # Therefore, if the state is not now indeterminate/partially checked,
2463        # ignore this event.
2464        if not event.detail1:
2465            return
2466
2467        obj = event.source
2468        if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
2469            return
2470
2471        oldObj, oldState = self.pointOfReference.get('indeterminateChange', (None, 0))
2472        if hash(oldObj) == hash(obj) and oldState == event.detail1:
2473            return
2474
2475        self.updateBraille(obj)
2476        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
2477        self.pointOfReference['indeterminateChange'] = hash(obj), event.detail1
2478
2479    def onMouseButton(self, event):
2480        """Callback for mouse:button events."""
2481
2482        mouseEvent = input_event.MouseButtonEvent(event)
2483        orca_state.lastInputEvent = mouseEvent
2484        if not mouseEvent.pressed:
2485            return
2486
2487        windowChanged = orca_state.activeWindow != mouseEvent.window
2488        if windowChanged:
2489            orca_state.activeWindow = mouseEvent.window
2490            orca.setLocusOfFocus(None, mouseEvent.window, False)
2491
2492        self.presentationInterrupt()
2493        obj = mouseEvent.obj
2494        if obj and obj.getState().contains(pyatspi.STATE_FOCUSED):
2495            orca.setLocusOfFocus(None, obj, windowChanged)
2496
2497    def onNameChanged(self, event):
2498        """Callback for object:property-change:accessible-name events."""
2499
2500        obj = event.source
2501        names = self.pointOfReference.get('names', {})
2502        oldName = names.get(hash(obj))
2503        if oldName == event.any_data:
2504            msg = "DEFAULT: Old name (%s) is the same as new name" % oldName
2505            debug.println(debug.LEVEL_INFO, msg, True)
2506            return
2507
2508        role = obj.getRole()
2509        if role in [pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_TABLE_CELL]:
2510            msg = "DEFAULT: Event is redundant notification for this role"
2511            debug.println(debug.LEVEL_INFO, msg, True)
2512            return
2513
2514        if role == pyatspi.ROLE_FRAME:
2515            if obj != orca_state.activeWindow:
2516                msg = "DEFAULT: Event is for frame other than the active window"
2517                debug.println(debug.LEVEL_INFO, msg, True)
2518                return
2519        elif obj != orca_state.locusOfFocus:
2520            msg = "DEFAULT: Event is for object other than the locusOfFocus"
2521            debug.println(debug.LEVEL_INFO, msg, True)
2522            return
2523
2524        names[hash(obj)] = event.any_data
2525        self.pointOfReference['names'] = names
2526        self.updateBraille(obj)
2527        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
2528
2529    def onPressedChanged(self, event):
2530        """Callback for object:state-changed:pressed accessibility events."""
2531
2532        obj = event.source
2533        if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
2534            return
2535
2536        oldObj, oldState = self.pointOfReference.get('pressedChange', (None, 0))
2537        if hash(oldObj) == hash(obj) and oldState == event.detail1:
2538            return
2539
2540        self.updateBraille(obj)
2541        speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True))
2542        self.pointOfReference['pressedChange'] = hash(obj), event.detail1
2543
2544    def onSelectedChanged(self, event):
2545        """Callback for object:state-changed:selected accessibility events."""
2546
2547        obj = event.source
2548        obj.clearCache()
2549        state = obj.getState()
2550        if not state.contains(pyatspi.STATE_FOCUSED):
2551            return
2552
2553        if not self.utilities.isSameObject(orca_state.locusOfFocus, obj):
2554            return
2555
2556        if _settingsManager.getSetting('onlySpeakDisplayedText'):
2557            return
2558
2559        isSelected = state.contains(pyatspi.STATE_SELECTED)
2560        if isSelected != event.detail1:
2561            msg = "DEFAULT: Bogus event: detail1 doesn't match state"
2562            debug.println(debug.LEVEL_INFO, msg, True)
2563            return
2564
2565        oldObj, oldState = self.pointOfReference.get('selectedChange', (None, 0))
2566        if hash(oldObj) == hash(obj) and oldState == event.detail1:
2567            msg = "DEFAULT: Duplicate or spam event"
2568            debug.println(debug.LEVEL_INFO, msg, True)
2569            return
2570
2571        announceState = False
2572        keyString, mods = self.utilities.lastKeyAndModifiers()
2573        if keyString == "space":
2574            announceState = True
2575        elif keyString in ["Down", "Up"] \
2576             and isSelected and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
2577            announceState = True
2578
2579        if not announceState:
2580            return
2581
2582        # TODO - JD: Unlike the other state-changed callbacks, it seems unwise
2583        # to call generateSpeech() here because that also will present the
2584        # expandable state if appropriate for the object type. The generators
2585        # need to gain some smarts w.r.t. state changes.
2586
2587        if event.detail1:
2588            self.speakMessage(messages.TEXT_SELECTED, interrupt=False)
2589        else:
2590            self.speakMessage(messages.TEXT_UNSELECTED, interrupt=False)
2591
2592        self.pointOfReference['selectedChange'] = hash(obj), event.detail1
2593
2594    def onSelectionChanged(self, event):
2595        """Callback for object:selection-changed accessibility events."""
2596
2597        obj = event.source
2598        state = obj.getState()
2599
2600        if self.utilities.handlePasteLocusOfFocusChange():
2601            if self.utilities.topLevelObjectIsActiveAndCurrent(event.source):
2602                orca.setLocusOfFocus(event, event.source, False)
2603        elif self.utilities.handleContainerSelectionChange(event.source):
2604            return
2605        else:
2606            if state.contains(pyatspi.STATE_MANAGES_DESCENDANTS):
2607                return
2608
2609        # TODO - JD: We need to give more thought to where we look to this
2610        # event and where we prefer object:state-changed:selected.
2611
2612        # If the current item's selection is toggled, we'll present that
2613        # via the state-changed event.
2614        keyString, mods = self.utilities.lastKeyAndModifiers()
2615        if keyString == "space":
2616            return
2617
2618        role = obj.getRole()
2619        if role == pyatspi.ROLE_COMBO_BOX and not state.contains(pyatspi.STATE_EXPANDED):
2620            entry = self.utilities.getEntryForEditableComboBox(event.source)
2621            if entry and entry.getState().contains(pyatspi.STATE_FOCUSED):
2622                return
2623
2624        # If a wizard-like notebook page being reviewed changes, we might not get
2625        # any events to update the locusOfFocus. As a result, subsequent flat
2626        # review commands will continue to present the stale content.
2627        if role == pyatspi.ROLE_PAGE_TAB_LIST and self.flatReviewContext:
2628            self.flatReviewContext = None
2629
2630        mouseReviewItem = mouse_review.reviewer.getCurrentItem()
2631        selectedChildren = self.utilities.selectedChildren(obj)
2632        for child in selectedChildren:
2633            if pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == child):
2634                msg = "DEFAULT: Child %s is ancestor of locusOfFocus" % child
2635                debug.println(debug.LEVEL_INFO, msg, True)
2636                self._saveFocusedObjectInfo(orca_state.locusOfFocus)
2637                return
2638
2639            if child == mouseReviewItem:
2640                msg = "DEFAULT: Child %s is current mouse review item" % child
2641                debug.println(debug.LEVEL_INFO, msg, True)
2642                continue
2643
2644            if child.getRole() == pyatspi.ROLE_PAGE_TAB and orca_state.locusOfFocus \
2645               and child.name == orca_state.locusOfFocus.name \
2646               and not state.contains(pyatspi.STATE_FOCUSED):
2647                msg = "DEFAULT: %s's selection redundant to %s" % (child, orca_state.locusOfFocus)
2648                debug.println(debug.LEVEL_INFO, msg, True)
2649                break
2650
2651            if not self.utilities.isLayoutOnly(child):
2652                orca.setLocusOfFocus(event, child)
2653                break
2654
2655    def onSensitiveChanged(self, event):
2656        """Callback for object:state-changed:sensitive accessibility events."""
2657        pass
2658
2659    def onFocus(self, event):
2660        """Callback for focus: accessibility events."""
2661
2662        pass
2663
2664    def onFocusedChanged(self, event):
2665        """Callback for object:state-changed:focused accessibility events."""
2666
2667        if not event.detail1:
2668            return
2669
2670        obj = event.source
2671        state = obj.getState()
2672        if not state.contains(pyatspi.STATE_FOCUSED):
2673            return
2674
2675        window, dialog = self.utilities.frameAndDialog(obj)
2676        clearCache = window != orca_state.activeWindow
2677        if window and not self.utilities.canBeActiveWindow(window, clearCache) and not dialog:
2678            return
2679
2680        try:
2681            childCount = obj.childCount
2682            role = obj.getRole()
2683        except:
2684            msg = "DEFAULT: Exception getting childCount and role for %s" % obj
2685            debug.println(debug.LEVEL_INFO, msg, True)
2686            return
2687
2688        if childCount and role != pyatspi.ROLE_COMBO_BOX:
2689            selectedChildren = self.utilities.selectedChildren(obj)
2690            if selectedChildren:
2691                obj = selectedChildren[0]
2692
2693        orca.setLocusOfFocus(event, obj)
2694
2695    def onShowingChanged(self, event):
2696        """Callback for object:state-changed:showing accessibility events."""
2697
2698        obj = event.source
2699        role = obj.getRole()
2700        if role == pyatspi.ROLE_NOTIFICATION:
2701            speech.speak(self.speechGenerator.generateSpeech(obj))
2702            visibleOnly = not self.utilities.isStatusBarNotification(obj)
2703            labels = self.utilities.unrelatedLabels(obj, visibleOnly, 1)
2704            msg = ''.join(map(self.utilities.displayedText, labels))
2705            self.displayBrailleMessage(msg, flashTime=settings.brailleFlashTime)
2706            notification_messages.saveMessage(msg)
2707            return
2708
2709        if role == pyatspi.ROLE_TOOL_TIP:
2710            keyString, mods = self.utilities.lastKeyAndModifiers()
2711            if keyString != "F1" \
2712               and not _settingsManager.getSetting('presentToolTips'):
2713                return
2714            if event.detail1:
2715                self.presentObject(obj)
2716                return
2717
2718            if orca_state.locusOfFocus and keyString == "F1":
2719                obj = orca_state.locusOfFocus
2720                self.updateBraille(obj)
2721                speech.speak(self.speechGenerator.generateSpeech(obj, priorObj=event.source))
2722                return
2723
2724    def onTextAttributesChanged(self, event):
2725        """Callback for object:text-attributes-changed accessibility events."""
2726
2727        if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event):
2728            return
2729
2730        text = self.utilities.queryNonEmptyText(event.source)
2731        if not text:
2732            msg = "DEFAULT: Querying non-empty text returned None"
2733            debug.println(debug.LEVEL_INFO, msg, True)
2734            return
2735
2736        if _settingsManager.getSetting('speakMisspelledIndicator'):
2737            offset = text.caretOffset
2738            if not text.getText(offset, offset+1).isalnum():
2739                offset -= 1
2740            if self.utilities.isWordMisspelled(event.source, offset-1) \
2741               or self.utilities.isWordMisspelled(event.source, offset+1):
2742                self.speakMessage(messages.MISSPELLED)
2743
2744    def onTextDeleted(self, event):
2745        """Callback for object:text-changed:delete accessibility events."""
2746
2747        if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event):
2748            return
2749
2750        self.utilities.handleUndoTextEvent(event)
2751
2752        orca.setLocusOfFocus(event, event.source, False)
2753        self.updateBraille(event.source)
2754
2755        full, brief = "", ""
2756        if self.utilities.isClipboardTextChangedEvent(event):
2757            msg = "DEFAULT: Deletion is believed to be due to clipboard cut"
2758            debug.println(debug.LEVEL_INFO, msg, True)
2759            full, brief = messages.CLIPBOARD_CUT_FULL, messages.CLIPBOARD_CUT_BRIEF
2760        elif self.utilities.isSelectedTextDeletionEvent(event):
2761            msg = "DEFAULT: Deletion is believed to be due to deleting selected text"
2762            debug.println(debug.LEVEL_INFO, msg, True)
2763            full = messages.SELECTION_DELETED
2764
2765        if full or brief:
2766            self.presentMessage(full, brief)
2767            self.utilities.updateCachedTextSelection(event.source)
2768            return
2769
2770        string = self.utilities.deletedText(event)
2771        if self.utilities.isDeleteCommandTextDeletionEvent(event):
2772            msg = "DEFAULT: Deletion is believed to be due to Delete command"
2773            debug.println(debug.LEVEL_INFO, msg, True)
2774            string = self.utilities.getCharacterAtOffset(event.source)
2775        elif self.utilities.isBackSpaceCommandTextDeletionEvent(event):
2776            msg = "DEFAULT: Deletion is believed to be due to BackSpace command"
2777            debug.println(debug.LEVEL_INFO, msg, True)
2778        else:
2779            msg = "INFO: Event is not being presented due to lack of cause"
2780            debug.println(debug.LEVEL_INFO, msg, True)
2781            return
2782
2783        if len(string) == 1:
2784            self.speakCharacter(string)
2785        else:
2786            voice = self.speechGenerator.voice(string=string)
2787            string = self.utilities.adjustForRepeats(string)
2788            speech.speak(string, voice)
2789
2790    def onTextInserted(self, event):
2791        """Callback for object:text-changed:insert accessibility events."""
2792
2793        if not self.utilities.isPresentableTextChangedEventForLocusOfFocus(event):
2794            return
2795
2796        self.utilities.handleUndoTextEvent(event)
2797
2798        if event.source == orca_state.locusOfFocus and self.utilities.isAutoTextEvent(event):
2799            self._saveFocusedObjectInfo(event.source)
2800        orca.setLocusOfFocus(event, event.source, False)
2801        self.updateBraille(event.source)
2802
2803        full, brief = "", ""
2804        if self.utilities.isClipboardTextChangedEvent(event):
2805            msg = "DEFAULT: Insertion is believed to be due to clipboard paste"
2806            debug.println(debug.LEVEL_INFO, msg, True)
2807            full, brief = messages.CLIPBOARD_PASTED_FULL, messages.CLIPBOARD_PASTED_BRIEF
2808        elif self.utilities.isSelectedTextRestoredEvent(event):
2809            msg = "DEFAULT: Insertion is believed to be due to restoring selected text"
2810            debug.println(debug.LEVEL_INFO, msg, True)
2811            full = messages.SELECTION_RESTORED
2812
2813        if full or brief:
2814            self.presentMessage(full, brief)
2815            self.utilities.updateCachedTextSelection(event.source)
2816            return
2817
2818        speakString = True
2819
2820        # Because some implementations are broken.
2821        string = self.utilities.insertedText(event)
2822
2823        if self.utilities.lastInputEventWasPageSwitch():
2824            msg = "DEFAULT: Insertion is believed to be due to page switch"
2825            debug.println(debug.LEVEL_INFO, msg, True)
2826            speakString = False
2827        elif self.utilities.lastInputEventWasCommand():
2828            msg = "DEFAULT: Insertion is believed to be due to command"
2829            debug.println(debug.LEVEL_INFO, msg, True)
2830        elif self.utilities.isMiddleMouseButtonTextInsertionEvent(event):
2831            msg = "DEFAULT: Insertion is believed to be due to middle mouse button"
2832            debug.println(debug.LEVEL_INFO, msg, True)
2833        elif self.utilities.isEchoableTextInsertionEvent(event):
2834            msg = "DEFAULT: Insertion is believed to be echoable"
2835            debug.println(debug.LEVEL_INFO, msg, True)
2836        elif self.utilities.isAutoTextEvent(event):
2837            msg = "DEFAULT: Insertion is believed to be auto text event"
2838            debug.println(debug.LEVEL_INFO, msg, True)
2839        elif self.utilities.isSelectedTextInsertionEvent(event):
2840            msg = "DEFAULT: Insertion is also selected"
2841            debug.println(debug.LEVEL_INFO, msg, True)
2842        else:
2843            msg = "DEFAULT: Not speaking inserted string due to lack of cause"
2844            debug.println(debug.LEVEL_INFO, msg, True)
2845            speakString = False
2846
2847        if speakString:
2848            if len(string) == 1:
2849                self.speakCharacter(string)
2850            else:
2851                voice = self.speechGenerator.voice(string=string)
2852                string = self.utilities.adjustForRepeats(string)
2853                speech.speak(string, voice)
2854
2855        if len(string) != 1:
2856            return
2857
2858        if _settingsManager.getSetting('enableEchoBySentence') \
2859           and self.echoPreviousSentence(event.source):
2860            return
2861
2862        if _settingsManager.getSetting('enableEchoByWord'):
2863            self.echoPreviousWord(event.source)
2864
2865    def onTextSelectionChanged(self, event):
2866        """Callback for object:text-selection-changed accessibility events."""
2867
2868        obj = event.source
2869
2870        # We won't handle undo here as it can lead to double-presentation.
2871        # If there is an application for which text-changed events are
2872        # missing upon undo, handle them in an app or toolkit script.
2873
2874        self.utilities.handleTextSelectionChange(obj)
2875        self.updateBraille(obj)
2876
2877    def onColumnReordered(self, event):
2878        """Callback for object:column-reordered accessibility events."""
2879
2880        if not self.utilities.lastInputEventWasTableSort():
2881            return
2882
2883        if event.source != self.utilities.getTable(orca_state.locusOfFocus):
2884            return
2885
2886        self.pointOfReference['last-table-sort-time'] = time.time()
2887        self.presentMessage(messages.TABLE_REORDERED_COLUMNS)
2888
2889    def onRowReordered(self, event):
2890        """Callback for object:row-reordered accessibility events."""
2891
2892        if not self.utilities.lastInputEventWasTableSort():
2893            return
2894
2895        if event.source != self.utilities.getTable(orca_state.locusOfFocus):
2896            return
2897
2898        self.pointOfReference['last-table-sort-time'] = time.time()
2899        self.presentMessage(messages.TABLE_REORDERED_ROWS)
2900
2901    def onValueChanged(self, event):
2902        """Called whenever an object's value changes.  Currently, the
2903        value changes for non-focused objects are ignored.
2904
2905        Arguments:
2906        - event: the Event
2907        """
2908
2909        obj = event.source
2910        role = obj.getRole()
2911
2912        try:
2913            value = obj.queryValue()
2914            currentValue = value.currentValue
2915        except NotImplementedError:
2916            msg = "ERROR: %s doesn't implement AtspiValue" % obj
2917            debug.println(debug.LEVEL_INFO, msg, True)
2918            return
2919        except:
2920            msg = "ERROR: Exception getting current value for %s" % obj
2921            debug.println(debug.LEVEL_INFO, msg, True)
2922            return
2923
2924        if "oldValue" in self.pointOfReference \
2925           and (currentValue == self.pointOfReference["oldValue"]):
2926            return
2927
2928        isProgressBarUpdate, msg = self.utilities.isProgressBarUpdate(obj, event)
2929        msg = "DEFAULT: Is progress bar update: %s, %s" % (isProgressBarUpdate, msg)
2930        debug.println(debug.LEVEL_INFO, msg, True)
2931
2932        if not isProgressBarUpdate and obj != orca_state.locusOfFocus:
2933            msg = "DEFAULT: Source != locusOfFocus (%s)" % orca_state.locusOfFocus
2934            debug.println(debug.LEVEL_INFO, msg, True)
2935            return
2936
2937        if role == pyatspi.ROLE_SPIN_BUTTON:
2938            self._saveFocusedObjectInfo(event.source)
2939
2940        self.pointOfReference["oldValue"] = currentValue
2941        self.updateBraille(obj, isProgressBarUpdate=isProgressBarUpdate)
2942        speech.speak(self.speechGenerator.generateSpeech(
2943            obj, alreadyFocused=True, isProgressBarUpdate=isProgressBarUpdate))
2944        self.__play(self.soundGenerator.generateSound(
2945            obj, alreadyFocused=True, isProgressBarUpdate=isProgressBarUpdate))
2946
2947    def onWindowActivated(self, event):
2948        """Called whenever a toplevel window is activated.
2949
2950        Arguments:
2951        - event: the Event
2952        """
2953
2954        if not self.utilities.canBeActiveWindow(event.source, False):
2955            return
2956
2957        if self.utilities.isSameObject(event.source, orca_state.activeWindow):
2958            msg = "DEFAULT: Event is for active window."
2959            debug.println(debug.LEVEL_INFO, msg, True)
2960            return
2961
2962        self.pointOfReference = {}
2963
2964        self.windowActivateTime = time.time()
2965        orca_state.activeWindow = event.source
2966
2967        if self.utilities.isKeyGrabEvent(event):
2968            msg = "DEFAULT: Ignoring event. Likely from key grab."
2969            debug.println(debug.LEVEL_INFO, msg, True)
2970            return
2971
2972        try:
2973            childCount = event.source.childCount
2974            childRole = event.source[0].getRole()
2975        except:
2976            pass
2977        else:
2978            if childCount == 1 and childRole == pyatspi.ROLE_MENU:
2979                orca.setLocusOfFocus(event, event.source[0])
2980                return
2981
2982        orca.setLocusOfFocus(event, event.source)
2983
2984    def onWindowCreated(self, event):
2985        """Callback for window:create accessibility events."""
2986
2987        pass
2988
2989    def onWindowDestroyed(self, event):
2990        """Callback for window:destroy accessibility events."""
2991
2992        pass
2993
2994    def onWindowDeactivated(self, event):
2995        """Called whenever a toplevel window is deactivated.
2996
2997        Arguments:
2998        - event: the Event
2999        """
3000
3001        if self.utilities.inMenu():
3002            msg = "DEFAULT: Ignoring event. In menu."
3003            debug.println(debug.LEVEL_INFO, msg, True)
3004            return
3005
3006        if event.source != orca_state.activeWindow:
3007            msg = "DEFAULT: Ignoring event. Not for active window %s." % orca_state.activeWindow
3008            debug.println(debug.LEVEL_INFO, msg, True)
3009            return
3010
3011        if self.utilities.isKeyGrabEvent(event):
3012            msg = "DEFAULT: Ignoring event. Likely from key grab."
3013            debug.println(debug.LEVEL_INFO, msg, True)
3014            return
3015
3016        self.presentationInterrupt()
3017        self.clearBraille()
3018
3019        if self.flatReviewContext:
3020            self.flatReviewContext = None
3021
3022        self.pointOfReference = {}
3023
3024        if not self.utilities.eventIsUserTriggered(event):
3025            msg = "DEFAULT: Not clearing state. Event is not user triggered."
3026            debug.println(debug.LEVEL_INFO, msg, True)
3027            return
3028
3029        msg = "DEFAULT: Clearing state."
3030        debug.println(debug.LEVEL_INFO, msg, True)
3031
3032        orca.setLocusOfFocus(event, None)
3033        orca_state.activeWindow = None
3034        orca_state.activeScript = None
3035        orca_state.listNotificationsModeEnabled = False
3036        orca_state.learnModeEnabled = False
3037
3038    def onClipboardContentsChanged(self, *args):
3039        if self.flatReviewContext:
3040            return
3041
3042        if not self.utilities.objectContentsAreInClipboard():
3043            return
3044
3045        if not self.utilities.topLevelObjectIsActiveAndCurrent():
3046            return
3047
3048        if self.utilities.lastInputEventWasCopy():
3049            self.presentMessage(messages.CLIPBOARD_COPIED_FULL, messages.CLIPBOARD_COPIED_BRIEF)
3050            return
3051
3052        if not self.utilities.lastInputEventWasCut():
3053            return
3054
3055        try:
3056            state = orca_state.locusOfFocus.getState()
3057        except:
3058            msg = "ERROR: Exception getting state of %s" % orca_state.locusOfFocus
3059            debug.println(debug.LEVEL_INFO, msg, True)
3060        else:
3061            if state.contains(pyatspi.STATE_EDITABLE):
3062                return
3063
3064        self.presentMessage(messages.CLIPBOARD_CUT_FULL, messages.CLIPBOARD_CUT_BRIEF)
3065
3066    ########################################################################
3067    #                                                                      #
3068    # Methods for presenting content                                       #
3069    #                                                                      #
3070    ########################################################################
3071
3072    def _presentTextAtNewCaretPosition(self, event, otherObj=None):
3073        obj = otherObj or event.source
3074        self.updateBrailleForNewCaretPosition(obj)
3075        if self._inSayAll:
3076            return
3077
3078        if self.utilities.lastInputEventWasLineNav():
3079            msg = "DEFAULT: Presenting result of line nav"
3080            debug.println(debug.LEVEL_INFO, msg, True)
3081            self.sayLine(obj)
3082            return
3083
3084        if self.utilities.lastInputEventWasWordNav():
3085            msg = "DEFAULT: Presenting result of word nav"
3086            debug.println(debug.LEVEL_INFO, msg, True)
3087            self.sayWord(obj)
3088            return
3089
3090        if self.utilities.lastInputEventWasCharNav():
3091            msg = "DEFAULT: Presenting result of char nav"
3092            debug.println(debug.LEVEL_INFO, msg, True)
3093            self.sayCharacter(obj)
3094            return
3095
3096        if self.utilities.lastInputEventWasPageNav():
3097            msg = "DEFAULT: Presenting result of page nav"
3098            debug.println(debug.LEVEL_INFO, msg, True)
3099            self.sayLine(obj)
3100            return
3101
3102        if self.utilities.lastInputEventWasLineBoundaryNav():
3103            msg = "DEFAULT: Presenting result of line boundary nav"
3104            debug.println(debug.LEVEL_INFO, msg, True)
3105            self.sayCharacter(obj)
3106            return
3107
3108        if self.utilities.lastInputEventWasFileBoundaryNav():
3109            msg = "DEFAULT: Presenting result of file boundary nav"
3110            debug.println(debug.LEVEL_INFO, msg, True)
3111            self.sayLine(obj)
3112            return
3113
3114        if self.utilities.lastInputEventWasPrimaryMouseRelease():
3115            start, end, string = self.utilities.getCachedTextSelection(event.source)
3116            if not string:
3117                msg = "DEFAULT: Presenting result of primary mouse button release"
3118                debug.println(debug.LEVEL_INFO, msg, True)
3119                self.sayLine(obj)
3120                return
3121
3122    def _rewindSayAll(self, context, minCharCount=10):
3123        if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'):
3124            return False
3125
3126        index = self._sayAllContexts.index(context)
3127        self._sayAllContexts = self._sayAllContexts[0:index]
3128        while self._sayAllContexts:
3129            context = self._sayAllContexts.pop()
3130            if context.endOffset - context.startOffset > minCharCount:
3131                break
3132
3133        try:
3134            text = context.obj.queryText()
3135        except:
3136            pass
3137        else:
3138            orca.setLocusOfFocus(None, context.obj, notifyScript=False)
3139            text.setCaretOffset(context.startOffset)
3140
3141        self.sayAll(None, context.obj, context.startOffset)
3142        return True
3143
3144    def _fastForwardSayAll(self, context):
3145        if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'):
3146            return False
3147
3148        try:
3149            text = context.obj.queryText()
3150        except:
3151            pass
3152        else:
3153            orca.setLocusOfFocus(None, context.obj, notifyScript=False)
3154            text.setCaretOffset(context.endOffset)
3155
3156        self.sayAll(None, context.obj, context.endOffset)
3157        return True
3158
3159    def __sayAllProgressCallback(self, context, progressType):
3160        # [[[TODO: WDW - this needs work.  Need to be able to manage
3161        # the monitoring of progress and couple that with both updating
3162        # the visual progress of what is being spoken as well as
3163        # positioning the cursor when speech has stopped.]]]
3164        #
3165        try:
3166            text = context.obj.queryText()
3167            char = text.getText(context.currentOffset, context.currentOffset+1)
3168        except:
3169            return
3170
3171        # Setting the caret at the offset of an embedded object results in
3172        # focus changes.
3173        if char == self.EMBEDDED_OBJECT_CHARACTER:
3174            return
3175
3176        if progressType == speechserver.SayAllContext.PROGRESS:
3177            orca.emitRegionChanged(
3178                context.obj, context.currentOffset, context.currentEndOffset, orca.SAY_ALL)
3179            return
3180
3181        if progressType == speechserver.SayAllContext.INTERRUPTED:
3182            if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
3183                self._sayAllIsInterrupted = True
3184                lastKey = orca_state.lastInputEvent.event_string
3185                if lastKey == "Down" and self._fastForwardSayAll(context):
3186                    return
3187                elif lastKey == "Up" and self._rewindSayAll(context):
3188                    return
3189
3190            self._inSayAll = False
3191            self._sayAllContexts = []
3192            orca.emitRegionChanged(context.obj, context.currentOffset)
3193            text.setCaretOffset(context.currentOffset)
3194        elif progressType == speechserver.SayAllContext.COMPLETED:
3195            orca.setLocusOfFocus(None, context.obj, notifyScript=False)
3196            orca.emitRegionChanged(context.obj, context.currentOffset, mode=orca.SAY_ALL)
3197            text.setCaretOffset(context.currentOffset)
3198
3199        # If there is a selection, clear it. See bug #489504 for more details.
3200        #
3201        if text.getNSelections() > 0:
3202            text.setSelection(0, context.currentOffset, context.currentOffset)
3203
3204    def inSayAll(self, treatInterruptedAsIn=True):
3205        if self._inSayAll:
3206            msg = "DEFAULT: In SayAll"
3207            debug.println(debug.LEVEL_INFO, msg, True)
3208            return True
3209
3210        if self._sayAllIsInterrupted:
3211            msg = "DEFAULT: SayAll is interrupted"
3212            debug.println(debug.LEVEL_INFO, msg, True)
3213            return treatInterruptedAsIn
3214
3215        msg = "DEFAULT: Not in SayAll"
3216        debug.println(debug.LEVEL_INFO, msg, True)
3217        return False
3218
3219    def echoPreviousSentence(self, obj):
3220        """Speaks the sentence prior to the caret, as long as there is
3221        a sentence prior to the caret and there is no intervening sentence
3222        delimiter between the caret and the end of the sentence.
3223
3224        The entry condition for this method is that the character
3225        prior to the current caret position is a sentence delimiter,
3226        and it's what caused this method to be called in the first
3227        place.
3228
3229        Arguments:
3230        - obj: an Accessible object that implements the AccessibleText
3231        interface.
3232        """
3233
3234        try:
3235            text = obj.queryText()
3236        except NotImplementedError:
3237            return False
3238
3239        offset = text.caretOffset - 1
3240        previousOffset = text.caretOffset - 2
3241        if (offset < 0 or previousOffset < 0):
3242            return False
3243
3244        [currentChar, startOffset, endOffset] = \
3245            text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
3246        [previousChar, startOffset, endOffset] = \
3247            text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
3248        if not self.utilities.isSentenceDelimiter(currentChar, previousChar):
3249            return False
3250
3251        # OK - we seem to be cool so far.  So...starting with what
3252        # should be the last character in the sentence (caretOffset - 2),
3253        # work our way to the beginning of the sentence, stopping when
3254        # we hit another sentence delimiter.
3255        #
3256        sentenceEndOffset = text.caretOffset - 2
3257        sentenceStartOffset = sentenceEndOffset
3258
3259        while sentenceStartOffset >= 0:
3260            [currentChar, startOffset, endOffset] = \
3261                text.getTextAtOffset(sentenceStartOffset,
3262                                     pyatspi.TEXT_BOUNDARY_CHAR)
3263            [previousChar, startOffset, endOffset] = \
3264                text.getTextAtOffset(sentenceStartOffset-1,
3265                                     pyatspi.TEXT_BOUNDARY_CHAR)
3266            if self.utilities.isSentenceDelimiter(currentChar, previousChar):
3267                break
3268            else:
3269                sentenceStartOffset -= 1
3270
3271        # If we came across a sentence delimiter before hitting any
3272        # text, we really don't have a previous sentence.
3273        #
3274        # Otherwise, get the sentence.  Remember we stopped when we
3275        # hit a sentence delimiter, so the sentence really starts at
3276        # sentenceStartOffset + 1.  getText also does not include
3277        # the character at sentenceEndOffset, so we need to adjust
3278        # for that, too.
3279        #
3280        if sentenceStartOffset == sentenceEndOffset:
3281            return False
3282        else:
3283            sentence = self.utilities.substring(obj, sentenceStartOffset + 1,
3284                                         sentenceEndOffset + 1)
3285
3286        voice = self.speechGenerator.voice(string=sentence)
3287        sentence = self.utilities.adjustForRepeats(sentence)
3288        speech.speak(sentence, voice)
3289        return True
3290
3291    def echoPreviousWord(self, obj, offset=None):
3292        """Speaks the word prior to the caret, as long as there is
3293        a word prior to the caret and there is no intervening word
3294        delimiter between the caret and the end of the word.
3295
3296        The entry condition for this method is that the character
3297        prior to the current caret position is a word delimiter,
3298        and it's what caused this method to be called in the first
3299        place.
3300
3301        Arguments:
3302        - obj: an Accessible object that implements the AccessibleText
3303               interface.
3304        - offset: if not None, the offset within the text to use as the
3305                  end of the word.
3306        """
3307
3308        try:
3309            text = obj.queryText()
3310        except NotImplementedError:
3311            return False
3312
3313        if not offset:
3314            if text.caretOffset == -1:
3315                offset = text.characterCount
3316            else:
3317                offset = text.caretOffset - 1
3318
3319        if (offset < 0):
3320            return False
3321
3322        [char, startOffset, endOffset] = \
3323            text.getTextAtOffset( \
3324                offset,
3325                pyatspi.TEXT_BOUNDARY_CHAR)
3326        if not self.utilities.isWordDelimiter(char):
3327            return False
3328
3329        # OK - we seem to be cool so far.  So...starting with what
3330        # should be the last character in the word (caretOffset - 2),
3331        # work our way to the beginning of the word, stopping when
3332        # we hit another word delimiter.
3333        #
3334        wordEndOffset = offset - 1
3335        wordStartOffset = wordEndOffset
3336
3337        while wordStartOffset >= 0:
3338            [char, startOffset, endOffset] = \
3339                text.getTextAtOffset( \
3340                    wordStartOffset,
3341                    pyatspi.TEXT_BOUNDARY_CHAR)
3342            if self.utilities.isWordDelimiter(char):
3343                break
3344            else:
3345                wordStartOffset -= 1
3346
3347        # If we came across a word delimiter before hitting any
3348        # text, we really don't have a previous word.
3349        #
3350        # Otherwise, get the word.  Remember we stopped when we
3351        # hit a word delimiter, so the word really starts at
3352        # wordStartOffset + 1.  getText also does not include
3353        # the character at wordEndOffset, so we need to adjust
3354        # for that, too.
3355        #
3356        if wordStartOffset == wordEndOffset:
3357            return False
3358        else:
3359            word = self.utilities.\
3360                substring(obj, wordStartOffset + 1, wordEndOffset + 1)
3361
3362        voice = self.speechGenerator.voice(string=word)
3363        word = self.utilities.adjustForRepeats(word)
3364        speech.speak(word, voice)
3365        return True
3366
3367    def sayCharacter(self, obj):
3368        """Speak the character at the caret.
3369
3370        Arguments:
3371        - obj: an Accessible object that implements the AccessibleText
3372               interface
3373        """
3374
3375        text = obj.queryText()
3376        offset = text.caretOffset
3377
3378        # If we have selected text and the last event was a move to the
3379        # right, then speak the character to the left of where the text
3380        # caret is (i.e. the selected character).
3381        #
3382        eventString, mods = self.utilities.lastKeyAndModifiers()
3383        if (mods & keybindings.SHIFT_MODIFIER_MASK) \
3384           and eventString in ["Right", "Down"]:
3385            offset -= 1
3386
3387        character, startOffset, endOffset = text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
3388        orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING)
3389
3390        if not character or character == '\r':
3391            character = "\n"
3392
3393        speakBlankLines = _settingsManager.getSetting('speakBlankLines')
3394        if character == "\n":
3395            line = text.getTextAtOffset(max(0, offset),
3396                                        pyatspi.TEXT_BOUNDARY_LINE_START)
3397            if not line[0] or line[0] == "\n":
3398                # This is a blank line. Announce it if the user requested
3399                # that blank lines be spoken.
3400                if speakBlankLines:
3401                    self.speakMessage(messages.BLANK, interrupt=False)
3402                return
3403
3404        if character in ["\n", "\r\n"]:
3405            # This is a blank line. Announce it if the user requested
3406            # that blank lines be spoken.
3407            if speakBlankLines:
3408                self.speakMessage(messages.BLANK, interrupt=False)
3409            return
3410        else:
3411            self.speakMisspelledIndicator(obj, offset)
3412            self.speakCharacter(character)
3413
3414        self.pointOfReference["lastTextUnitSpoken"] = "char"
3415
3416    def sayLine(self, obj):
3417        """Speaks the line of an AccessibleText object that contains the
3418        caret, unless the line is empty in which case it's ignored.
3419
3420        Arguments:
3421        - obj: an Accessible object that implements the AccessibleText
3422               interface
3423        """
3424
3425        [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
3426        if len(line) and line != "\n":
3427            result = self.utilities.indentationDescription(line)
3428            if result:
3429                self.speakMessage(result)
3430
3431            endOffset = startOffset + len(line)
3432            orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING)
3433
3434            voice = self.speechGenerator.voice(string=line)
3435            line = self.utilities.adjustForLinks(obj, line, startOffset)
3436            line = self.utilities.adjustForRepeats(line)
3437            if self.utilities.shouldVerbalizeAllPunctuation(obj):
3438                line = self.utilities.verbalizeAllPunctuation(line)
3439
3440            utterance = [line]
3441            utterance.extend(voice)
3442            speech.speak(utterance)
3443        else:
3444            # Speak blank line if appropriate.
3445            #
3446            self.sayCharacter(obj)
3447
3448        self.pointOfReference["lastTextUnitSpoken"] = "line"
3449
3450    def sayPhrase(self, obj, startOffset, endOffset):
3451        """Speaks the text of an Accessible object between the start and
3452        end offsets, unless the phrase is empty in which case it's ignored.
3453
3454        Arguments:
3455        - obj: an Accessible object that implements the AccessibleText
3456               interface
3457        - startOffset: the start text offset.
3458        - endOffset: the end text offset.
3459        """
3460
3461        phrase = self.utilities.expandEOCs(obj, startOffset, endOffset)
3462        if not phrase:
3463            return
3464
3465        if len(phrase) > 1 or phrase.isalnum():
3466            result = self.utilities.indentationDescription(phrase)
3467            if result:
3468                self.speakMessage(result)
3469
3470            orca.emitRegionChanged(obj, startOffset, endOffset, orca.CARET_TRACKING)
3471
3472            voice = self.speechGenerator.voice(string=phrase)
3473            phrase = self.utilities.adjustForRepeats(phrase)
3474            if self.utilities.shouldVerbalizeAllPunctuation(obj):
3475                phrase = self.utilities.verbalizeAllPunctuation(phrase)
3476
3477            utterance = [phrase]
3478            utterance.extend(voice)
3479            speech.speak(utterance)
3480        else:
3481            self.speakCharacter(phrase)
3482
3483        self.pointOfReference["lastTextUnitSpoken"] = "phrase"
3484
3485    def sayWord(self, obj):
3486        """Speaks the word at the caret, taking into account the previous caret position."""
3487
3488        try:
3489            text = obj.queryText()
3490            offset = text.caretOffset
3491        except:
3492            self.sayCharacter(obj)
3493            return
3494
3495        word, startOffset, endOffset = self.utilities.getWordAtOffsetAdjustedForNavigation(obj, offset)
3496
3497        # Announce when we cross a hard line boundary.
3498        if "\n" in word:
3499            if _settingsManager.getSetting('enableSpeechIndentation'):
3500                self.speakCharacter("\n")
3501            if word.startswith("\n"):
3502                startOffset += 1
3503            elif word.endswith("\n"):
3504                endOffset -= 1
3505            word = text.getText(startOffset, endOffset)
3506
3507        # sayPhrase is useful because it handles punctuation verbalization, but we don't want
3508        # to trigger its whitespace presentation.
3509        matches = list(re.finditer(r"\S+", word))
3510        if matches:
3511            startOffset += matches[0].start()
3512            endOffset -= len(word) - matches[-1].end()
3513            word = text.getText(startOffset, endOffset)
3514
3515        msg = "DEFAULT: Final word at offset %i is '%s' (%i-%i)" \
3516            % (offset, word.replace("\n", "\\n"), startOffset, endOffset)
3517        debug.println(debug.LEVEL_INFO, msg, True)
3518
3519        self.speakMisspelledIndicator(obj, startOffset)
3520        self.sayPhrase(obj, startOffset, endOffset)
3521        self.pointOfReference["lastTextUnitSpoken"] = "word"
3522
3523    def presentObject(self, obj, **args):
3524        interrupt = args.get("interrupt", False)
3525        self.updateBraille(obj, **args)
3526        utterances = self.speechGenerator.generateSpeech(obj, **args)
3527        speech.speak(utterances, interrupt=interrupt)
3528
3529    def stopSpeechOnActiveDescendantChanged(self, event):
3530        """Whether or not speech should be stopped prior to setting the
3531        locusOfFocus in onActiveDescendantChanged.
3532
3533        Arguments:
3534        - event: the Event
3535
3536        Returns True if speech should be stopped; False otherwise.
3537        """
3538
3539        if not event.any_data:
3540            return True
3541
3542        # In an object which manages its descendants, the
3543        # 'descendants' may really be a single object which changes
3544        # its name. If the name-change occurs followed by the active
3545        # descendant changing (to the same object) we won't present
3546        # the locusOfFocus because it hasn't changed. Thus we need to
3547        # be sure not to cut of the presentation of the name-change
3548        # event.
3549
3550        if orca_state.locusOfFocus == event.any_data:
3551            names = self.pointOfReference.get('names', {})
3552            oldName = names.get(hash(orca_state.locusOfFocus), '')
3553            if not oldName or event.any_data.name == oldName:
3554                return False
3555
3556        if event.source == orca_state.locusOfFocus == event.any_data.parent:
3557            return False
3558
3559        return True
3560
3561    def getFlatReviewContext(self):
3562        """Returns the flat review context, creating one if necessary."""
3563
3564        if not self.flatReviewContext:
3565            self.flatReviewContext = flat_review.Context(self)
3566            self.justEnteredFlatReviewMode = True
3567
3568            # Remember where the cursor currently was
3569            # when the user was in focus tracking mode.  We'll try to
3570            # keep the position the same as we move to characters above
3571            # and below us.
3572            #
3573            self.targetCursorCell = self.getBrailleCursorCell()
3574
3575        return self.flatReviewContext
3576
3577    def updateBrailleReview(self, targetCursorCell=0):
3578        """Obtains the braille regions for the current flat review line
3579        and displays them on the braille display.  If the targetCursorCell
3580        is non-0, then an attempt will be made to position the review cursor
3581        at that cell.  Otherwise, we will pan in display-sized increments
3582        to show the review cursor."""
3583
3584        if not _settingsManager.getSetting('enableBraille') \
3585           and not _settingsManager.getSetting('enableBrailleMonitor'):
3586            debug.println(debug.LEVEL_INFO, "BRAILLE: update review disabled", True)
3587            return
3588
3589        context = self.getFlatReviewContext()
3590
3591        [regions, regionWithFocus] = context.getCurrentBrailleRegions()
3592        if not regions:
3593            regions = []
3594            regionWithFocus = None
3595
3596        line = self.getNewBrailleLine()
3597        self.addBrailleRegionsToLine(regions, line)
3598        braille.setLines([line])
3599        self.setBrailleFocus(regionWithFocus, False)
3600        if regionWithFocus and not targetCursorCell:
3601            offset = regionWithFocus.brailleOffset + regionWithFocus.cursorOffset
3602            msg = "DEFAULT: Update to %i in %s" % (offset, regionWithFocus)
3603            debug.println(debug.LEVEL_INFO, msg, True)
3604            self.panBrailleToOffset(offset)
3605
3606        if self.justEnteredFlatReviewMode:
3607            self.refreshBraille(True, self.targetCursorCell)
3608            self.justEnteredFlatReviewMode = False
3609        else:
3610            self.refreshBraille(True, targetCursorCell)
3611
3612    def _setFlatReviewContextToBeginningOfBrailleDisplay(self):
3613        """Sets the character of interest to be the first character showing
3614        at the beginning of the braille display."""
3615
3616        context = self.getFlatReviewContext()
3617        [regions, regionWithFocus] = context.getCurrentBrailleRegions()
3618
3619        # The first character on the flat review line has to be in object with text.
3620        isTextOrComponent = lambda x: isinstance(x, (braille.ReviewText, braille.ReviewComponent))
3621        regions = list(filter(isTextOrComponent, regions))
3622
3623        msg = "DEFAULT: Text/Component regions on line:\n%s" % "\n".join(map(str, regions))
3624        debug.println(debug.LEVEL_INFO, msg, True)
3625
3626        # TODO - JD: The current code was stopping on the first region which met the
3627        # following condition. Is that definitely the right thing to do? Assume so for now.
3628        # Also: Should the default script be accessing things like the viewport directly??
3629        isMatch = lambda x: x.brailleOffset + len(x.string) > braille.viewport[0]
3630        regions = list(filter(isMatch, regions))
3631
3632        if not regions:
3633            msg = "DEFAULT: Could not find review region to move to start of display"
3634            debug.println(debug.LEVEL_INFO, msg, True)
3635            return
3636
3637        msg = "DEFAULT: Candidates for start of display:\n%s" % "\n".join(map(str, regions))
3638        debug.println(debug.LEVEL_INFO, msg, True)
3639
3640        # TODO - JD: Again, for now we're preserving the original behavior of choosing the first.
3641        region = regions[0]
3642        position = max(region.brailleOffset, braille.viewport[0])
3643        if region.contracted:
3644            offset = region.inPos[position - region.brailleOffset]
3645        else:
3646            offset = position - region.brailleOffset
3647        if isinstance(region.zone, flat_review.TextZone):
3648            offset += region.zone.startOffset
3649        msg = "DEFAULT: Offset for region: %i" % offset
3650        debug.println(debug.LEVEL_INFO, msg, True)
3651
3652        [word, charOffset] = region.zone.getWordAtOffset(offset)
3653        if word:
3654            msg = "DEFAULT: Setting start of display to %s, %i" % (str(word), charOffset)
3655            debug.println(debug.LEVEL_INFO, msg, True)
3656            self.flatReviewContext.setCurrent(
3657                word.zone.line.index,
3658                word.zone.index,
3659                word.index,
3660                charOffset)
3661        else:
3662            msg = "DEFAULT: Setting start of display to %s" % region.zone
3663            debug.println(debug.LEVEL_INFO, msg, True)
3664            self.flatReviewContext.setCurrent(
3665                region.zone.line.index,
3666                region.zone.index,
3667                0, # word index
3668                0) # character index
3669
3670    def find(self, query=None):
3671        """Searches for the specified query.  If no query is specified,
3672        it searches for the query specified in the Orca Find dialog.
3673
3674        Arguments:
3675        - query: The search query to find.
3676        """
3677
3678        if not query:
3679            query = find.getLastQuery()
3680        if query:
3681            context = self.getFlatReviewContext()
3682            location = query.findQuery(context, self.justEnteredFlatReviewMode)
3683            if not location:
3684                self.presentMessage(messages.STRING_NOT_FOUND)
3685            else:
3686                context.setCurrent(location.lineIndex, location.zoneIndex, \
3687                                   location.wordIndex, location.charIndex)
3688                self.reviewCurrentItem(None)
3689                self.targetCursorCell = self.getBrailleCursorCell()
3690
3691    def textLines(self, obj, offset=None):
3692        """Creates a generator that can be used to iterate over each line
3693        of a text object, starting at the caret offset.
3694
3695        Arguments:
3696        - obj: an Accessible that has a text specialization
3697
3698        Returns an iterator that produces elements of the form:
3699        [SayAllContext, acss], where SayAllContext has the text to be
3700        spoken and acss is an ACSS instance for speaking the text.
3701        """
3702
3703        self._sayAllIsInterrupted = False
3704        try:
3705            text = obj.queryText()
3706        except:
3707            self._inSayAll = False
3708            self._sayAllContexts = []
3709            return
3710
3711        self._inSayAll = True
3712        length = text.characterCount
3713        if offset is None:
3714            offset = text.caretOffset
3715
3716        # Determine the correct "say all by" mode to use.
3717        #
3718        sayAllStyle = _settingsManager.getSetting('sayAllStyle')
3719        if sayAllStyle == settings.SAYALL_STYLE_SENTENCE:
3720            mode = pyatspi.TEXT_BOUNDARY_SENTENCE_START
3721        elif sayAllStyle == settings.SAYALL_STYLE_LINE:
3722            mode = pyatspi.TEXT_BOUNDARY_LINE_START
3723        else:
3724            mode = pyatspi.TEXT_BOUNDARY_LINE_START
3725
3726        priorObj = obj
3727
3728        # Get the next line of text to read
3729        #
3730        done = False
3731        while not done:
3732            speech.speak(self.speechGenerator.generateContext(obj, priorObj=priorObj))
3733
3734            lastEndOffset = -1
3735            while offset < length:
3736                [lineString, startOffset, endOffset] = text.getTextAtOffset(
3737                    offset, mode)
3738
3739                # Some applications that don't support sentence boundaries
3740                # will provide the line boundary results instead; others
3741                # will return nothing.
3742                #
3743                if not lineString:
3744                    mode = pyatspi.TEXT_BOUNDARY_LINE_START
3745                    [lineString, startOffset, endOffset] = \
3746                        text.getTextAtOffset(offset, mode)
3747
3748                if endOffset > text.characterCount:
3749                    msg = "WARNING: endOffset: %i > characterCount: %i " \
3750                          " resulting from text.getTextAtOffset(%i, %s) for %s" \
3751                          % (endOffset, text.characterCount, offset, mode, obj)
3752                    debug.println(debug.LEVEL_INFO, msg, True)
3753                    endOffset = text.characterCount
3754
3755                # [[[WDW - HACK: this is here because getTextAtOffset
3756                # tends not to be implemented consistently across toolkits.
3757                # Sometimes it behaves properly (i.e., giving us an endOffset
3758                # that is the beginning of the next line), sometimes it
3759                # doesn't (e.g., giving us an endOffset that is the end of
3760                # the current line).  So...we hack.  The whole 'max' deal
3761                # is to account for lines that might be a brazillion lines
3762                # long.]]]
3763                #
3764                if endOffset == lastEndOffset:
3765                    offset = max(offset + 1, lastEndOffset + 1)
3766                    lastEndOffset = endOffset
3767                    continue
3768
3769                lastEndOffset = endOffset
3770                offset = endOffset
3771
3772                voice = self.speechGenerator.voice(string=lineString)
3773                if voice and isinstance(voice, list):
3774                    voice = voice[0]
3775
3776                lineString = \
3777                    self.utilities.adjustForLinks(obj, lineString, startOffset)
3778                lineString = self.utilities.adjustForRepeats(lineString)
3779
3780                context = speechserver.SayAllContext(
3781                    obj, lineString, startOffset, endOffset)
3782                msg = "DEFAULT %s" % context
3783                debug.println(debug.LEVEL_INFO, msg, True)
3784                self._sayAllContexts.append(context)
3785                eventsynthesizer.scrollIntoView(obj, startOffset, endOffset)
3786                yield [context, voice]
3787
3788            moreLines = False
3789            relations = obj.getRelationSet()
3790            for relation in relations:
3791                if relation.getRelationType() == pyatspi.RELATION_FLOWS_TO:
3792                    priorObj = obj
3793                    obj = relation.getTarget(0)
3794
3795                    try:
3796                        text = obj.queryText()
3797                    except NotImplementedError:
3798                        return
3799
3800                    length = text.characterCount
3801                    offset = 0
3802                    moreLines = True
3803                    break
3804            if not moreLines:
3805                done = True
3806
3807        self._inSayAll = False
3808        self._sayAllContexts = []
3809
3810        msg = "DEFAULT: textLines complete. Verifying SayAll status"
3811        debug.println(debug.LEVEL_INFO, msg, True)
3812        self.inSayAll()
3813
3814    def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None):
3815        """To-be-removed. Returns the string, caretOffset, startOffset."""
3816
3817        try:
3818            text = obj.queryText()
3819            offset = text.caretOffset
3820            characterCount = text.characterCount
3821        except NotImplementedError:
3822            return ["", 0, 0]
3823        except:
3824            msg = "DEFAULT: Exception getting offset and length for %s" % obj
3825            debug.println(debug.LEVEL_INFO, msg, True)
3826            return ["", 0, 0]
3827
3828        targetOffset = startOffset
3829        if targetOffset is None:
3830            targetOffset = max(0, offset)
3831
3832        # The offset might be positioned at the very end of the text area.
3833        # In these cases, calling text.getTextAtOffset on an offset that's
3834        # not positioned to a character can yield unexpected results.  In
3835        # particular, we'll see the Gecko toolkit return a start and end
3836        # offset of (0, 0), and we'll see other implementations, such as
3837        # gedit, return reasonable results (i.e., gedit will give us the
3838        # last line).
3839        #
3840        # In order to accommodate the differing behavior of different
3841        # AT-SPI implementations, we'll make sure we give getTextAtOffset
3842        # the offset of an actual character.  Then, we'll do a little check
3843        # to see if that character is a newline - if it is, we'll treat it
3844        # as the line.
3845        #
3846        if targetOffset == characterCount:
3847            fixedTargetOffset = max(0, targetOffset - 1)
3848            character = text.getText(fixedTargetOffset, fixedTargetOffset + 1)
3849        else:
3850            fixedTargetOffset = targetOffset
3851            character = None
3852
3853        if (targetOffset == characterCount) \
3854            and (character == "\n"):
3855            lineString = ""
3856            startOffset = fixedTargetOffset
3857        else:
3858            # Get the line containing the caret.  [[[TODO: HACK WDW - If
3859            # there's only 1 character in the string, well, we get it.  We
3860            # do this because Gecko's implementation of getTextAtOffset
3861            # is broken if there is just one character in the string.]]]
3862            #
3863            if (characterCount == 1):
3864                lineString = text.getText(fixedTargetOffset, fixedTargetOffset + 1)
3865                startOffset = fixedTargetOffset
3866            else:
3867                if fixedTargetOffset == -1:
3868                    fixedTargetOffset = characterCount
3869                try:
3870                    [lineString, startOffset, endOffset] = text.getTextAtOffset(
3871                        fixedTargetOffset, pyatspi.TEXT_BOUNDARY_LINE_START)
3872                except:
3873                    return ["", 0, 0]
3874
3875            # Sometimes we get the trailing line-feed-- remove it
3876            # It is important that these are in order.
3877            # In some circumstances we might get:
3878            # word word\r\n
3879            # so remove \n, and then remove \r.
3880            # See bgo#619332.
3881            #
3882            lineString = lineString.rstrip('\n')
3883            lineString = lineString.rstrip('\r')
3884
3885        return [lineString, text.caretOffset, startOffset]
3886
3887    def phoneticSpellCurrentItem(self, itemString):
3888        """Phonetically spell the current flat review word or line.
3889
3890        Arguments:
3891        - itemString: the string to phonetically spell.
3892        """
3893
3894        for (charIndex, character) in enumerate(itemString):
3895            voice = self.speechGenerator.voice(string=character)
3896            phoneticString = phonnames.getPhoneticName(character.lower())
3897            speech.speak(phoneticString, voice)
3898
3899    def _saveLastCursorPosition(self, obj, caretOffset):
3900        """Save away the current text cursor position for next time.
3901
3902        Arguments:
3903        - obj: the current accessible
3904        - caretOffset: the cursor position within this object
3905        """
3906
3907        prevObj, prevOffset = self.pointOfReference.get("lastCursorPosition", (None, -1))
3908        self.pointOfReference["penultimateCursorPosition"] = prevObj, prevOffset
3909        self.pointOfReference["lastCursorPosition"] = obj, caretOffset
3910
3911    def systemBeep(self):
3912        """Rings the system bell. This is really a hack. Ideally, we want
3913        a method that will present an earcon (any sound designated for the
3914        purpose of representing an error, event etc)
3915        """
3916
3917        print("\a")
3918
3919    def speakMisspelledIndicator(self, obj, offset):
3920        """Speaks an announcement indicating that a given word is misspelled.
3921
3922        Arguments:
3923        - obj: An accessible which implements the accessible text interface.
3924        - offset: Offset in the accessible's text for which to retrieve the
3925          attributes.
3926        """
3927
3928        if _settingsManager.getSetting('speakMisspelledIndicator'):
3929            try:
3930                text = obj.queryText()
3931            except:
3932                return
3933            # If we're on whitespace, we cannot be on a misspelled word.
3934            #
3935            charAndOffsets = \
3936                text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
3937            if not charAndOffsets[0].strip() \
3938               or self.utilities.isWordDelimiter(charAndOffsets[0]):
3939                self._lastWordCheckedForSpelling = charAndOffsets[0]
3940                return
3941
3942            wordAndOffsets = \
3943                text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_WORD_START)
3944            if self.utilities.isWordMisspelled(obj, offset) \
3945               and wordAndOffsets[0] != self._lastWordCheckedForSpelling:
3946                self.speakMessage(messages.MISSPELLED)
3947            # Store this word so that we do not continue to present the
3948            # presence of the red squiggly as the user arrows amongst
3949            # the characters.
3950            #
3951            self._lastWordCheckedForSpelling = wordAndOffsets[0]
3952
3953    ############################################################################
3954    #                                                                          #
3955    # Presentation methods                                                     #
3956    # (scripts should not call methods in braille.py or speech.py directly)    #
3957    #                                                                          #
3958    ############################################################################
3959
3960    def presentationInterrupt(self):
3961        """Convenience method to interrupt presentation of whatever is being
3962        presented at the moment."""
3963
3964        msg = "DEFAULT: Interrupting presentation"
3965        debug.println(debug.LEVEL_INFO, msg, True)
3966        speech.stop()
3967        braille.killFlash()
3968
3969    def presentKeyboardEvent(self, event):
3970        """Convenience method to present the KeyboardEvent event. Returns True
3971        if we fully present the event; False otherwise."""
3972
3973        if not event.isPressedKey():
3974            self._sayAllIsInterrupted = False
3975            self.utilities.clearCachedCommandState()
3976
3977        if not orca_state.learnModeEnabled:
3978            if event.shouldEcho == False or event.isOrcaModified():
3979                return False
3980
3981        try:
3982            role = orca_state.locusOfFocus.getRole()
3983        except:
3984            return False
3985
3986        if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_FRAME, pyatspi.ROLE_WINDOW]:
3987            focusedObject = self.utilities.focusedObject(orca_state.activeWindow)
3988            if focusedObject:
3989                orca.setLocusOfFocus(None, focusedObject, False)
3990                role = focusedObject.getRole()
3991
3992        if role == pyatspi.ROLE_PASSWORD_TEXT and not event.isLockingKey():
3993            return False
3994
3995        if not event.isPressedKey():
3996            return False
3997
3998        braille.displayKeyEvent(event)
3999        orcaModifierPressed = event.isOrcaModifier() and event.isPressedKey()
4000        if event.isCharacterEchoable() and not orcaModifierPressed:
4001            return False
4002        if orca_state.learnModeEnabled:
4003            if event.isPrintableKey() and event.getClickCount() == 2:
4004                self.phoneticSpellCurrentItem(event.event_string)
4005                return True
4006
4007        string = None
4008        if event.isPrintableKey():
4009            string = event.event_string
4010
4011        msg = "DEFAULT: Presenting keyboard event"
4012        debug.println(debug.LEVEL_INFO, msg, True)
4013
4014        voice = self.speechGenerator.voice(string=string)
4015        speech.speakKeyEvent(event, voice)
4016        return True
4017
4018    def presentMessage(self, fullMessage, briefMessage=None, voice=None, resetStyles=True, force=False):
4019        """Convenience method to speak a message and 'flash' it in braille.
4020
4021        Arguments:
4022        - fullMessage: This can be a string or a list. This will be presented
4023          as the message for users whose flash or message verbosity level is
4024          verbose.
4025        - briefMessage: This can be a string or a list. This will be presented
4026          as the message for users whose flash or message verbosity level is
4027          brief. Note that providing no briefMessage will result in the full
4028          message being used for either. Callers wishing to present nothing as
4029          the briefMessage should set briefMessage to an empty string.
4030        - voice: The voice to use when speaking this message. By default, the
4031          "system" voice will be used.
4032        """
4033
4034        if not fullMessage:
4035            return
4036
4037        if briefMessage is None:
4038            briefMessage = fullMessage
4039
4040        if _settingsManager.getSetting('enableSpeech'):
4041            if not _settingsManager.getSetting('messagesAreDetailed'):
4042                message = briefMessage
4043            else:
4044                message = fullMessage
4045            if message:
4046                self.speakMessage(message, voice=voice, resetStyles=resetStyles, force=force)
4047
4048        if (_settingsManager.getSetting('enableBraille') \
4049             or _settingsManager.getSetting('enableBrailleMonitor')) \
4050           and _settingsManager.getSetting('enableFlashMessages'):
4051            if not _settingsManager.getSetting('flashIsDetailed'):
4052                message = briefMessage
4053            else:
4054                message = fullMessage
4055            if not message:
4056                return
4057
4058            if isinstance(message[0], list):
4059                message = message[0]
4060            if isinstance(message, list):
4061                message = [i for i in message if isinstance(i, str)]
4062                message = " ".join(message)
4063
4064            if _settingsManager.getSetting('flashIsPersistent'):
4065                duration = -1
4066            else:
4067                duration = _settingsManager.getSetting('brailleFlashTime')
4068
4069            braille.displayMessage(message, flashTime=duration)
4070
4071    def idleMessage(self):
4072        """Convenience method to tell speech and braille engines to hand off
4073        control to other screen readers."""
4074
4075        braille.disableBraille()
4076
4077    @staticmethod
4078    def __play(sounds, interrupt=True):
4079        if not sounds:
4080            return
4081
4082        if not isinstance(sounds, list):
4083            icon = [sounds]
4084
4085        _player = sound.getPlayer()
4086        _player.play(sounds[0], interrupt)
4087        for i in range(1, len(sounds)):
4088            sound.play(sounds[i], interrupt=False)
4089
4090    @staticmethod
4091    def addBrailleRegionToLine(region, line):
4092        """Adds the braille region to the line.
4093
4094        Arguments:
4095        - region: a braille.Region (e.g. what is returned by the braille
4096          generator's generateBraille() method.
4097        - line: a braille.Line
4098        """
4099
4100        line.addRegion(region)
4101
4102    @staticmethod
4103    def addBrailleRegionsToLine(regions, line):
4104        """Adds the braille region to the line.
4105
4106        Arguments:
4107        - regions: a series of braille.Region instances (a single instance
4108          being what is returned by the braille generator's generateBraille()
4109          method.
4110        - line: a braille.Line
4111        """
4112
4113        line.addRegions(regions)
4114
4115    @staticmethod
4116    def addToLineAsBrailleRegion(string, line):
4117        """Creates a Braille Region out of string and adds it to the line.
4118
4119        Arguments:
4120        - string: the string to be displayed
4121        - line: a braille.Line
4122        """
4123
4124        line.addRegion(braille.Region(string))
4125
4126    @staticmethod
4127    def brailleRegionsFromStrings(strings):
4128        """Creates a list of braille regions from the list of strings.
4129
4130        Arguments:
4131        - strings: a list of strings from which to create the list of
4132          braille Region instances
4133
4134        Returns the list of braille Region instances
4135        """
4136
4137        brailleRegions = []
4138        for string in strings:
4139            brailleRegions.append(braille.Region(string))
4140
4141        return brailleRegions
4142
4143    @staticmethod
4144    def clearBraille():
4145        """Clears the logical structure, but keeps the Braille display as is
4146        (until a refresh operation)."""
4147
4148        braille.clear()
4149
4150    @staticmethod
4151    def displayBrailleMessage(message, cursor=-1, flashTime=0):
4152        """Displays a single line, setting the cursor to the given position,
4153        ensuring that the cursor is in view.
4154
4155        Arguments:
4156        - message: the string to display
4157        - cursor: the 0-based cursor position, where -1 (default) means no
4158          cursor
4159        - flashTime:  if non-0, the number of milliseconds to display the
4160          regions before reverting back to what was there before. A 0 means
4161          to not do any flashing.  A negative number means to display the
4162          message until some other message comes along or the user presses
4163          a cursor routing key.
4164        """
4165
4166        if not _settingsManager.getSetting('enableBraille') \
4167           and not _settingsManager.getSetting('enableBrailleMonitor'):
4168            debug.println(debug.LEVEL_INFO, "BRAILLE: display message disabled", True)
4169            return
4170
4171        braille.displayMessage(message, cursor, flashTime)
4172
4173    @staticmethod
4174    def displayBrailleRegions(regionInfo, flashTime=0):
4175        """Displays a list of regions on a single line, setting focus to the
4176        specified region.  The regionInfo parameter is something that is
4177        typically returned by a call to braille_generator.generateBraille.
4178
4179        Arguments:
4180        - regionInfo: a list where the first element is a list of regions
4181          to display and the second element is the region with focus (must
4182          be in the list from element 0)
4183        - flashTime:  if non-0, the number of milliseconds to display the
4184          regions before reverting back to what was there before. A 0 means
4185          to not do any flashing. A negative number means to display the
4186          message until some other message comes along or the user presses
4187          a cursor routing key.
4188        """
4189
4190        if not _settingsManager.getSetting('enableBraille') \
4191           and not _settingsManager.getSetting('enableBrailleMonitor'):
4192            debug.println(debug.LEVEL_INFO, "BRAILLE: display regions disabled", True)
4193            return
4194
4195        braille.displayRegions(regionInfo, flashTime)
4196
4197    def displayBrailleForObject(self, obj):
4198        """Convenience method for scripts combining the call to the braille
4199        generator for the script with the call to displayBrailleRegions.
4200
4201        Arguments:
4202        - obj: the accessible object to display in braille
4203        """
4204
4205        regions = self.brailleGenerator.generateBraille(obj)
4206        self.displayBrailleRegions(regions)
4207
4208    @staticmethod
4209    def getBrailleCaretContext(event):
4210        """Gets the accesible and caret offset associated with the given
4211        event.  The event should have a BrlAPI event that contains an
4212        argument value that corresponds to a cell on the display.
4213
4214        Arguments:
4215        - event: an instance of input_event.BrailleEvent.  event.event is
4216          the dictionary form of the expanded BrlAPI event.
4217        """
4218
4219        return braille.getCaretContext(event)
4220
4221    @staticmethod
4222    def getBrailleCursorCell():
4223        """Returns the value of position of the braille cell which has the
4224        cursor. A value of 0 means no cell has the cursor."""
4225
4226        return braille.cursorCell
4227
4228    @staticmethod
4229    def getNewBrailleLine(clearBraille=False, addLine=False):
4230        """Creates a new braille Line.
4231
4232        Arguments:
4233        - clearBraille: Whether the display should be cleared.
4234        - addLine: Whether the line should be added to the logical display
4235          for painting.
4236
4237        Returns the new Line.
4238        """
4239
4240        if clearBraille:
4241            braille.clear()
4242        line = braille.Line()
4243        if addLine:
4244            braille.addLine(line)
4245
4246        return line
4247
4248    @staticmethod
4249    def getNewBrailleComponent(accessible, string, cursorOffset=0,
4250                               indicator='', expandOnCursor=False):
4251        """Creates a new braille Component.
4252
4253        Arguments:
4254        - accessible: the accessible associated with this region
4255        - string: the string to be displayed
4256        - cursorOffset: a 0-based index saying where to draw the cursor
4257          for this Region if it gets focus
4258
4259        Returns the new Component.
4260        """
4261
4262        return braille.Component(accessible, string, cursorOffset,
4263                                 indicator, expandOnCursor)
4264
4265    @staticmethod
4266    def getNewBrailleRegion(string, cursorOffset=0, expandOnCursor=False):
4267        """Creates a new braille Region.
4268
4269        Arguments:
4270        - string: the string to be displayed
4271        - cursorOffset: a 0-based index saying where to draw the cursor
4272          for this Region if it gets focus
4273
4274        Returns the new Region.
4275        """
4276
4277        return braille.Region(string, cursorOffset, expandOnCursor)
4278
4279    @staticmethod
4280    def getNewBrailleText(accessible, label="", eol="", startOffset=None,
4281                          endOffset=None):
4282
4283        """Creates a new braille Text region.
4284
4285        Arguments:
4286        - accessible: the accessible associated with this region and which
4287          implements AtkText
4288        - label: an optional label to display
4289        - eol: the endOfLine indicator
4290
4291        Returns the new Text region.
4292        """
4293
4294        return braille.Text(accessible, label, eol, startOffset, endOffset)
4295
4296    @staticmethod
4297    def isBrailleBeginningShowing():
4298        """If True, the beginning of the line is showing on the braille
4299        display."""
4300
4301        return braille.beginningIsShowing
4302
4303    @staticmethod
4304    def isBrailleEndShowing():
4305        """If True, the end of the line is showing on the braille display."""
4306
4307        return braille.endIsShowing
4308
4309    @staticmethod
4310    def panBrailleInDirection(panAmount=0, panToLeft=True):
4311        """Pans the display to the left, limiting the pan to the beginning
4312        of the line being displayed.
4313
4314        Arguments:
4315        - panAmount: the amount to pan.  A value of 0 means the entire
4316          width of the physical display.
4317        - panToLeft: if True, pan to the left; otherwise to the right
4318
4319        Returns True if a pan actually happened.
4320        """
4321
4322        if panToLeft:
4323            return braille.panLeft(panAmount)
4324        else:
4325            return braille.panRight(panAmount)
4326
4327    @staticmethod
4328    def panBrailleToOffset(offset):
4329        """Automatically pan left or right to make sure the current offset
4330        is showing."""
4331
4332        braille.panToOffset(offset)
4333
4334    @staticmethod
4335    def presentItemsInBraille(items):
4336        """Method to braille a list of items. Scripts should call this
4337        method rather than handling the creation and displaying of a
4338        braille line directly.
4339
4340        Arguments:
4341        - items: a list of strings to be presented
4342        """
4343
4344        line = braille.getShowingLine()
4345        for item in items:
4346            line.addRegion(braille.Region(" " + item))
4347
4348        braille.refresh()
4349
4350    def updateBrailleForNewCaretPosition(self, obj):
4351        """Try to reposition the cursor without having to do a full update."""
4352
4353        if not _settingsManager.getSetting('enableBraille') \
4354           and not _settingsManager.getSetting('enableBrailleMonitor'):
4355            debug.println(debug.LEVEL_INFO, "BRAILLE: update caret disabled", True)
4356            return
4357
4358        brailleNeedsRepainting = True
4359        line = braille.getShowingLine()
4360        for region in line.regions:
4361            if isinstance(region, braille.Text) and region.accessible == obj:
4362                if region.repositionCursor():
4363                    self.refreshBraille(True)
4364                    brailleNeedsRepainting = False
4365                break
4366
4367        if brailleNeedsRepainting:
4368            self.updateBraille(obj)
4369
4370    @staticmethod
4371    def refreshBraille(panToCursor=True, targetCursorCell=0, getLinkMask=True,
4372                       stopFlash=True):
4373        """This is the method scripts should use to refresh braille rather
4374        than calling self.refreshBraille() directly. The intent is to centralize
4375        such calls into as few places as possible so that we can easily and
4376        safely not perform braille-related functions for users who do not
4377        have braille and/or the braille monitor enabled.
4378
4379        Arguments:
4380
4381        - panToCursor: if True, will adjust the viewport so the cursor is
4382          showing.
4383        - targetCursorCell: Only effective if panToCursor is True.
4384          0 means automatically place the cursor somewhere on the display so
4385          as to minimize movement but show as much of the line as possible.
4386          A positive value is a 1-based target cell from the left side of
4387          the display and a negative value is a 1-based target cell from the
4388          right side of the display.
4389        - getLinkMask: Whether or not we should take the time to get the
4390          attributeMask for links. Reasons we might not want to include
4391          knowing that we will fail and/or it taking an unreasonable
4392          amount of time (AKA Gecko).
4393        - stopFlash: if True, kill any flashed message that may be showing.
4394        """
4395
4396        braille.refresh(panToCursor, targetCursorCell, getLinkMask, stopFlash)
4397
4398    @staticmethod
4399    def setBrailleFocus(region, panToFocus=True, getLinkMask=True):
4400        """Specififes the region with focus.  This region will be positioned
4401        at the home position if panToFocus is True.
4402
4403        Arguments:
4404        - region: the given region, which much be in a line that has been
4405          added to the logical display
4406        - panToFocus: whether or not to position the region at the home
4407          position
4408        - getLinkMask: Whether or not we should take the time to get the
4409          attributeMask for links. Reasons we might not want to include
4410          knowing that we will fail and/or it taking an unreasonable
4411          amount of time (AKA Gecko).
4412        """
4413
4414        braille.setFocus(region, panToFocus, getLinkMask)
4415
4416    @staticmethod
4417    def _setContractedBraille(event):
4418        """Turns contracted braille on or off based upon the event.
4419
4420        Arguments:
4421        - event: an instance of input_event.BrailleEvent.  event.event is
4422          the dictionary form of the expanded BrlAPI event.
4423        """
4424
4425        braille.setContractedBraille(event)
4426
4427    ########################################################################
4428    #                                                                      #
4429    # Speech methods                                                       #
4430    # (scripts should not call methods in speech.py directly)              #
4431    #                                                                      #
4432    ########################################################################
4433
4434    def speakCharacter(self, character):
4435        """Method to speak a single character. Scripts should use this
4436        method rather than calling speech.speakCharacter directly."""
4437
4438        voice = self.speechGenerator.voice(string=character)
4439        speech.speakCharacter(character, voice)
4440
4441    def speakMessage(self, string, voice=None, interrupt=True, resetStyles=True, force=False):
4442        """Method to speak a single string. Scripts should use this
4443        method rather than calling speech.speak directly.
4444
4445        - string: The string to be spoken.
4446        - voice: The voice to use. By default, the "system" voice will
4447          be used.
4448        - interrupt: If True, any current speech should be interrupted
4449          prior to speaking the new text.
4450        """
4451
4452        if not _settingsManager.getSetting('enableSpeech') \
4453           or (_settingsManager.getSetting('onlySpeakDisplayedText') and not force):
4454            return
4455
4456        voices = _settingsManager.getSetting('voices')
4457        systemVoice = voices.get(settings.SYSTEM_VOICE)
4458
4459        voice = voice or systemVoice
4460        if voice == systemVoice and resetStyles:
4461            capStyle = _settingsManager.getSetting('capitalizationStyle')
4462            _settingsManager.setSetting('capitalizationStyle', settings.CAPITALIZATION_STYLE_NONE)
4463            speech.updateCapitalizationStyle()
4464
4465            punctStyle = _settingsManager.getSetting('verbalizePunctuationStyle')
4466            _settingsManager.setSetting('verbalizePunctuationStyle', settings.PUNCTUATION_STYLE_NONE)
4467            speech.updatePunctuationLevel()
4468
4469        speech.speak(string, voice, interrupt)
4470
4471        if voice == systemVoice and resetStyles:
4472            _settingsManager.setSetting('capitalizationStyle', capStyle)
4473            speech.updateCapitalizationStyle()
4474
4475            _settingsManager.setSetting('verbalizePunctuationStyle', punctStyle)
4476            speech.updatePunctuationLevel()
4477
4478    @staticmethod
4479    def presentItemsInSpeech(items):
4480        """Method to speak a list of items. Scripts should call this
4481        method rather than handling the creation and speaking of
4482        utterances directly.
4483
4484        Arguments:
4485        - items: a list of strings to be presented
4486        """
4487
4488        utterances = []
4489        for item in items:
4490            utterances.append(item)
4491
4492        speech.speak(utterances)
4493
4494    def speakUnicodeCharacter(self, character):
4495        """ Speaks some information about an unicode character.
4496        At the moment it just announces the character unicode number but
4497        this information may be changed in the future
4498
4499        Arguments:
4500        - character: the character to speak information of
4501        """
4502        speech.speak(messages.UNICODE % \
4503                         self.utilities.unicodeValueString(character))
4504
4505    def presentTime(self, inputEvent):
4506        """ Presents the current time. """
4507        timeFormat = _settingsManager.getSetting('presentTimeFormat')
4508        message = time.strftime(timeFormat, time.localtime())
4509        self.presentMessage(message)
4510        return True
4511
4512    def presentDate(self, inputEvent):
4513        """ Presents the current date. """
4514        dateFormat = _settingsManager.getSetting('presentDateFormat')
4515        message = time.strftime(dateFormat, time.localtime())
4516        self.presentMessage(message)
4517        return True
4518
4519    def presentSizeAndPosition(self, inputEvent):
4520        """ Presents the size and position of the locusOfFocus. """
4521
4522        if self.flatReviewContext:
4523            obj = self.flatReviewContext.getCurrentAccessible()
4524        else:
4525            obj = orca_state.locusOfFocus
4526
4527        x, y, width, height = self.utilities.getBoundingBox(obj)
4528        if (x, y, width, height) == (-1, -1, 0, 0):
4529            full = messages.LOCATION_NOT_FOUND_FULL
4530            brief = messages.LOCATION_NOT_FOUND_BRIEF
4531            self.presentMessage(full, brief)
4532            return True
4533
4534        full = messages.SIZE_AND_POSITION_FULL % (width, height, x, y)
4535        brief = messages.SIZE_AND_POSITION_BRIEF % (width, height, x, y)
4536        self.presentMessage(full, brief)
4537        return True
4538