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 |