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 |