Coverage Report - orca.script

ModuleCoverage %
orca.script
63%
1
# Orca
2
#
3
# Copyright 2004-2006 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 1996 2007-02-11 18:07:28Z joanied $"
37 1
__version__   = "$Revision: 1996 $"
38 1
__date__      = "$Date: 2007-02-11 10:07:28 -0800 (Sun, 11 Feb 2007) $"
39 1
__copyright__ = "Copyright (c) 2005-2006 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
50 2
class Script:
51
    """The specific focus tracking scripts for applications.
52
    """
53
54 1
    def __init__(self, app):
55
        """Creates a script for the given application, if necessary.
56
        This method should not be called by anyone except the
57
        focus_tracking_presenter.
58
59
        Arguments:
60
        - app: the Python Accessible application to create a script for
61
        """
62
63 61
        self.app = app
64
65 61
        if app:
66 58
            self.name = self.app.name
67
        else:
68 3
            self.name = "default"
69
70 61
        self.name += " (module=" + self.__module__ + ")"
71
72 61
        self.listeners = self.getListeners()
73
74
        # By default, handle events for non-active applications.
75
        #
76 61
        self.presentIfInactive = True
77
78 61
        self.inputEventHandlers = {}
79 61
        self.setupInputEventHandlers()
80 61
        self.keyBindings = self.getKeyBindings()
81 61
        self.brailleBindings = self.getBrailleBindings()
82
83 61
        self.brailleGenerator = self.getBrailleGenerator()
84 61
        self.speechGenerator = self.getSpeechGenerator()
85 61
        self.voices = settings.voices
86
87 61
        self.flatReviewContextClass = flat_review.Context
88
89 61
        self.findCommandRun = False
90
91
        # Assists with dealing with CORBA COMM_FAILURES.  A failure doesn't
92
        # always mean an object disappeared - there just might be a network
93
        # glitch.  So, on COMM_FAILURES, we might retry a few times before
94
        # giving up on an object.  This might need to be overridden by the
95
        # script.  See bug #397787.
96
        #
97 61
        self.commFailureWaitTime = 0.1
98 61
        self.commFailureAttemptLimit = 5
99
100 61
        debug.println(debug.LEVEL_FINE, "NEW SCRIPT: %s" % self.name)
101
102 1
    def getListeners(self):
103
        """Sets up the AT-SPI event listeners for this script.
104
105
        Returns a dictionary where the keys are AT-SPI event names
106
        and the values are script methods.
107
        """
108 61
        return {}
109
110 1
    def setupInputEventHandlers(self):
111
        """Defines InputEventHandler fields for this script that can be
112
        called by the key and braille bindings."""
113
        pass
114
115 1
    def getKeyBindings(self):
116
        """Defines the key bindings for this script.
117
118
        Returns an instance of keybindings.KeyBindings.
119
        """
120 62
        return keybindings.KeyBindings()
121
122 1
    def getKeyBindingsForInputHandler(self, inputEventHandler):
123
        """ Returns a KeyBindings object with the list of KeyBindings that
124
        matche the passed inputEventHandler as argument (at least the
125
        inputEventHandler that has the same handler function)
126
127
        Arguments:
128
        - inputEventHandler: an instance of input_event.InputEventHandler
129
130
        Returns an instance of keybindings.KeyBindings populated with
131
        keybindings.KeyBinding instances that match the inputEventHandler.
132
        """
133 0
        matches = keybindings.KeyBindings()
134
135 0
        for binding in self.keyBindings.keyBindings:
136 0
            if inputEventHandler._function == binding.handler._function:
137 0
                matches.add(binding)
138
139 0
        return matches
140
141 1
    def getBrailleBindings(self):
142
        """Defines the braille bindings for this script.
143
144
        Returns a dictionary where the keys are BrlTTY commands and the
145
        values are InputEventHandler instances.
146
        """
147 62
        return {}
148
149 1
    def getBrailleCommandsForInputHandler(self, inputEventHandler):
150
        """Returns a list of BrlTTY commands (they're in braille.py) that
151
        match the given inputEventHandler passed as argument.
152
153
        Arguments:
154
        - inputEventHandler: an instance of input_event.InputEventHandler
155
156
        Returns a list (possibly empty) of BrlTTY commands (they're in
157
        braille.py) that match the given inputEventHandler passed.
158
        """
159 0
        matches = []
160
161 0
        for command,handler in self.brailleBindings.iteritems():
162 0
            if inputEventHandler._function == handler._function:
163 0
                matches.append(command)
164
165 0
        return matches
166
167 1
    def getBrailleGenerator(self):
168
        """Returns the braille generator for this script.
169
        """
170 57
        return braillegenerator.BrailleGenerator(self)
171
172 1
    def getSpeechGenerator(self):
173
        """Returns the speech generator for this script.
174
        """
175 48
        return speechgenerator.SpeechGenerator(self)
176
177
    # [[[WDW - There is a circular reference going on somewhere (see
178
    # bug 333168).  In the presence of this reference, the existence
179
    # of a __del__ method prevents the garbage collector from
180
    # collecting this object. So, we will not define a __del__ method
181
    # until we understand where the circular reference is coming from.
182
    #
183
    #def __del__(self):
184
    #    debug.println(debug.LEVEL_FINE, "DELETE SCRIPT: %s" % self.name)
185
186 1
    def processObjectEvent(self, event):
187
        """Processes all AT-SPI object events of interest to this
188
        script.  The interest in events is specified via the
189
        'listeners' field that was defined during the construction of
190
        this script.
191
192
        In general, the primary purpose of handling object events is to
193
        keep track of changes to the locus of focus and notify the
194
        orca module of these changes via orca.setLocusOfFocus and
195
        orca.visualAppearanceChanged.
196
197
        Note that this script may be passed events it doesn't care
198
        about, so it needs to react accordingly.
199
200
        Arguments:
201
        - event: the Event
202
        """
203
204
        # Check to see if we really want to process this event.
205
        #
206 20555
        processEvent = (orca_state.activeScript == self \
207
                        or self.presentIfInactive)
208
209 20555
        if not processEvent:
210 220
            return
211
212
        # This calls the first listener it finds whose key *begins with* or is
213
        # the same as the event.type.  The reason we do this is that the event
214
        # type in the listeners dictionary may not be as specific as the event
215
        # type we received (e.g., the listeners dictionary might contain the
216
        # key "object:state-changed:" and the event.type might be
217
        # "object:state-changed:focused".  [[[TODO: WDW - the order of the
218
        # keys is *not* deterministic, and is not guaranteed to be related
219
        # to the order in which they were added.  So...we need to do something
220
        # different here.  Logged as bugzilla bug 319781.]]]
221
        #
222 545670
        for key in self.listeners.keys():
223 525540
            if event.type.startswith(key):
224 21639
                self.listeners[key](event)
225
226 1
    def consumesKeyboardEvent(self, keyboardEvent):
227
        """Called when a key is pressed on the keyboard.
228
229
        Arguments:
230
        - keyboardEvent: an instance of input_event.KeyboardEvent
231
232
        Returns True if the event is of interest.
233
        """
234 3257
        user_bindings = None
235 3257
        user_bindings_map = settings.keyBindingsMap
236 3257
        if user_bindings_map.has_key(self.__module__):
237 0
            user_bindings = user_bindings_map[self.__module__]
238 3257
        elif user_bindings_map.has_key("default"):
239 0
            user_bindings = user_bindings_map["default"]
240
241 3257
        consumes = False
242 3257
        if user_bindings:
243 0
            consumes = user_bindings.getInputHandler(keyboardEvent) != None
244 3257
        if not consumes:
245 3257
            consumes = self.keyBindings.getInputHandler(keyboardEvent) != None
246 3257
        return consumes
247
248 1
    def processKeyboardEvent(self, keyboardEvent):
249
        """Processes the given keyboard event.
250
251
        This method will primarily use the keybindings field of this
252
        script instance see if this script has an interest in the
253
        event.
254
255
        NOTE: there is latent, but unsupported, logic for allowing
256
        the user's user-settings.py file to extend and/or override
257
        the keybindings for a script.
258
259
        Arguments:
260
        - keyboardEvent: an instance of input_event.KeyboardEvent
261
        """
262
263
        # We'll annotate the event with a reference to this script.
264
        # This will allow external scripts to muck with the script
265
        # instance if they wish.
266
        #
267 154
        keyboardEvent.script = self
268
269
        # We'll let the user keybindings take precedence.  First, we'll
270
        # check to see if they have keybindings specific for the particular
271
        # application, then we'll check to see if they have any default
272
        # bindings to use.
273
        #
274
        # [[[TODO: WDW - for performance, these bindings should probably
275
        # be conflated at initialization time.]]]
276
        #
277 154
        user_bindings = None
278
279 154
        user_bindings_map = settings.keyBindingsMap
280 154
        if user_bindings_map.has_key(self.__module__):
281 0
            user_bindings = user_bindings_map[self.__module__]
282 154
        elif user_bindings_map.has_key("default"):
283 0
            user_bindings = user_bindings_map["default"]
284
285 154
        consumed = False
286 154
        if user_bindings:
287 0
            consumed = user_bindings.consumeKeyboardEvent(self,
288 0
                                                          keyboardEvent)
289 154
        if not consumed:
290 154
            consumed = self.keyBindings.consumeKeyboardEvent(self,
291 154
                                                             keyboardEvent)
292 154
        return consumed
293
294 1
    def consumesBrailleEvent(self, brailleEvent):
295
        """Called when a key is pressed on the braille display.
296
297
        Arguments:
298
        - brailleEvent: an instance of input_event.KeyboardEvent
299
300
        Returns True if the event is of interest.
301
        """
302 0
        user_bindings = None
303 0
        user_bindings_map = settings.brailleBindingsMap
304 0
        if user_bindings_map.has_key(self.__module__):
305 0
            user_bindings = user_bindings_map[self.__module__]
306 0
        elif user_bindings_map.has_key("default"):
307 0
            user_bindings = user_bindings_map["default"]
308
309 0
        command = brailleEvent.event
310 0
        consumes = False
311 0
        if user_bindings:
312 0
            consumes = user_bindings.has_key(command)
313 0
        if not consumes:
314 0
            consumes = self.brailleBindings.has_key(command)
315 0
        return consumes
316
317 1
    def processBrailleEvent(self, brailleEvent):
318
        """Called whenever a key is pressed on the Braille display.
319
320
        This method will primarily use the brailleBindings field of
321
        this script instance see if this script has an interest in the
322
        event.
323
324
        NOTE: there is latent, but unsupported, logic for allowing
325
        the user's user-settings.py file to extend and/or override
326
        the brailleBindings for a script.
327
328
        Arguments:
329
        - brailleEvent: an instance of input_event.BrailleEvent
330
        """
331
332
        # We'll annotate the event with a reference to this script.
333
        # This will allow external scripts to muck with the script
334
        # instance if they wish.
335
        #
336 0
        brailleEvent.script = self
337
338
        # We'll let the user bindings take precedence.  First, we'll
339
        # check to see if they have bindings specific for the particular
340
        # application, then we'll check to see if they have any default
341
        # bindings to use.
342
        #
343
        # [[[TODO: WDW - for performance, these bindings should probably
344
        # be conflated at initialization time.]]]
345
        #
346 0
        consumed = False
347 0
        user_bindings = None
348 0
        command = brailleEvent.event
349
350 0
        user_bindings_map = settings.brailleBindingsMap
351 0
        if user_bindings_map.has_key(self.name):
352 0
            user_bindings = user_bindings_map[self.name]
353 0
        elif user_bindings_map.has_key("default"):
354 0
            user_bindings = user_bindings_map["default"]
355
356 0
        if user_bindings and user_bindings.has_key(command):
357 0
            handler = user_bindings[command]
358 0
            consumed = handler.processInputEvent(self, brailleEvent)
359
360 0
        if (not consumed) and self.brailleBindings.has_key(command):
361 0
            handler = self.brailleBindings[command]
362 0
            consumed = handler.processInputEvent(self, brailleEvent)
363
364 0
        return consumed
365
366 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
367
        """Called when the visual object with focus changes.
368
369
        The primary purpose of this method is to present locus of focus
370
        information to the user.
371
372
        NOTE: scripts should not call this method directly.  Instead,
373
        a script should call orca.setLocusOfFocus, which will eventually
374
        result in this method being called.
375
376
        Arguments:
377
        - event: if not None, the Event that caused the change
378
        - oldLocusOfFocus: Accessible that is the old locus of focus
379
        - newLocusOfFocus: Accessible that is the new locus of focus
380
        """
381
        pass
382
383 1
    def visualAppearanceChanged(self, event, obj):
384
        """Called when the visual appearance of an object changes.
385
        This method should not be called for objects whose visual
386
        appearance changes solely because of focus -- setLocusOfFocus
387
        is used for that.  Instead, it is intended mostly for objects
388
        whose notional 'value' has changed, such as a checkbox
389
        changing state, a progress bar advancing, a slider moving,
390
        text inserted, caret moved, etc.
391
392
        The primary purpose of this method is to present the changed
393
        information to the user.
394
395
        NOTE: scripts should not call this method directly.  Instead,
396
        a script should call orca.visualAppearanceChanged, which will
397
        eventually result in this method being called.
398
399
        Arguments:
400
        - event: if not None, the Event that caused this to happen
401
        - obj: the Accessible whose visual appearance changed.
402
        """
403
        pass