Coverage Report - orca.atspi

ModuleCoverage %
orca.atspi
75%
1
# Orca
2
#
3
# Copyright 2005-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 1
"""Provides the interface to the AT-SPI Registry."""
21
22 1
__id__        = "$Id: atspi.py 2047 2007-02-23 01:52:09Z lmonsanto $"
23 1
__version__   = "$Revision: 2047 $"
24 1
__date__      = "$Date: 2007-02-22 17:52:09 -0800 (Thu, 22 Feb 2007) $"
25 1
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
26 1
__license__   = "LGPL"
27
28 1
import signal
29 1
import time
30
31 1
import gobject
32 1
gobject.threads_init()
33
34 1
import bonobo
35 1
import ORBit
36
37 1
ORBit.load_typelib("Accessibility")
38
39
# We will pass "orbit-io-thread" to initialize the ORB in threaded mode.
40
# This should hopefully help address bug 319652:
41
#
42
#   http://bugzilla.gnome.org/show_bug.cgi?id=319652
43
#
44
# See also:
45
#
46
#   http://bugzilla.gnome.org/show_bug.cgi?id=342614
47
#   http://mail.gnome.org/archives/orbit-list/2005-December/msg00001.html
48
#
49 1
ORBit.CORBA.ORB_init(orb_id="orbit-io-thread")
50
51 1
import Accessibility
52 1
import Accessibility__POA
53
54 1
import debug
55 1
import rolenames
56 1
import settings
57
58 2
class Event:
59
    """Converts the source of an event to an Accessible object.  We
60
    need this since the event object we get from the atspi is
61
    read-only.  So, we create this dummy event object to contain a copy
62
    of all the event members with the source converted to an
63
    Accessible.  It is perfectly OK for event handlers to annotate this
64
    object with their own attributes.
65
    """
66
67 1
    def __init__(self, e=None):
68 20899
        if e:
69 20899
            self.source   = Accessible.makeAccessible(e.source)
70 20899
            self.type     = e.type
71 20899
            self.detail1  = e.detail1
72 20899
            self.detail2  = e.detail2
73
74
            # If were talking to AT-SPI 1.7.0 or greater, we can get the
75
            # application information right away because it is tucked in
76
            # the EventDetails data new for 1.7.0.
77
            #
78 20899
            if e.any_data and (e.any_data.typecode().name) == "EventDetails":
79 20899
                details = e.any_data.value()
80 20899
                self.any_data = details.any_data
81 20899
                if self.source and details.host_application:
82 20895
                    self.source.app = Accessible.makeAccessible(
83
                        details.host_application)
84
            else:
85 0
                self.any_data = e.any_data
86
87
            # We need to make sure we reference any object that comes
88
            # to us via an any_data because we process events
89
            # asynchronously.  If we don't reference them, we may
90
            # end up with OBJECT_NOT_EXIST errors.  Please see
91
            # http://bugzilla.gnome.org/show_bug.cgi?id=395749 for
92
            # more information.
93
            #
94 20899
            if self.type == "object:active-descendant-changed":
95 160
                self.any_data = Accessible.makeAccessible(
96
                    self.any_data.value())
97 20739
            elif self.type == "object:text-changed:insert":
98 712
                self.any_data = self.any_data.value()
99 20027
            elif self.type == "object:text-changed:delete":
100 37
                self.any_data = self.any_data.value()
101
        else:
102 0
            self.source   = None
103 0
            self.type     = None
104 0
            self.detail1  = None
105 0
            self.detail2  = None
106 0
            self.any_data = None
107
108 2
class Registry:
109
    """Delegates to the actual AT-SPI Regisitry.
110
    """
111
112
    # The "Borg" singleton model - ensures we're really
113
    # only connecting to the registry once.
114
    #
115 1
    __sharedState = {}
116 1
    __instanceCount = 0
117
118 1
    __listeners=[]
119 1
    __keystrokeListeners=[]
120
121 1
    def __init__(self):
122
123
        # The "Borg" singleton model - ensures we're really
124
        # only connecting to the registry once.
125
        #
126 2454
        self.__dict__ = self.__sharedState
127 2454
        self.__instanceCount += 1
128 2454
        if not self.__dict__.has_key("registry"):
129 1
            self.registry = bonobo.get_object(
130
                "OAFIID:Accessibility_Registry:1.0",
131 1
                "Accessibility/Registry")
132 2454
        if not self.__dict__.has_key("desktop"):
133 1
            self.desktop = self.registry.getDesktop(0)
134
135 1
    def __blockPreventor(self):
136
        """[[[TODO: HACK to attempt to prevent deadlocks.  We call time.sleep
137
        here as a means to sidestep the global interpreter lock (GIL).]]]
138
        """
139 0
        if settings.gilSleepTime:
140 0
            time.sleep(settings.gilSleepTime)
141 0
        return True
142
143 1
    def start(self):
144
        """Starts event notification with the AT-SPI Registry.  This method
145
        only returns after 'stop' has been called.
146
        """
147 1
        Accessible.init(self)
148
149
        # We'll try our own main loop to help debug things.  Code borrowed
150
        # "The Whole PyGtk FAQ": http://www.async.com.br/faq/pygtk/
151
        #
152 1
        if settings.useBonoboMain:
153 1
            debug.println(debug.LEVEL_CONFIGURATION,
154 1
                          "atspi.start: using bonobo.main; "
155
                          + "gilSleepTime=%f" % settings.gilSleepTime)
156 1
            if settings.useBlockPreventor and settings.gilSleepTime:
157 0
                gobject.idle_add(self.__blockPreventor)
158 1
            bonobo.main()
159
        else:
160 0
            debug.println(debug.LEVEL_CONFIGURATION,
161 0
                          "atspi.start: using our custom main loop; "
162
                          + "gilSleepTime=%f" % settings.gilSleepTime)
163 0
            self.running = True
164 0
            context = gobject.MainLoop().get_context()
165 0
            while self.running:
166 0
                if settings.gilSleepTime:
167 0
                    time.sleep(settings.gilSleepTime)
168 0
                context.iteration(False)
169
170 1
    def stop(self):
171
        """Unregisters any event or keystroke listeners registered with
172
        the AT-SPI Registry and then stops event notification with the
173
        AT-SPI Registry.
174
        """
175 1
        Accessible.shutdown(self)
176 257
        for listener in (self.__listeners + self.__keystrokeListeners):
177 256
            listener.deregister()
178 1
        if settings.useBonoboMain:
179 1
            bonobo.main_quit()
180
        else:
181 0
            self.running = False
182
183 1
    def registerEventListener(self, callback, eventType):
184
        """Registers the given eventType and callback with the Registry.
185
186
        Arguments:
187
        - callback: function to call with an AT-SPI event instance
188
        - eventType: string representing the type of event
189
        """
190 34
        listener = EventListener(self.registry, callback, eventType)
191 34
        self.__listeners.append(listener)
192
193 1
    def deregisterEventListener(self, callback, eventType):
194
        """Unregisters the given eventType and callback with the Registry.
195
196
        Arguments:
197
        - callback: function to call with an AT-SPI event instance
198
        - eventType: string representing the type of event
199
        """
200 34
        found = True
201 101
        while len(self.__listeners) and found:
202 650
            for i in range(0, len(self.__listeners)):
203 617
                if (self.__listeners[i].callback == callback) \
204
                   and (self.__listeners[i].eventType == eventType):
205
                    # The __del__ method of the listener will unregister it.
206
                    #
207 34
                    self.__listeners.pop(i)
208 34
                    found = True
209 34
                    break
210
                else:
211 583
                    found = False
212
213 1
    def registerKeystrokeListeners(self, callback):
214
        """Registers a single callback for all possible keystrokes.
215
        """
216 257
        for i in range(0, (1 << (Accessibility.MODIFIER_NUMLOCK + 1))):
217 256
            self.__keystrokeListeners.append(
218
                KeystrokeListener(self.registry,
219 256
                                  callback, # callback
220 256
                                  [],       # keyset
221 256
                                  i,        # modifier mask
222 256
                                  [Accessibility.KEY_PRESSED_EVENT,
223
                                  Accessibility.KEY_RELEASED_EVENT],
224 256
                                  True,     # synchronous
225 256
                                  True,     # preemptive
226 256
                                  False))   # global
227
228
########################################################################
229
#                                                                      #
230
# Event listener classes for global and keystroke events               #
231
#                                                                      #
232
########################################################################
233
234 2
class EventListener(Accessibility__POA.EventListener):
235
    """Registers a callback directly with the AT-SPI Registry for the
236
    given event type.  Most users of this module will not use this
237
    class directly, but will instead use the registerEventListener method
238
    of the Registry."""
239
240 1
    def __init__(self, registry, callback, eventType):
241 34
        self.registry  = registry
242 34
        self.callback  = callback
243 34
        self.eventType = eventType
244 34
        self.register()
245
246 35
    def ref(self): pass
247
248 35
    def unref(self): pass
249
250 1
    def queryInterface(self, repo_id):
251 0
        thiz = None
252 0
        if repo_id == "IDL:Accessibility/EventListener:1.0":
253 0
            thiz = self._this()
254 0
        return thiz
255
256 1
    def register(self):
257 34
        self._default_POA().the_POAManager.activate()
258 34
        self.registry.registerGlobalEventListener(self._this(),
259 34
                                                  self.eventType)
260 34
        self.__registered = True
261 34
        return self.__registered
262
263 1
    def deregister(self):
264 34
        if not self.__registered:
265 0
            return
266 34
        self.registry.deregisterGlobalEventListener(self._this(),
267 34
                                                    self.eventType)
268 34
        self.__registered = False
269
270 1
    def notifyEvent(self, event):
271 40471
        if settings.timeoutCallback and (settings.timeoutTime > 0):
272 40471
            signal.signal(signal.SIGALRM, settings.timeoutCallback)
273 40471
            signal.alarm(settings.timeoutTime)
274
275 40471
        try:
276 40471
            self.callback(event)
277 0
        except:
278 0
            debug.printException(debug.LEVEL_WARNING)
279
280 40471
        if settings.timeoutCallback and (settings.timeoutTime > 0):
281 40471
            signal.alarm(0)
282
283 1
    def __del__(self):
284 34
        self.deregister()
285
286 2
class KeystrokeListener(Accessibility__POA.DeviceEventListener):
287
    """Registers a callback directly with the AT-SPI Registry for the
288
    given keystroke.  Most users of this module will not use this
289
    class directly, but will instead use the registerKeystrokeListeners
290
    method of the Registry."""
291
292 1
    def keyEventToString(event):
293 3257
        return ("KEYEVENT: type=%d\n" % event.type) \
294
               + ("          hw_code=%d\n" % event.hw_code) \
295
               + ("          modifiers=%d\n" % event.modifiers) \
296
               + ("          event_string=(%s)\n" % event.event_string) \
297
               + ("          is_text=%s\n" % event.is_text) \
298
               + ("          time=%f" % time.time())
299
300 1
    keyEventToString = staticmethod(keyEventToString)
301
302 1
    def __init__(self, registry, callback,
303
                 keyset, mask, type, synchronous, preemptive, isGlobal):
304 256
        self._default_POA().the_POAManager.activate()
305
306 256
        self.registry         = registry
307 256
        self.callback         = callback
308 256
        self.keyset           = keyset
309 256
        self.mask             = mask
310 256
        self.type             = type
311 256
        self.mode             = Accessibility.EventListenerMode()
312 256
        self.mode.synchronous = synchronous
313 256
        self.mode.preemptive  = preemptive
314 256
        self.mode._global     = isGlobal
315 256
        self.register()
316
317 513
    def ref(self): pass
318
319 513
    def unref(self): pass
320
321 1
    def queryInterface(self, repo_id):
322 0
        thiz = None
323 0
        if repo_id == "IDL:Accessibility/EventListener:1.0":
324 0
            thiz = self._this()
325 0
        return thiz
326
327 1
    def register(self):
328 256
        d = self.registry.getDeviceEventController()
329 256
        if d.registerKeystrokeListener(self._this(),
330 256
                                       self.keyset,
331 256
                                       self.mask,
332 256
                                       self.type,
333 256
                                       self.mode):
334 256
            self.__registered = True
335
        else:
336 0
            self.__registered = False
337 256
        return self.__registered
338
339 1
    def deregister(self):
340 256
        if not self.__registered:
341 0
            return
342 256
        d = self.registry.getDeviceEventController()
343 256
        d.deregisterKeystrokeListener(self._this(),
344 256
                                      self.keyset,
345 256
                                      self.mask,
346 256
                                      self.type)
347 256
        self.__registered = False
348
349 1
    def notifyEvent(self, event):
350
        """Called by the at-spi registry when a key is pressed or released.
351
352
        Arguments:
353
        - event: an at-spi DeviceEvent
354
355
        Returns True if the event has been consumed.
356
        """
357 3257
        if settings.timeoutCallback and (settings.timeoutTime > 0):
358 3257
            signal.signal(signal.SIGALRM, settings.timeoutCallback)
359 3257
            signal.alarm(settings.timeoutTime)
360
361 3257
        try:
362 3257
            consumed = self.callback(event)
363 0
        except:
364 0
            debug.printException(debug.LEVEL_WARNING)
365 0
            consumed = False
366
367 3257
        if settings.timeoutCallback and (settings.timeoutTime > 0):
368 3257
            signal.alarm(0)
369
370 3257
        return consumed
371
372 1
    def __del__(self):
373 0
        self.deregister()
374
375
########################################################################
376
#                                                                      #
377
# The Accessible class.                                                #
378
#                                                                      #
379
########################################################################
380
381 2
class Accessible:
382
    """Wraps AT-SPI Accessible objects and caches properties such as
383
    name, description, and parent.
384
385
    It also adds some properties to the AT-SPI Accessible including
386
    the Application to which the object belongs.
387
388
    For efficiency purposes, this class also maintains a cache of all
389
    Accessible objects obtained so far, and will return an element
390
    from that cache instead of creating a duplicate object.
391
    """
392
393
    # The cache of the currently active accessible objects.  The key is
394
    # the AT-SPI Accessible, and the value is the Python Accessible.
395
    # [[[TODO: WDW - probably should look at the __new__ method as a means
396
    # to handle singletons.]]]
397
    #
398 1
    _cache = {}
399
400 1
    def init(registry):
401
        """Registers various event listeners with the Registry to keep
402
        the Accessible cache up to date.
403
404
        Arguments:
405
        - registry: an instance of Registry
406
        """
407 1
        registry.registerEventListener(
408
            Accessible._onNameChanged,
409 1
            "object:property-change:accessible-name")
410 1
        registry.registerEventListener(
411
            Accessible._onDescriptionChanged,
412 1
            "object:property-change:accessible-description")
413 1
        registry.registerEventListener(
414
            Accessible._onParentChanged,
415 1
            "object:property-change:accessible-parent")
416 1
        registry.registerEventListener(
417
            Accessible._onStateChanged,
418 1
            "object:state-changed:")
419 1
        registry.registerEventListener(
420
            Accessible._onChildrenChanged,
421 1
            "object:children-changed:")
422
423 1
    init = staticmethod(init)
424
425 1
    def shutdown(registry):
426
        """Unregisters the event listeners that were registered in the
427
        init method.
428
429
        Arguments:
430
        - registry: an instance of Registry
431
        """
432 1
        registry.deregisterEventListener(
433
            Accessible._onNameChanged,
434 1
            "object:property-change:accessible-name")
435 1
        registry.deregisterEventListener(
436
            Accessible._onDescriptionChanged,
437 1
            "object:property-change:accessible-description")
438 1
        registry.deregisterEventListener(
439
            Accessible._onParentChanged,
440 1
            "object:property-change:accessible-parent")
441 1
        registry.deregisterEventListener(
442
            Accessible._onStateChanged,
443 1
            "object:state-changed:")
444 1
        registry.deregisterEventListener(
445
            Accessible._onChildrenChanged,
446 1
            "object:children-changed:")
447
448 1
    shutdown = staticmethod(shutdown)
449
450 1
    def _onNameChanged(e):
451
        """Core module event listener called when an object's name
452
        changes.  Updates the cache accordingly.
453
454
        Arguments:
455
        - e: AT-SPI event from the AT-SPI registry
456
        """
457
458 268
        if Accessible._cache.has_key(e.source):
459 235
            obj = Accessible._cache[e.source]
460 235
            if obj.__dict__.has_key("name"):
461 107
                del obj.__dict__["name"]
462 235
            if obj.__dict__.has_key("label"):
463 0
                del obj.__dict__["label"]
464
465 1
    _onNameChanged = staticmethod(_onNameChanged)
466
467 1
    def _onDescriptionChanged(e):
468
        """Core module event listener called when an object's description
469
        changes.  Updates the cache accordingly.
470
471
        Arguments:
472
        - e: AT-SPI event from the AT-SPI registry
473
        """
474
475 31
        if Accessible._cache.has_key(e.source):
476 28
            obj = Accessible._cache[e.source]
477 28
            if obj.__dict__.has_key("description"):
478 0
                del obj.__dict__["description"]
479 28
            if obj.__dict__.has_key("label"):
480 0
                del obj.__dict__["label"]
481
482 1
    _onDescriptionChanged = staticmethod(_onDescriptionChanged)
483
484 1
    def _onParentChanged(e):
485
        """Core module event listener called when an object's parent
486
        changes.  Updates the cache accordingly.
487
488
        Arguments:
489
        - e: AT-SPI event from the AT-SPI registry
490
        """
491
492
        # [[[TODO: WDW - I put this in here for now.  The idea is that
493
        # we will probably get parent changed events for objects that
494
        # are or will soon be defunct, so let's just forget about the
495
        # object rather than try to keep the cache in sync.]]]
496
        #
497 2211
        if Accessible._cache.has_key(e.source):
498 787
            Accessible.deleteAccessible(e.source)
499 2211
        return
500
501 1
    _onParentChanged = staticmethod(_onParentChanged)
502
503 1
    def _onStateChanged(e):
504
        """Core module event listener called when an object's state
505
        changes.  Updates the cache accordingly.
506
507
        Arguments:
508
        - e: AT-SPI event from the AT-SPI registry
509
        """
510
511 6095
        if Accessible._cache.has_key(e.source):
512
            # Let's get rid of defunct objects.  We hate them.
513
            #
514 4242
            if e.type == "object:state-changed:defunct":
515 957
                Accessible.deleteAccessible(e.source)
516
            else:
517 3285
                obj = Accessible._cache[e.source]
518 3285
                if obj.__dict__.has_key("state"):
519 0
                    del obj.state
520
521 1
    _onStateChanged = staticmethod(_onStateChanged)
522
523 1
    def _onChildrenChanged(e):
524
        """Core module event listener called when an object's child count
525
        changes.  Updates the cache accordingly.
526
527
        Arguments:
528
        - e: AT-SPI event from the AT-SPI registry
529
        """
530
531 2446
        if Accessible._cache.has_key(e.source):
532 1687
            obj = Accessible._cache[e.source]
533 1687
            if obj.__dict__.has_key("childCount"):
534 0
                del obj.childCount
535
536 1
    _onChildrenChanged = staticmethod(_onChildrenChanged)
537
538 1
    def makeAccessible(acc):
539
        """Make an Accessible.  This is used instead of a simple calls to
540
        Accessible's constructor because the object may already be in the
541
        cache.
542
543
        Arguments:
544
        - acc: the AT-SPI Accessibility_Accessible
545
546
        Returns a Python Accessible.
547
        """
548
549 58028
        obj = None
550
551 58028
        if not acc:
552 6
            return obj
553
554 58022
        if isinstance(acc, Accessible):
555 0
            debug.println(
556
                debug.LEVEL_WARNING,
557 0
                "WARNING: atspi.Accessible.makeAccessible:\n"
558
                "         Parameter acc passed in is a\n" \
559
                "         Python Accessible instead of an\n" \
560
                "         AT-SPI Accessible.\n"
561
                "         Returning Python Accessible.")
562 0
            return acc
563
564
        # [[[TODO: WDW - the AT-SPI appears to give us a different
565
        # accessible when we repeatedly ask for the same child of a
566
        # parent that manages its descendants.  So...we probably
567
        # shouldn't cache those kind of children because we're likely
568
        # to cause a memory leak. Logged as bugzilla bug 319675.]]]
569
        #
570 58022
        if not settings.cacheAccessibles:
571 0
            obj = Accessible(acc)
572 0
            return obj
573
574 58022
        if Accessible._cache.has_key(acc):
575 54128
            obj = Accessible._cache[acc]
576 54128
            if not obj.valid:
577 0
                del Accessible._cache[acc]
578 0
                obj = None
579
580 58022
        if not obj:
581 3894
            obj = Accessible(acc)
582
583 58022
        if obj.valid:
584 58022
            Accessible._cache[acc] = obj
585
        else:
586 0
            obj = None
587
588 58022
        return obj
589
590 1
    makeAccessible = staticmethod(makeAccessible)
591
592 1
    def deleteAccessible(acc):
593
        """Delete an Accessible from the cache if it exists.
594
595
        Arguments:
596
        - acc: the AT-SPI Accessibility_Accessible
597
        """
598
599 4087
        if acc and Accessible._cache.has_key(acc):
600 2401
            try:
601 2401
                del Accessible._cache[acc]
602 0
            except:
603 0
                pass
604
605 1
    deleteAccessible = staticmethod(deleteAccessible)
606
607 1
    def __init__(self, acc):
608
        """Obtains, and creates if necessary, a Python Accessible from
609
        an AT-SPI Accessibility_Accessible.  Applications should not
610
        call this method, but should instead call makeAccessible.
611
612
        Arguments:
613
        - acc: the AT-SPI Accessibility_Accessible to back this object
614
615
        Returns the associated Python Accessible.
616
        """
617
618
        # The setting of self._acc to None here is to help with manual
619
        # and unit testing of this module.  Furthermore, it helps us
620
        # determine if this particular instance is really backed by an
621
        # object or not.
622
        #
623 3894
        self._acc = None
624
625
        # We'll also keep track of whether this object is any good to
626
        # us or not.  This object will be deleted when a defunct event
627
        # is received for it, but anything could happen prior to that
628
        # event, so we keep this extra field around to help us.
629
        #
630 3894
        self.valid = False
631
632
        # [[[TODO: WDW - should do an assert here to make sure we're
633
        # getting a raw AT-SPI Accessible and not one of our own locally
634
        # cached Accessible instances. Logged as bugzilla bug 319673.]]]
635
        #
636 3894
        assert (not Accessible._cache.has_key(acc)), \
637
               "Attempt to create an Accessible that's already been made."
638
639
        # See if we have an application. Via bridges such as the Java
640
        # access bridge, we might be getting a CORBA::Object, which is
641
        # of little use to us.  We need to narrow it down to something
642
        # we can use.  The first attempt is to see if we can get an
643
        # application out of it.  Then we go for an accessible.
644
        #
645 3894
        self.accessible = None
646 3894
        try:
647 3894
            self.accessible = acc._narrow(Accessibility.Application)
648 37
            try:
649 37
                self.toolkitName = self.accessible.toolkitName
650 0
            except:
651 0
                self.toolkitName = None
652 37
            try:
653 37
                self.version = self.accessible.version
654 0
            except:
655 0
                self.version = None
656 3857
        except:
657 3857
            try:
658 3857
                self.accessible = acc._narrow(Accessibility.Accessible)
659 0
            except:
660 0
                debug.printException(debug.LEVEL_SEVERE)
661 0
                debug.println(debug.LEVEL_SEVERE,
662 0
                              "atspi.py:Accessible.__init__" \
663
                              + " NOT GIVEN AN ACCESSIBLE!")
664 0
                self.accessible = None
665
666
        # Save a reference to the AT-SPI object.
667
        #
668 3894
        if self.accessible:
669 3894
            try:
670 3894
                self.accessible.ref()
671 3894
                self._acc = acc
672 3894
                self.valid = True
673 0
            except:
674 0
                debug.printException(debug.LEVEL_SEVERE)
675
676 1
    def getRelationString(self):
677
        """Returns a space-delimited string composed of the given object's
678
        Accessible relations attribute.  This is for debug purposes.
679
        """
680
681 1835
        relations = self.relations
682 1835
        relString = " "
683 1837
        for relation in relations:
684 2
            if relation.getRelationType() == Accessibility.RELATION_LABEL_FOR:
685 0
                relString += "LABEL_FOR "
686 2
            if relation.getRelationType() == Accessibility.RELATION_LABELLED_BY:
687 2
                relString += "LABELLED_BY "
688 2
            if relation.getRelationType() == \
689
                                       Accessibility.RELATION_CONTROLLER_FOR:
690 0
                relString += "CONTROLLER_FOR "
691 2
            if relation.getRelationType() == \
692
                                       Accessibility.RELATION_CONTROLLED_BY:
693 0
                relString += "CONTROLLED_BY "
694 2
            if relation.getRelationType() == Accessibility.RELATION_MEMBER_OF:
695 0
                relString += "MEMBER_OF "
696 2
            if relation.getRelationType() == Accessibility.RELATION_TOOLTIP_FOR:
697 0
                relString += "TOOLTIP_FOR "
698 2
            if relation.getRelationType() == \
699
                                       Accessibility.RELATION_NODE_CHILD_OF:
700 0
                relString += "NODE_CHILD_OF "
701 2
            if relation.getRelationType() == Accessibility.RELATION_EXTENDED:
702 0
                relString += "RELATION_EXTENDED "
703 2
            if relation.getRelationType() == Accessibility.RELATION_FLOWS_TO:
704 0
                relString += "FLOWS_TO "
705 2
            if relation.getRelationType() == Accessibility.RELATION_FLOWS_FROM:
706 0
                relString += "FLOWS_FROM "
707 2
            if relation.getRelationType() == \
708
                                       Accessibility.RELATION_SUBWINDOW_OF:
709 0
                relString += "SUBWINDOW_OF "
710 2
            if relation.getRelationType() == Accessibility.RELATION_EMBEDS:
711 0
                relString += "EMBEDS "
712 2
            if relation.getRelationType() == Accessibility.RELATION_EMBEDDED_BY:
713 0
                relString += "EMBEDDED_BY "
714 2
            if relation.getRelationType() == Accessibility.RELATION_POPUP_FOR:
715 0
                relString += "POPUP_FOR "
716 2
            if relation.getRelationType() == \
717
                                       Accessibility.RELATION_PARENT_WINDOW_OF:
718 0
                relString += "WINDOW_OF "
719
720 1835
        return relString.strip()
721
722 1
    def getStateString(self):
723
        """Returns a space-delimited string composed of the given object's
724
        Accessible state attribute.  This is for debug purposes.
725
        """
726
727 1835
        stateSet = self.state
728 1835
        stateString = " "
729 1835
        if stateSet.count(Accessibility.STATE_INVALID):
730 0
            stateString += "INVALID "
731 1835
        if stateSet.count(Accessibility.STATE_ACTIVE):
732 31
            stateString += "ACTIVE "
733 1835
        if stateSet.count(Accessibility.STATE_ARMED):
734 90
            stateString += "ARMED "
735 1835
        if stateSet.count(Accessibility.STATE_BUSY):
736 0
            stateString += "BUSY "
737 1835
        if stateSet.count(Accessibility.STATE_CHECKED):
738 8
            stateString += "CHECKED "
739 1835
        if stateSet.count(Accessibility.STATE_COLLAPSED):
740 0
            stateString += "COLLAPSED "
741 1835
        if stateSet.count(Accessibility.STATE_DEFUNCT):
742 0
            stateString += "DEFUNCT "
743 1835
        if stateSet.count(Accessibility.STATE_EDITABLE):
744 543
            stateString += "EDITABLE "
745 1835
        if stateSet.count(Accessibility.STATE_ENABLED):
746 1835
            stateString += "ENABLED "
747 1835
        if stateSet.count(Accessibility.STATE_EXPANDABLE):
748 0
            stateString += "EXPANDABLE "
749 1835
        if stateSet.count(Accessibility.STATE_EXPANDED):
750 0
            stateString += "EXPANDED "
751 1835
        if stateSet.count(Accessibility.STATE_FOCUSABLE):
752 700
            stateString += "FOCUSABLE "
753 1835
        if stateSet.count(Accessibility.STATE_FOCUSED):
754 762
            stateString += "FOCUSED "
755 1835
        if stateSet.count(Accessibility.STATE_HAS_TOOLTIP):
756 0
            stateString += "HAS_TOOLTIP "
757 1835
        if stateSet.count(Accessibility.STATE_HORIZONTAL):
758 0
            stateString += "HORIZONTAL "
759 1835
        if stateSet.count(Accessibility.STATE_ICONIFIED):
760 0
            stateString += "ICONIFIED "
761 1835
        if stateSet.count(Accessibility.STATE_MODAL):
762 0
            stateString += "MODAL "
763 1835
        if stateSet.count(Accessibility.STATE_MULTI_LINE):
764 551
            stateString += "MULTI_LINE "
765 1835
        if stateSet.count(Accessibility.STATE_MULTISELECTABLE):
766 2
            stateString += "MULTISELECTABLE "
767 1835
        if stateSet.count(Accessibility.STATE_OPAQUE):
768 106
            stateString += "OPAQUE "
769 1835
        if stateSet.count(Accessibility.STATE_PRESSED):
770 0
            stateString += "PRESSED "
771 1835
        if stateSet.count(Accessibility.STATE_RESIZABLE):
772 34
            stateString += "RESIZABLE "
773 1835
        if stateSet.count(Accessibility.STATE_SELECTABLE):
774 234
            stateString += "SELECTABLE "
775 1835
        if stateSet.count(Accessibility.STATE_SELECTED):
776 95
            stateString += "SELECTED "
777 1835
        if stateSet.count(Accessibility.STATE_SENSITIVE):
778 1833
            stateString += "SENSITIVE "
779 1835
        if stateSet.count(Accessibility.STATE_SHOWING):
780 1808
            stateString += "SHOWING "
781 1835
        if stateSet.count(Accessibility.STATE_SINGLE_LINE):
782 64
            stateString += "SINGLE_LINE "
783 1835
        if stateSet.count(Accessibility.STATE_STALE):
784 0
            stateString += "STALE "
785 1835
        if stateSet.count(Accessibility.STATE_TRANSIENT):
786 64
            stateString += "TRANSIENT "
787 1835
        if stateSet.count(Accessibility.STATE_VERTICAL):
788 0
            stateString += "VERTICAL "
789 1835
        if stateSet.count(Accessibility.STATE_VISIBLE):
790 1811
            stateString += "VISIBLE "
791 1835
        if stateSet.count(Accessibility.STATE_MANAGES_DESCENDANTS):
792 35
            stateString += "MANAGES_DESCENDANTS "
793 1835
        if stateSet.count(Accessibility.STATE_INDETERMINATE):
794 0
            stateString += "INDETERMINATE "
795
796 1835
        return stateString.strip()
797
798 1
    def accessibleNameToString(self):
799
        """Returns the accessible's name in single quotes or
800
        the string None if the accessible does not have a name.
801
        """
802
803 10211
        if self.name:
804 5085
            return "'" + self.name + "'"
805
        else:
806 5108
            return "None"
807
808 1
    def __del__(self):
809
        """Unrefs the AT-SPI Accessible associated with this object.
810
        """
811
812 1954
        if self.accessible:
813 1954
            try:
814 1954
                self.accessible.unref()
815 11
            except:
816 11
                pass
817 1954
            try:
818 1954
                Accessible.deleteAccessible(self._acc)
819 1954
                self.accessible = None
820 1954
                self._acc = None
821 0
            except:
822 0
                pass
823
824 1
    def __get_name(self):
825
        """Returns the object's accessible name as a string.
826
        """
827
828
        # Combo boxes don't seem to issue accessible-name changed
829
        # events, so we can't cache their names.  The main culprit
830
        # here seems to be the combo box in gaim's "Join Chat" window.
831
        #
832 15002
        name = self.accessible.name
833
834 14941
        if name and settings.cacheValues \
835
            and (self.role != rolenames.ROLE_COMBO_BOX):
836 1015
            self.name = name
837
838 14941
        return name
839
840 1
    def __get_description(self):
841
        """Returns the object's accessible description as a string.
842
        """
843
844 220
        description = self.accessible.description
845
846 220
        if description and settings.cacheValues and settings.cacheDescriptions:
847 16
            self.description = description
848
849 220
        return description
850
851 1
    def __get_parent(self):
852
        """Returns the object's parent as a Python Accessible.  If
853
        this object has no parent, None will be returned.
854
        """
855
856
        # We will never set self.parent if the backing accessible doesn't
857
        # have a parent.  The reason we do this is that we may sometimes
858
        # get events for objects without a parent, but then the object ends
859
        # up getting a parent later on.
860
        #
861 12475
        accParent = self.accessible.parent
862
863 12349
        if not accParent:
864 9753
            return None
865
        else:
866 2596
            parent = Accessible.makeAccessible(accParent);
867
868 2596
            if settings.cacheValues:
869 2596
                self.parent = parent
870
871 2596
            return parent;
872
873 1
    def __get_child_count(self):
874
        """Returns the number of children for this object.
875
        """
876
877 9047
        childCount = self.accessible.childCount
878
879
        # We don't want to cache this value because it's possible that it
880
        # will continually change.
881
        # if settings.cacheValues:
882
        #     self.childCount = childCount
883
884 9036
        return childCount
885
886 1
    def __get_index(self):
887
        """Returns the index of this object in its parent's child list.
888
        """
889
890 2229
        index = self.accessible.getIndexInParent()
891
892
        # We don't want to cache this value because it's possible that it
893
        # will continually change.
894
        # if settings.cacheValues:
895
        #     self.index = index
896
897 2228
        return index
898
899 1
    def __get_role(self):
900
        """Returns the Accessible role name of this object as a string.
901
        This string is not localized and can be used for comparison.
902
903
        Note that this fudges the rolename of the object to match more closely
904
        what it is.  The only thing that is being fudged right now is to
905
        coalesce radio and check menu items that are also submenus; gtk-demo
906
        has an example of this in its menus demo.
907
        """
908
909 8150
        role = self.accessible.getRoleName()
910
911
        # [[[TODO: WDW - HACK to handle the situation where some
912
        # things might not be quite lined up with the ATK and AT-SPI.
913
        # That is, some roles might have ids but no string yet.  See
914
        # http://bugzilla.gnome.org/show_bug.cgi?id=361757.
915
        #
916 8129
        if not len(role):
917 0
            try:
918 0
                roleId = self.accessible.getRole()
919 0
                if roleId == Accessibility.ROLE_LINK:
920 0
                    role = rolenames.ROLE_LINK
921 0
                elif roleId == Accessibility.ROLE_INPUT_METHOD_WINDOW:
922 0
                    role = rolenames.ROLE_INPUT_METHOD_WINDOW
923 0
            except:
924 0
                pass
925
926
        # [[[TODO: HACK to coalesce menu items with children into
927
        # menus.  The menu demo in gtk-demo does this, and one
928
        # might view that as an edge case.  But, in
929
        # gnome-terminal, "Terminal" -> "Set Character Encoding"
930
        # is a menu item with children, but it behaves like a
931
        # menu.]]]
932
        #
933 8129
        if (role == rolenames.ROLE_CHECK_MENU_ITEM) \
934
            and (self.childCount > 0):
935 0
                role = rolenames.ROLE_CHECK_MENU
936 8129
        elif (role == rolenames.ROLE_RADIO_MENU_ITEM) \
937
            and (self.childCount > 0):
938 0
                role = rolenames.ROLE_RADIO_MENU
939 8129
        elif (role == rolenames.ROLE_MENU_ITEM) \
940
            and (self.childCount > 0):
941 0
                role = rolenames.ROLE_MENU
942
943
        # [[[TODO: HACK because Java gives us radio button role and
944
        # check box role for menu item objects, instead of giving us
945
        # radio menu item role and check menu item role (see SwingSet
946
        # menus).]]]
947
        #
948 8129
        if (self.parent) and (self.parent.role == rolenames.ROLE_MENU):
949 180
            if (role == rolenames.ROLE_RADIO_BUTTON):
950 0
                role = rolenames.ROLE_RADIO_MENU_ITEM
951 180
            elif (role == rolenames.ROLE_CHECK_BOX):
952 0
                role = rolenames.ROLE_CHECK_MENU_ITEM
953
954
        # [[[TODO: HACK because we sometimes get an object with an
955
        # unknown role but it's role changes later on and we are not
956
        # notified.  An example of this is gnome-terminal.  So...to
957
        # help with this, we will not cache the role if it is unknown.
958
        # See http://bugzilla.gnome.org/show_bug.cgi?id=344218 for
959
        # more info.]]]
960
        #
961 8129
        if role and settings.cacheValues \
962
            and (role != rolenames.ROLE_UNKNOWN):
963 3795
            self.role = role
964
965 8129
        return role
966
967 1
    def __get_localized_rolename(self):
968
        """Returns the Accessible role name of this object as a
969
        localized string.  Most callers should use __get_role instead
970
        since it returns a non-localized string that can be used for
971
        comparison.
972
        """
973
974 1
        localizedRoleName = self.accessible.getLocalizedRoleName()
975
976 1
        if localizedRoleName and settings.cacheValues:
977 1
            self.localizedRoleName = localizedRoleName
978
979 1
        return localizedRoleName
980
981 1
    def __get_state(self):
982
        """Returns the Accessible StateSeq of this object, which is a
983
        sequence of Accessible StateTypes.
984
        """
985
986 48267
        try:
987 48267
            stateSet = self.accessible.getState()
988 96
        except:
989 96
            stateSet = None
990 48267
        if stateSet:
991 48171
            try:
992 48171
                state = stateSet._narrow(Accessibility.StateSet).getStates()
993 3
            except:
994 3
                state = []
995
        else:
996 96
            state = []
997
998
        # [[[WDW - we don't seem to always get appropriate state changed
999
        # information, so we will not cache state information.]]]
1000
        #
1001
        #if state and settings.cacheValues:
1002
        #    self.state = state
1003
1004 48267
        return state
1005
1006 1
    def __get_relations(self):
1007
        """Returns the Accessible RelationSet of this object as a list.
1008
        """
1009
1010 20807
        relations = []
1011
1012 20807
        relationSet = self.accessible.getRelationSet()
1013
1014 21837
        for relation in relationSet:
1015 1162
            try:
1016 1162
                relations.append(relation._narrow(Accessibility.Relation))
1017 0
            except:
1018 0
                pass
1019
1020 20675
        return relations
1021
1022 1
    def __get_app(self):
1023
        """Returns the AT-SPI Accessibility_Application associated with this
1024
        object.  Returns None if the application cannot be found (usually
1025
        the indication of an AT-SPI bug).
1026
        """
1027
1028
        # [[[TODO: WDW - this code seems like it might break if this
1029
        # object is an application to begin with. Logged as bugzilla
1030
        # bug 319677.]]]
1031
        #
1032 209
        debug.println(debug.LEVEL_FINEST,
1033 209
                      "Finding app for source.name=" \
1034
                      + self.accessibleNameToString())
1035 209
        obj = self
1036 1393
        while obj.parent and (obj != obj.parent):
1037 1184
            obj = obj.parent
1038 1184
            debug.println(debug.LEVEL_FINEST,
1039 1184
                          "--> parent.name=" + obj.accessibleNameToString())
1040
1041 209
        if (obj == obj.parent):
1042 0
            debug.println(debug.LEVEL_SEVERE,
1043 0
                          "ERROR in Accessible.__get_app: obj == obj.parent!")
1044 0
            return None
1045 209
        elif (obj.role != rolenames.ROLE_APPLICATION):
1046 3
            debug.println(debug.LEVEL_FINEST,
1047 3
                          "ERROR in Accessible.__get_app: top most parent " \
1048
                          "(name='%s') is of role %s" % (obj.name, obj.role))
1049
1050
            # [[[TODO: We'll let this fall through for some cases.  It
1051
            # seems as though we don't always end up with an
1052
            # application, but we do end up with *something* that is
1053
            # uniquely identifiable as the app.
1054
            #
1055 3
            if (obj.role != rolenames.ROLE_INVALID) \
1056
               and (obj.role != rolenames.ROLE_FRAME):
1057 3
                return None
1058
1059 206
        debug.println(debug.LEVEL_FINEST, "Accessible app for %s is %s" \
1060
                      % (self.accessibleNameToString(), \
1061
                         obj.accessibleNameToString()))
1062
1063 206
        if settings.cacheValues:
1064 206
            self.app = obj
1065
1066 206
        return obj
1067
1068 1
    def __get_extents(self, coordinateType = 0):
1069
        """Returns the object's accessible extents as an
1070
        Accessibility.BoundingBox object, or None if the object doesn't
1071
        implement the Accessibility Component interface.
1072
1073
        Arguments:
1074
        - coordinateType: 0 = get the extents in screen coordinates,
1075
                          1 = get the extents in window coordinates
1076
1077
        Returns:
1078
        This object's accessible extents as an Accessibility.BoundingBox
1079
        object, or None if the object doesn't implement the Accessibility
1080
        Component interface.
1081
        """
1082
1083 438
        component = self.component
1084
1085 438
        if not component:
1086 0
            return None
1087
1088 438
        extents = component.getExtents(coordinateType)
1089
1090
        # [[[TODO: WDW - caching the extents is dangerous because
1091
        # the object may move, resulting in the current extents
1092
        # becoming way out of date.  Perhaps need to cache just
1093
        # the component interface and suffer the hit for getting
1094
        # the extents if we cannot figure out how to determine if
1095
        # the cached extents is out of date. Logged as bugzilla
1096
        # bug 319678.]]]
1097
        #
1098
        #if settings.cacheValues:
1099
        #    self.extents = extents
1100
1101 438
        return extents
1102
1103 1
    def __get_action(self):
1104
        """Returns an object that implements the Accessibility_Action
1105
        interface for this object, or None if this object doesn't implement
1106
        the Accessibility_Action interface.
1107
        """
1108
1109 415
        action = self.accessible.queryInterface("IDL:Accessibility/Action:1.0")
1110
1111 415
        if action:
1112 369
            try:
1113 369
                action = action._narrow(Accessibility.Action)
1114 0
            except:
1115 0
                action = None
1116
1117 415
        if action and settings.cacheValues:
1118 369
            self.action = action
1119
1120 415
        return action
1121
1122 1
    def __get_component(self):
1123
        """Returns an object that implements the Accessibility_Component
1124
        interface for this object, or None if this object doesn't implement
1125
        the Accessibility_Component interface.
1126
        """
1127
1128 389
        component = self.accessible.queryInterface(\
1129
            "IDL:Accessibility/Component:1.0")
1130
1131 389
        if component:
1132 389
            try:
1133 389
                component = component._narrow(Accessibility.Component)
1134 0
            except:
1135 0
                component = None
1136
1137 389
        if component and settings.cacheValues:
1138 389
            self.component = component
1139
1140 389
        return component
1141
1142 1
    def __get_hyperlink(self):
1143
        """Returns an object that implements the Accessibility_Hyperlink
1144
        interface for this object, or None if this object doesn't implement
1145
        the Accessibility_Hyperlink interface.
1146
        """
1147
1148 0
        hyperlink = self.accessible.queryInterface(\
1149
            "IDL:Accessibility/Hyperlink:1.0")
1150
1151 0
        if hyperlink:
1152 0
            try:
1153 0
                hyperlink = hyperlink._narrow(Accessibility.Hyperlink)
1154 0
            except:
1155 0
                hyperlink = None
1156
1157 0
        if hyperlink and settings.cacheValues:
1158 0
            self.hyperlink = hyperlink
1159
1160 0
        return hyperlink
1161
1162 1
    def __get_hypertext(self):
1163
        """Returns an object that implements the Accessibility_Hypertext
1164
        interface for this object, or None if this object doesn't implement
1165
        the Accessibility_Hypertext interface.
1166
        """
1167
1168 33
        hypertext = self.accessible.queryInterface(\
1169
            "IDL:Accessibility/Hypertext:1.0")
1170
1171 33
        if hypertext:
1172 9
            try:
1173 9
                hypertext = hypertext._narrow(Accessibility.Hypertext)
1174 0
            except:
1175 0
                hypertext = None
1176
1177 33
        if hypertext and settings.cacheValues:
1178 9
            self.hypertext = hypertext
1179
1180 33
        return hypertext
1181
1182 1
    def __get_image(self):
1183
        """Returns an object that implements the Accessibility_Image
1184
        interface for this object, or None if this object doesn't implement
1185
        the Accessibility_Image interface.
1186
        """
1187
1188 307
        image = self.accessible.queryInterface(\
1189
            "IDL:Accessibility/Image:1.0")
1190
1191 307
        if image:
1192 0
            try:
1193 0
                image = image._narrow(Accessibility.Image)
1194 0
            except:
1195 0
                image = None
1196
1197 307
        if image and settings.cacheValues:
1198 0
            self.image = image
1199
1200 307
        return image
1201
1202 1
    def __get_selection(self):
1203
        """Returns an object that implements the Accessibility_Selection
1204
        interface for this object, or None if this object doesn't implement
1205
        the Accessibility_Selection interface.
1206
        """
1207
1208 234
        selection = self.accessible.queryInterface(\
1209
            "IDL:Accessibility/Selection:1.0")
1210
1211 234
        if selection:
1212 91
            try:
1213 91
                selection = selection._narrow(Accessibility.Selection)
1214 0
            except:
1215 0
                selection = None
1216
1217 234
        if selection and settings.cacheValues:
1218 91
            self.selection = selection
1219
1220 234
        return selection
1221
1222 1
    def __get_table(self):
1223
        """Returns an object that implements the Accessibility_Table
1224
        interface for this object, or None if this object doesn't implement
1225
        the Accessibility_Table interface.
1226
        """
1227
1228 1138
        table = self.accessible.queryInterface("IDL:Accessibility/Table:1.0")
1229
1230 1138
        if table:
1231 14
            try:
1232 14
                table = table._narrow(Accessibility.Table)
1233 0
            except:
1234 0
                table = None
1235
1236 1138
        if table and settings.cacheValues:
1237 14
            self.table = table
1238
1239 1138
        return table
1240
1241 1
    def __get_text(self):
1242
        """Returns an object that implements the Accessibility_Text
1243
        interface for this object, or None if this object doesn't implement
1244
        the Accessibility_Text interface.
1245
        """
1246
1247 11643
        text = self.accessible.queryInterface("IDL:Accessibility/Text:1.0")
1248
1249 11636
        if text:
1250 673
            try:
1251 673
                text = text._narrow(Accessibility.Text)
1252 0
            except:
1253 0
                text = None
1254
1255 11636
        if text and settings.cacheValues:
1256 673
            self.text = text
1257
1258 11636
        return text
1259
1260 1
    def __get_value(self):
1261
        """Returns an object that implements the Accessibility_Value
1262
        interface for this object, or None if this object doesn't implement
1263
        the Accessibility_Value interface.
1264
        """
1265
1266 4941
        value = self.accessible.queryInterface("IDL:Accessibility/Value:1.0")
1267
1268 4941
        if value:
1269 13
            try:
1270 13
                value = value._narrow(Accessibility.Value)
1271 0
            except:
1272 0
                value = None
1273
1274 4941
        if value and settings.cacheValues:
1275 13
            self.value = value
1276
1277 4941
        return value
1278
1279 1
    def __get_attributes(self):
1280
        """Returns an Accessibility_AttributeSet of the object.
1281
        """
1282
1283 174
        try:
1284 174
            attributes = self.accessible.getAttributes()
1285 174
        except:
1286 174
            attributes = None
1287
1288 174
        return attributes
1289
1290 1
    def __getattr__(self, attr):
1291
        """Created virtual attributes for the Accessible object to make
1292
        the syntax a bit nicer (e.g., acc.name rather than acc.name()).
1293
        This method is also called if and only if the given attribute
1294
        does not exist in the object.  Thus, we're effectively lazily
1295
        building a cache to the remote object attributes here.
1296
1297
        Arguments:
1298
        - attr: a string indicating the attribute name to retrieve
1299
1300
        Returns the value of the given attribute.
1301
        """
1302
1303 1212396
        if attr == "name":
1304 15002
            return self.__get_name()
1305 1197394
        elif attr == "description":
1306 220
            return self.__get_description()
1307 1197174
        elif attr == "parent":
1308 12475
            return self.__get_parent()
1309 1184699
        elif attr == "childCount":
1310 9047
            return self.__get_child_count()
1311 1175652
        elif attr == "index":
1312 2229
            return self.__get_index()
1313 1173423
        elif attr == "role":
1314 8150
            return self.__get_role()
1315 1165273
        elif attr == "localizedRoleName":
1316 1
            return self.__get_localized_rolename()
1317 1165272
        elif attr == "state":
1318 48267
            return self.__get_state()
1319 1117005
        elif attr == "relations":
1320 20807
            return self.__get_relations()
1321 1096198
        elif attr == "app":
1322 209
            return self.__get_app()
1323 1095989
        elif attr == "extents":
1324 438
            return self.__get_extents()
1325 1095551
        elif attr == "action":
1326 415
            return self.__get_action()
1327 1095136
        elif attr == "component":
1328 389
            return self.__get_component()
1329 1094747
        elif attr == "hyperlink":
1330 0
            return self.__get_hyperlink()
1331 1094747
        elif attr == "hypertext":
1332 33
            return self.__get_hypertext()
1333 1094714
        elif attr == "image":
1334 307
            return self.__get_image()
1335 1094407
        elif attr == "selection":
1336 234
            return self.__get_selection()
1337 1094173
        elif attr == "table":
1338 1138
            return self.__get_table()
1339 1093035
        elif attr == "text":
1340 11643
            return self.__get_text()
1341 1081392
        elif attr == "value":
1342 4941
            return self.__get_value()
1343 1076451
        elif attr == "attributes":
1344 174
            return self.__get_attributes()
1345 1076277
        elif attr.startswith('__') and attr.endswith('__'):
1346 1076215
            raise AttributeError, attr
1347
        else:
1348 62
            return self.__dict__[attr]
1349
1350 1
    def child(self, index):
1351
        """Returns the specified child of this object.
1352
1353
        Arguments:
1354
        - index: an integer specifying which child to obtain
1355
1356
        Returns the child at the given index or raise an exception if the
1357
        index is out of bounds or the child is invalid.
1358
        """
1359
1360
        # [[[TODO: WDW - the AT-SPI appears to give us a different accessible
1361
        # when we repeatedly ask for the same child of a parent that manages
1362
        # its descendants.  So...we probably shouldn't cache those kind of
1363
        # children because we're likely to cause a memory leak.]]]
1364
        #
1365
        # Save away details we now know about this child
1366
        #
1367 10972
        newChild = None
1368 10972
        if index >= 0 and index < self.accessible.childCount:
1369 10972
            accChild = self.accessible.getChildAtIndex(index)
1370 10972
            if accChild:
1371 10972
                newChild = Accessible.makeAccessible(accChild)
1372 10972
                newChild.index = index
1373 10972
                newChild.parent = self
1374 10972
                newChild.app = self.app
1375
1376 10972
        if not newChild:
1377
            # The problem with a child not existing is a bad one.
1378
            # We want to issue a warning and we also want to know
1379
            # where it happens.
1380
            #
1381 0
            debug.printStack(debug.LEVEL_WARNING)
1382 0
            debug.println(debug.LEVEL_WARNING,
1383 0
                          "Child at index %d is not an Accessible" % index)
1384
1385 10972
        return newChild
1386
1387 1
    def toString(self, indent="", includeApp=True):
1388
1389
        """Returns a string, suitable for printing, that describes the
1390
        given accessible.
1391
1392
        Arguments:
1393
        - indent: A string to prefix the output with
1394
        - includeApp: If True, include information about the app
1395
                      for this accessible.
1396
        """
1397
1398 1847
        if includeApp:
1399 1847
            if self.app:
1400 1847
                string = indent + "app.name=%-20s " \
1401 0
                         % self.app.accessibleNameToString()
1402
            else:
1403 0
                string = indent + "app=None "
1404
        else:
1405 0
            string = indent
1406
1407 1847
        string += "name=%s role='%s' state='%s' relations='%s'" \
1408
                  % (self.accessibleNameToString(),
1409
                     self.role,
1410
                     self.getStateString(),
1411
                     self.getRelationString())
1412
1413 1835
        return string
1414
1415
########################################################################
1416
#                                                                      #
1417
# Testing functions.                                                   #
1418
#                                                                      #
1419
########################################################################
1420
1421 1
def __printTopObject(child):
1422 0
    parent = child
1423 0
    while parent:
1424 0
        if not parent.parent:
1425 0
            print "RAW TOP:", parent.name, parent.role
1426 0
        parent = parent.parent
1427 0
    if (child.parent):
1428 0
        accessible = Accessible.makeAccessible(child)
1429 0
        app = accessible.app
1430 0
        print "ACC TOP:", app.name, app.role
1431
1432 1
def __printDesktops():
1433 0
    registry = Registry().registry
1434 0
    print "There are %d desktops" % registry.getDesktopCount()
1435 0
    for i in range(0,registry.getDesktopCount()):
1436 0
        desktop = registry.getDesktop(i)
1437 0
        print "  Desktop %d (name=%s) has %d apps" \
1438
              % (i, desktop.name, desktop.childCount)
1439 0
        for j in range(0, desktop.childCount):
1440 0
            app = desktop.getChildAtIndex(j)
1441 0
            print "    App %d: name=%s role=%s" \
1442
                  % (j, app.name, app.getRoleName())
1443
1444 1
def __notifyEvent(event):
1445 0
        print event.type, event.source.name, \
1446
              event.detail1, event.detail2,  \
1447
              event.any_data
1448 0
        __printTopObject(event.source)
1449 0
        if not event.source.parent:
1450 0
            print "NO PARENT:", event.source.name, event.source.role
1451
1452 1
def __notifyKeystroke(event):
1453 0
    print "keystroke type=%d hw_code=%d modifiers=%d event_string=(%s) " \
1454
          "is_text=%s" \
1455
          % (event.type, event.hw_code, event.modifiers, event.event_string,
1456
             event.is_text)
1457 0
    if event.event_string == "F12":
1458 0
        __shutdownAndExit(None, None)
1459 0
    return False
1460
1461 1
def __shutdownAndExit(signum, frame):
1462 0
    Registry().stop()
1463 0
    print "Goodbye."
1464
1465 1
def __test():
1466 0
    eventTypes = [
1467
        "focus:",
1468
        "mouse:rel",
1469
        "mouse:button",
1470
        "mouse:abs",
1471
        "keyboard:modifiers",
1472
        "object:property-change",
1473
        "object:property-change:accessible-name",
1474
        "object:property-change:accessible-description",
1475
        "object:property-change:accessible-parent",
1476
        "object:state-changed",
1477
        "object:state-changed:focused",
1478
        "object:selection-changed",
1479
        "object:children-changed"
1480
        "object:active-descendant-changed"
1481
        "object:visible-data-changed"
1482
        "object:text-selection-changed",
1483
        "object:text-caret-moved",
1484
        "object:text-changed",
1485
        "object:column-inserted",
1486
        "object:row-inserted",
1487
        "object:column-reordered",
1488
        "object:row-reordered",
1489
        "object:column-deleted",
1490
        "object:row-deleted",
1491
        "object:model-changed",
1492
        "object:link-selected",
1493
        "object:bounds-changed",
1494
        "window:minimize",
1495
        "window:maximize",
1496
        "window:restore",
1497
        "window:activate",
1498
        "window:create",
1499
        "window:deactivate",
1500
        "window:close",
1501
        "window:lower",
1502
        "window:raise",
1503
        "window:resize",
1504
        "window:shade",
1505
        "window:unshade",
1506
        "object:property-change:accessible-table-summary",
1507
        "object:property-change:accessible-table-row-header",
1508
        "object:property-change:accessible-table-column-header",
1509
        "object:property-change:accessible-table-summary",
1510
        "object:property-change:accessible-table-row-description",
1511
        "object:property-change:accessible-table-column-description",
1512
        "object:test",
1513
        "window:restyle",
1514
        "window:desktop-create",
1515
        "window:desktop-destroy"
1516
    ]
1517
1518 0
    __printDesktops()
1519
1520 0
    registry = Registry()
1521 0
    for eventType in eventTypes:
1522 0
        registry.registerEventListener(__notifyEvent, eventType)
1523 0
    registry.registerKeystrokeListeners(__notifyKeystroke)
1524 0
    registry.start()
1525
1526 1
if __name__ == "__main__":
1527 0
    signal.signal(signal.SIGINT, __shutdownAndExit)
1528 0
    signal.signal(signal.SIGQUIT, __shutdownAndExit)
1529 0
    __test()