2008-08-29 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / pyatspi / event.py
1 #Copyright (C) 2008 Codethink Ltd
2
3 #This library is free software; you can redistribute it and/or
4 #modify it under the terms of the GNU Lesser General Public
5 #License version 2 as published by the Free Software Foundation.
6
7 #This program is distributed in the hope that it will be useful,
8 #but WITHOUT ANY WARRANTY; without even the implied warranty of
9 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 #GNU General Public License for more details.
11 #You should have received a copy of the GNU Lesser General Public License
12 #along with this program; if not, write to the Free Software
13 #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14
15 import interfaces
16 from factory import create_accessible, add_accessible_class
17 from accessible import BoundingBox
18
19 __all__ = [
20                 "Event",
21                 "EventType",
22                 "event_type_to_signal_reciever",
23           ]
24
25 #------------------------------------------------------------------------------
26
27 _interface_to_klass = {
28                 "org.freedesktop.atspi.Event.Object":"object",
29                 "org.freedesktop.atspi.Event.Window":"window",
30                 "org.freedesktop.atspi.Event.Mouse":"mouse",
31                 "org.freedesktop.atspi.Event.Terminal":"terminal",
32                 "org.freedesktop.atspi.Event.Document":"document",
33                 "org.freedesktop.atspi.Event.Focus":"focus",
34                 }
35
36 _klass_to_interface = {
37                 "object":"org.freedesktop.atspi.Event.Object",
38                 "window":"org.freedesktop.atspi.Event.Window",
39                 "mouse":"org.freedesktop.atspi.Event.Mouse",
40                 "terminal":"org.freedesktop.atspi.Event.Terminal",
41                 "document":"org.freedesktop.atspi.Event.Document",
42                 "focus":"org.freedesktop.atspi.Event.Focus",
43                 }
44
45 #------------------------------------------------------------------------------
46
47 class _ELessList(list):
48         def __getitem__(self, index):
49                 try:
50                         return list.__getitem__(self, index)
51                 except IndexError:
52                         return None
53
54 class EventType(str):
55         """
56         Wraps the AT-SPI event type string so its components can be accessed 
57         individually as klass (can't use the keyword class), major, minor, and detail 
58         (klass_major_minor_detail).
59         
60         @note: All attributes of an instance of this class should be considered 
61                 public readable as it is acting a a struct.
62         @ivar klass: Most general event type identifier (object, window, mouse, etc.)
63         @type klass: string
64         @ivar major: Second level event type description
65         @type major: string
66         @ivar minor: Third level event type description
67         @type minor: string
68         @ivar detail: Lowest level event type description
69         @type detail: string
70         @ivar name: Full, unparsed event name as received from AT-SPI
71         @type name: string
72         @cvar format: Names of the event string components
73         @type format: 4-tuple of string
74         """
75
76         _SEPARATOR = ':'
77
78         def __init__(self, name):
79                 """             
80                 Parses the full AT-SPI event name into its components
81                 (klass:major:minor:detail). If the provided event name is an integer
82                 instead of a string, then the event is really a device event.
83                 
84                 @param name: Full AT-SPI event name
85                 @type name: string
86                 @raise AttributeError: When the given event name is not a valid string 
87                 """
88                 stripped = name.strip(self._SEPARATOR)
89                 separated = stripped.split(self._SEPARATOR, 3) 
90                 self._separated = _ELessList(separated)
91
92                 self.klass = self._separated[0]
93                 self.major = self._separated[1]
94                 self.minor = self._separated[2]
95                 self.detail = self._separated[3]
96
97                 self._name = ":".join(separated)
98
99         def is_subtype(self, event_type):
100                 """
101                 Determines if the passed event type is a subtype
102                 of this event.
103                 """
104                 if event_type.klass and event_type.klass !=  self.klass:
105                         return False
106                 else:
107                         if event_type.major and event_type.major != self.major:
108                                 return False
109                         else:
110                                 if event_type.minor and event_type.minor != self.minor:
111                                         return False
112                 return True
113
114         @property
115         def name(self):
116                 return self._name
117
118         @property
119         def value(self):
120                 return self._name
121
122 #------------------------------------------------------------------------------
123
124 def event_type_to_signal_reciever(bus, cache, event_handler, event_type):
125         kwargs = {
126                         'sender_keyword':'sender',
127                         'interface_keyword':'interface',
128                         'member_keyword':'member',
129                         'path_keyword':'path',
130                  }
131         if event_type.klass:
132                 kwargs['dbus_interface'] = _klass_to_interface[event_type.klass]
133         if event_type.major:
134                 kwargs['signal_name'] = event_type.major
135         if event_type.minor:
136                 kwargs['arg0'] = event_type.minor
137
138         def handler_wrapper(minor, detail1, detail2, any_data, 
139                             sender=None, interface=None, member=None, path=None):
140                 event = Event(cache, path, sender, interface, member, (minor, detail1, detail2, any_data))
141                 return event_handler(event)
142
143         return bus.add_signal_receiver(handler_wrapper, **kwargs) 
144
145 #------------------------------------------------------------------------------
146
147 class Event(object):
148         """
149         Wraps an AT-SPI event with a more Pythonic interface managing exceptions,
150         the differences in any_data across versions, and the reference counting of
151         accessibles provided with the event.
152         
153         @note: All unmarked attributes of this class should be considered public
154                 readable and writable as the class is acting as a record object.
155                 
156         @ivar type: The type of the AT-SPI event
157         @type type: L{EventType}
158         @ivar detail1: First AT-SPI event parameter
159         @type detail1: integer
160         @ivar detail2: Second AT-SPI event parameter
161         @type detail2: integer
162         @ivar any_data: Extra AT-SPI data payload
163         @type any_data: object
164         @ivar host_application: Application owning the event source
165         @type host_application: Accessibility.Application
166         @ivar source_name: Name of the event source at the time of event dispatch
167         @type source_name: string
168         @ivar source_role: Role of the event source at the time of event dispatch
169         @type source_role: Accessibility.Role
170         @ivar source: Source of the event
171         @type source: Accessibility.Accessible
172         """
173         def __init__(self, cache, source_path, source_application, interface, name, event):
174                 """
175                 Extracts information from the provided event. If the event is a "normal" 
176                 event, pulls the detail1, detail2, any_data, and source values out of the
177                 given object and stores it in this object. If the event is a device event,
178                 key ID is stored in detail1, scan code is stored in detail2, key name, 
179                 key modifiers (e.g. ALT, CTRL, etc.), is text flag, and timestamp are 
180                 stored as a 4-tuple in any_data, and source is None (since key events are
181                 global).
182
183                 @param event: Event from an AT-SPI callback
184                 @type event: Accessibility.Event or Accessibility.DeviceEvent
185                 """
186                 self._cache = cache
187                 self._source_path = source_path
188                 self._source_application = source_application
189
190                 self._source = None
191                 self._application = None
192
193                 self._klass = _interface_to_klass[interface]
194                 # The replace is neccessary as '-' not legal as signal name
195                 # so translated on the server side.
196                 self._major = name.replace('_', '-')
197                 self._minor = event[0]
198                 self.type = EventType(':'.join([self._klass, self._major, self._minor]))
199                 self.detail1 = event[1]
200                 self.detail2 = event[2]
201
202                 data = event[3]
203                 if name == "object_bounds_changed":
204                         self.any_data = BoundingBox(*data)
205                 else:
206                         self.any_data = data
207
208         @property
209         def host_application(self):
210                 if not self._application:
211                         application_root = self._cache[self._source_application]._get_root()
212                         return create_accessible(self._cache,
213                                                  self._source_application,
214                                                  application_root,
215                                                  interfaces.ATSPI_APPLICATION,
216                                                  connection=self._cache._connection)
217                 return self._application
218
219         @property
220         def source(self):
221                 if not self._source:
222                         self._source = create_accessible(self._cache,
223                                                          self._source_application,
224                                                          self._source_path,
225                                                          interfaces.ATSPI_ACCESSIBLE,
226                                                          connection=self._cache._connection)
227                 return self._source
228
229         @property
230         def source_name(self):
231                 return source.name
232
233         @property
234         def source_role(self):
235                 return source.getRole()
236
237         def __str__(self):
238                 """
239                 Builds a human readable representation of the event including event type,
240                 parameters, and source info.
241
242                 @return: Event description
243                 @rtype: string
244                 """
245                 return '%s(%s, %s, %s)\n\tsource: %s\n\thost_application: %s' % \
246                                          (self.type, self.detail1, self.detail2, self.any_data,
247                                                 self.source, self.host_application)
248
249 #END----------------------------------------------------------------------------