Coverage Report - orca.script

ModuleCoverage %
orca.script
67%
1
# Orca
2
#
3
# Copyright 2004-2007 Sun Microsystems Inc.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 of the License, or (at your option) any later version.
9
#
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# Library General Public License for more details.
14
#
15
# You should have received a copy of the GNU Library General Public
16
# License along with this library; if not, write to the
17
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
# Boston, MA 02111-1307, USA.
19
20
"""Each script maintains a set of key bindings, braille bindings, and
21
AT-SPI event listeners.  The key bindings are an instance of
22
KeyBindings.  The braille bindings are also a dictionary where the
23
keys are BrlTTY command integers and the values are instances of
24
InputEventHandler.  The listeners field is a dictionary where the keys
25
are AT-SPI event names and the values are function pointers.
26
27
Instances of scripts are intended to be created solely by the
28
focus_tracking_presenter.
29
30
This Script class is not intended to be instantiated directly.
31
Instead, it is expected that subclasses of the Script class will be
32
created in their own module.  The module defining the Script subclass
33
is also required to have a 'getScript(app)' method that returns an
34 1
instance of the Script subclass.  See default.py for an example."""
35
36 1
__id__        = "$Id: script.py 2572 2007-07-31 16:05:30Z richb $"
37 1
__version__   = "$Revision: 2572 $"
38 1
__date__      = "$Date: 2007-07-31 12:05:30 -0400 (Tue, 31 Jul 2007) $"
39 1
__copyright__ = "Copyright (c) 2005-2007 Sun Microsystems Inc."
40 1
__license__   = "LGPL"
41
42 1
import braillegenerator
43 1
import debug
44 1
import flat_review
45 1
import keybindings
46 1
import orca_state
47 1
import settings
48 1
import speechgenerator
49 1
import where_am_I
50 1
import bookmarks
51
52 2
class Script:
53
    """The specific focus tracking scripts for applications.
54 1
    """
55
56 1
    def __init__(self, app):
57
        """Creates a script for the given application, if necessary.
58
        This method should not be called by anyone except the
59
        focus_tracking_presenter.
60
61
        Arguments:
62
        - app: the Python Accessible application to create a script for
63
        """
64 79
        self.app = app
65
66 79
        if app:
67 78
            self.name = self.app.name
68
        else:
69 1
            self.name = "default"
70
71 79
        self.name += " (module=" + self.__module__ + ")"
72
73 79
        self.listeners = self.getListeners()
74
75
        # By default, handle events for non-active applications.
76
        #
77 79
        self.presentIfInactive = True
78
79 79
        self.inputEventHandlers = {}
80 79
        self.setupInputEventHandlers()
81 79
        self.keyBindings = self.getKeyBindings()
82 79
        self.brailleBindings = self.getBrailleBindings()
83 79
        self.app_pronunciation_dict = self.getPronunciations()
84
85 79
        self.brailleGenerator = self.getBrailleGenerator()
86 79
        self.speechGenerator = self.getSpeechGenerator()
87 79
        self.whereAmI = self.getWhereAmI()
88 79
        self.bookmarks = self.getBookmarks()
89 79
        self.voices = settings.voices
90
91 79
        self.flatReviewContextClass = flat_review.Context
92
93 79
        self.findCommandRun = False
94
95
        # Assists with dealing with CORBA COMM_FAILURES.  A failure doesn't
96
        # always mean an object disappeared - there just might be a network
97
        # glitch.  So, on COMM_FAILURES, we might retry a few times before
98
        # giving up on an object.  This might need to be overridden by the
99
        # script.  See bug #397787.
100
        #
101 79
        self.commFailureWaitTime = 0.1
102 79
        self.commFailureAttemptLimit = 5
103
104 79
        debug.println(debug.LEVEL_FINE, "NEW SCRIPT: %s" % self.name)
105
106 1
    def getListeners(self):
107
        """Sets up the AT-SPI event listeners for this script.
108
109
        Returns a dictionary where the keys are AT-SPI event names
110
        and the values are script methods.
111
        """
112 79
        return {}
113
114 1
    def setupInputEventHandlers(self):
115
        """Defines InputEventHandler fields for this script that can be
116
        called by the key and braille bindings."""
117 0
        pass
118
119 1
    def getKeyBindings(self):
120
        """Defines the key bindings for this script.
121
122
        Returns an instance of keybindings.KeyBindings.
123
        """
124 79
        return keybindings.KeyBindings()
125
126 1
    def getKeyBindingsForInputHandler(self, inputEventHandler):
127
        """ Returns a KeyBindings object with the list of KeyBindings that
128
        matche the passed inputEventHandler as argument (at least the
129
        inputEventHandler that has the same handler function)
130
131
        Arguments:
132
        - inputEventHandler: an instance of input_event.InputEventHandler
133
134
        Returns an instance of keybindings.KeyBindings populated with
135
        keybindings.KeyBinding instances that match the inputEventHandler.
136
        """
137 0
        matches = keybindings.KeyBindings()
138
139 0
        for binding in self.keyBindings.keyBindings:
140 0
            if inputEventHandler == binding.handler:
141 0
                matches.add(binding)
142
143 0
        return matches
144
145 1
    def getBrailleBindings(self):
146
        """Defines the braille bindings for this script.
147
148
        Returns a dictionary where the keys are BrlTTY commands and the
149
        values are InputEventHandler instances.
150
        """
151 79
        return {}
152
153 1
    def getPronunciations(self):
154
        """Defines the application specific pronunciations for this script.
155
156
        Returns a dictionary where the keys are the actual text strings and
157
        the values are the replacement strings that are spoken instead.
158
        """
159
160 79
        return {}
161
162 1
    def getBrailleCommandsForInputHandler(self, inputEventHandler):
163
        """Returns a list of BrlTTY commands (they're in braille.py) that
164
        match the given inputEventHandler passed as argument.
165
166
        Arguments:
167
        - inputEventHandler: an instance of input_event.InputEventHandler
168
169
        Returns a list (possibly empty) of BrlTTY commands (they're in
170
        braille.py) that match the given inputEventHandler passed.
171
        """
172 0
        return [command
173 0
                for command, handler in self.brailleBindings.iteritems()
174 0
                if inputEventHandler == handler]
175
176 1
    def getBrailleGenerator(self):
177
        """Returns the braille generator for this script.
178
        """
179 79
        return braillegenerator.BrailleGenerator(self)
180
181 1
    def getSpeechGenerator(self):
182
        """Returns the speech generator for this script.
183
        """
184 79
        return speechgenerator.SpeechGenerator(self)
185
186 1
    def getWhereAmI(self):
187
        """Returns the "where am I" class for this script.
188
        """
189 79
        return where_am_I.WhereAmI(self)
190
191 1
    def getBookmarks(self):
192
        """Returns the "bookmarks" class for this script.
193
        """
194 79
        try:
195 79
            return self.bookmarks 
196 79
        except AttributeError:
197 79
            self.bookmarks = bookmarks.Bookmarks(self)
198 79
            return self.bookmarks
199
200 1
    def getAppPreferencesGUI(self):
201
        """Return a GtkVBox contain the application unique configuration
202
        GUI items for the current application.
203
        """
204 0
        return None
205
206 1
    def setAppPreferences(self, prefs):
207
        """Write out the application specific preferences lines and set the
208
        new values.
209
210
        Arguments:
211
        - prefs: file handle for application preferences.
212
        """
213 0
        pass
214
215 1
    def overrideAppKeyBindings(self, script, keyBindings):
216
        """Allow for the customization of application specific key bindings.
217
218
        Arguments:
219
        - script: the application script.
220
        - keyBindings: the set of key bindings for this script.
221
        """
222
223 0
        return keyBindings
224
225 1
    def overridePronunciations(self, script, pronunciations):
226
        """Allow for the customization of application specific pronunciations.
227
228
        Arguments:
229
        - script: the application script.
230
        - pronunciations: the dictionary of pronunciations for this script.
231
        """
232
233 0
        return pronunciationDict
234
235 1
    def getAppState(self):
236
        """Returns an object that can be passed to setAppState.  This
237
        object will be used by setAppState to restore any state
238
        information that was being maintained by the script."""
239 4
        return None
240
241 1
    def setAppState(self, appState):
242
        """Sets the application state using the given appState object.
243
244
        Arguments:
245
        - appState: an object obtained from getAppState
246
        """
247 0
        return
248
249
    # [[[WDW - There is a circular reference going on somewhere (see
250
    # bug 333168).  In the presence of this reference, the existence
251
    # of a __del__ method prevents the garbage collector from
252
    # collecting this object. So, we will not define a __del__ method
253
    # until we understand where the circular reference is coming from.
254
    #
255
    #def __del__(self):
256
    #    debug.println(debug.LEVEL_FINE, "DELETE SCRIPT: %s" % self.name)
257
258 1
    def processObjectEvent(self, event):
259
        """Processes all AT-SPI object events of interest to this
260
        script.  The interest in events is specified via the
261
        'listeners' field that was defined during the construction of
262
        this script.
263
264
        In general, the primary purpose of handling object events is to
265
        keep track of changes to the locus of focus and notify the
266
        orca module of these changes via orca.setLocusOfFocus and
267
        orca.visualAppearanceChanged.
268
269
        Note that this script may be passed events it doesn't care
270
        about, so it needs to react accordingly.
271
272
        Arguments:
273
        - event: the Event
274
        """
275
276
        # Check to see if we really want to process this event.
277
        #
278 13070
        processEvent = (orca_state.activeScript == self \
279 698
                        or self.presentIfInactive)
280
281 13070
        if not processEvent:
282 426
            return
283
284
        # This calls the first listener it finds whose key *begins with* or is
285
        # the same as the event.type.  The reason we do this is that the event
286
        # type in the listeners dictionary may not be as specific as the event
287
        # type we received (e.g., the listeners dictionary might contain the
288
        # key "object:state-changed:" and the event.type might be
289
        # "object:state-changed:focused".  [[[TODO: WDW - the order of the
290
        # keys is *not* deterministic, and is not guaranteed to be related
291
        # to the order in which they were added.  So...we need to do something
292
        # different here.  Logged as bugzilla bug 319781.]]]
293
        #
294 337392
        for key in self.listeners.keys():
295 324970
            if event.type.startswith(key):
296 16582
                self.listeners[key](event)
297
298 1
    def consumesKeyboardEvent(self, keyboardEvent):
299
        """Called when a key is pressed on the keyboard.
300
301
        Arguments:
302
        - keyboardEvent: an instance of input_event.KeyboardEvent
303
304
        Returns True if the event is of interest.
305
        """
306 3579
        user_bindings = None
307 3579
        user_bindings_map = settings.keyBindingsMap
308 3579
        if user_bindings_map.has_key(self.__module__):
309 0
            user_bindings = user_bindings_map[self.__module__]
310 3579
        elif user_bindings_map.has_key("default"):
311 0
            user_bindings = user_bindings_map["default"]
312
313 3579
        consumes = False
314 3579
        if user_bindings:
315 0
            consumes = user_bindings.getInputHandler(keyboardEvent) != None
316 3579
        if not consumes:
317 3579
            consumes = self.keyBindings.getInputHandler(keyboardEvent) != None
318 3579
        return consumes
319
320 1
    def processKeyboardEvent(self, keyboardEvent):
321
        """Processes the given keyboard event.
322
323
        This method will primarily use the keybindings field of this
324
        script instance see if this script has an interest in the
325
        event.
326
327
        NOTE: there is latent, but unsupported, logic for allowing
328
        the user's user-settings.py file to extend and/or override
329
        the keybindings for a script.
330
331
        Arguments:
332
        - keyboardEvent: an instance of input_event.KeyboardEvent
333
        """
334
335
        # We'll annotate the event with a reference to this script.
336
        # This will allow external scripts to muck with the script
337
        # instance if they wish.
338
        #
339 193
        keyboardEvent.script = self
340
341
        # We'll let the user keybindings take precedence.  First, we'll
342
        # check to see if they have keybindings specific for the particular
343
        # application, then we'll check to see if they have any default
344
        # bindings to use.
345
        #
346
        # [[[TODO: WDW - for performance, these bindings should probably
347
        # be conflated at initialization time.]]]
348
        #
349 193
        user_bindings = None
350
351 193
        user_bindings_map = settings.keyBindingsMap
352 193
        if user_bindings_map.has_key(self.__module__):
353 0
            user_bindings = user_bindings_map[self.__module__]
354 193
        elif user_bindings_map.has_key("default"):
355 0
            user_bindings = user_bindings_map["default"]
356
357 193
        consumed = False
358 193
        if user_bindings:
359 0
            consumed = user_bindings.consumeKeyboardEvent(self,
360 0
                                                          keyboardEvent)
361 193
        if not consumed:
362 193
            consumed = self.keyBindings.consumeKeyboardEvent(self,
363 193
                                                             keyboardEvent)
364 193
        return consumed
365
366 1
    def consumesBrailleEvent(self, brailleEvent):
367
        """Called when a key is pressed on the braille display.
368
369
        Arguments:
370
        - brailleEvent: an instance of input_event.KeyboardEvent
371
372
        Returns True if the event is of interest.
373
        """
374 0
        user_bindings = None
375 0
        user_bindings_map = settings.brailleBindingsMap
376 0
        if user_bindings_map.has_key(self.__module__):
377 0
            user_bindings = user_bindings_map[self.__module__]
378 0
        elif user_bindings_map.has_key("default"):
379 0
            user_bindings = user_bindings_map["default"]
380
381 0
        command = brailleEvent.event
382 0
        consumes = False
383 0
        if user_bindings:
384 0
            consumes = user_bindings.has_key(command)
385 0
        if not consumes:
386 0
            consumes = self.brailleBindings.has_key(command)
387 0
        return consumes
388
389 1
    def processBrailleEvent(self, brailleEvent):
390
        """Called whenever a key is pressed on the Braille display.
391
392
        This method will primarily use the brailleBindings field of
393
        this script instance see if this script has an interest in the
394
        event.
395
396
        NOTE: there is latent, but unsupported, logic for allowing
397
        the user's user-settings.py file to extend and/or override
398
        the brailleBindings for a script.
399
400
        Arguments:
401
        - brailleEvent: an instance of input_event.BrailleEvent
402
        """
403
404
        # We'll annotate the event with a reference to this script.
405
        # This will allow external scripts to muck with the script
406
        # instance if they wish.
407
        #
408 0
        brailleEvent.script = self
409
410
        # We'll let the user bindings take precedence.  First, we'll
411
        # check to see if they have bindings specific for the particular
412
        # application, then we'll check to see if they have any default
413
        # bindings to use.
414
        #
415
        # [[[TODO: WDW - for performance, these bindings should probably
416
        # be conflated at initialization time.]]]
417
        #
418 0
        consumed = False
419 0
        user_bindings = None
420 0
        command = brailleEvent.event
421
422 0
        user_bindings_map = settings.brailleBindingsMap
423 0
        if user_bindings_map.has_key(self.name):
424 0
            user_bindings = user_bindings_map[self.name]
425 0
        elif user_bindings_map.has_key("default"):
426 0
            user_bindings = user_bindings_map["default"]
427
428 0
        if user_bindings and user_bindings.has_key(command):
429 0
            handler = user_bindings[command]
430 0
            consumed = handler.processInputEvent(self, brailleEvent)
431
432 0
        if (not consumed) and self.brailleBindings.has_key(command):
433 0
            handler = self.brailleBindings[command]
434 0
            consumed = handler.processInputEvent(self, brailleEvent)
435
436 0
        return consumed
437
438 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
439
        """Called when the visual object with focus changes.
440
441
        The primary purpose of this method is to present locus of focus
442
        information to the user.
443
444
        NOTE: scripts should not call this method directly.  Instead,
445
        a script should call orca.setLocusOfFocus, which will eventually
446
        result in this method being called.
447
448
        Arguments:
449
        - event: if not None, the Event that caused the change
450
        - oldLocusOfFocus: Accessible that is the old locus of focus
451
        - newLocusOfFocus: Accessible that is the new locus of focus
452
        """
453 0
        pass
454
455 1
    def visualAppearanceChanged(self, event, obj):
456
        """Called when the visual appearance of an object changes.
457
        This method should not be called for objects whose visual
458
        appearance changes solely because of focus -- setLocusOfFocus
459
        is used for that.  Instead, it is intended mostly for objects
460
        whose notional 'value' has changed, such as a checkbox
461
        changing state, a progress bar advancing, a slider moving,
462
        text inserted, caret moved, etc.
463
464
        The primary purpose of this method is to present the changed
465
        information to the user.
466
467
        NOTE: scripts should not call this method directly.  Instead,
468
        a script should call orca.visualAppearanceChanged, which will
469
        eventually result in this method being called.
470
471
        Arguments:
472
        - event: if not None, the Event that caused this to happen
473
        - obj: the Accessible whose visual appearance changed.
474
        """
475 0
        pass
476
477 1
    def activate(self):
478
        """Called when this script is activated."""
479 358
        pass
480
481 1
    def deactivate(self):
482
        """Called when this script is deactivated."""
483 358
        pass