1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 import java.util.Vector;
25 
26 import com.sun.star.uno.AnyConverter;
27 import com.sun.star.uno.UnoRuntime;
28 import com.sun.star.uno.Type;
29 import com.sun.star.accessibility.*;
30 
31 /** Handle all the events send from accessibility objects.  The events
32     denoting new or removed top windows are handled as well.
33 
34     It does not implement any listener interface as does the
35     EventListenerProxy class because it is interested only in a sub set of
36     the event types.
37 */
38 public class EventHandler
39 {
EventHandler()40     public EventHandler ()
41     {
42         mnTopWindowCount = 0;
43         maListenerProxy = new EventListenerProxy (this);
44         maConnectionTask = new ConnectionTask (maListenerProxy);
45         maObjectDisplays = new Vector ();
46     }
47 
addObjectDisplay(IAccessibleObjectDisplay aDisplay)48     public synchronized void addObjectDisplay (IAccessibleObjectDisplay aDisplay)
49     {
50         maObjectDisplays.add (aDisplay);
51     }
52 
53 
finalize()54     public void finalize ()
55     {
56         // When it is running then cancel the timer that tries to connect to
57         // the Office.
58         if (maConnectionTask != null)
59             maConnectionTask.cancel();
60     }
61 
62 
63 
disposing(com.sun.star.lang.EventObject aEvent)64     public void disposing (com.sun.star.lang.EventObject aEvent)
65     {
66         // Ignored: We are not holding references to accessibility objects.
67     }
68 
69 
70 
71 
72     /**  This method is called back when a new top level window has been opened.
73     */
windowOpened(XAccessible xAccessible)74     public void windowOpened (XAccessible xAccessible)
75     {
76         if (xAccessible != null)
77         {
78             // Update the counter of currently open top level windows
79             // observed by this object.
80             mnTopWindowCount += 1;
81 
82             XAccessibleContext xContext = xAccessible.getAccessibleContext();
83             if (xContext != null)
84             {
85                 MessageArea.println ("new top level window has accessible name "
86                     + xContext.getAccessibleName());
87 
88                 // Register at all accessible objects of the new window.
89                 new RegistrationThread (
90                     maListenerProxy,
91                     xContext,
92                     true,
93                     true);
94             }
95             else
96                 MessageArea.println ("new top level window is not accessible.");
97         }
98         else
99             MessageArea.println ("new top level window is not accessible.");
100     }
101 
102 
103 
104 
windowClosed(XAccessible xAccessible)105     public void windowClosed (XAccessible xAccessible)
106     {
107         mnTopWindowCount -= 1;
108         MessageArea.println ("window closed, " + mnTopWindowCount + " still open");
109         if (mnTopWindowCount == 0)
110         {
111             // This was the last window.  Wait for a new connection.
112             MessageArea.println ("lost connection to office");
113             new ConnectionTask (maListenerProxy);
114         }
115         if (xAccessible != null)
116             new RegistrationThread (
117                 maListenerProxy,
118                 xAccessible.getAccessibleContext(),
119                 false,
120                 true);
121     }
122 
123 
124 
125 
126     /** Print a message that the given object just received the focus.  Call
127         all accessible object diplays and tell them to update.
128     */
focusGained(XAccessibleContext xContext)129 	private synchronized void focusGained (XAccessibleContext xContext)
130     {
131         if (xContext != null)
132         {
133             MessageArea.println ("focusGained: " + xContext.getAccessibleName()
134                 + " with role "
135                 + NameProvider.getRoleName (xContext.getAccessibleRole()));
136 
137             // Tell the object displays to update their views.
138             for (int i=0; i<maObjectDisplays.size(); i++)
139             {
140                 IAccessibleObjectDisplay aDisplay =
141                     (IAccessibleObjectDisplay)maObjectDisplays.get(i);
142                 if (aDisplay != null)
143                     aDisplay.setAccessibleObject (xContext);
144             }
145 
146             // Remember the currently focused object.
147             mxFocusedObject = xContext;
148         }
149         else
150             MessageArea.println ("focusGained: null");
151     }
152 
153 
154 
155 
156     /** Print a message that the given object just lost the focus.  Call
157         all accessible object diplays and tell them to update.
158     */
focusLost(XAccessibleContext xContext)159 	private synchronized void focusLost (XAccessibleContext xContext)
160     {
161         if (xContext != null)
162         {
163             MessageArea.println ("focusLost: "
164                 + xContext.getAccessibleName()
165                 + " with role "
166                 + NameProvider.getRoleName (xContext.getAccessibleRole()));
167 
168             // Tell the object displays to update their views.
169             for (int i=0; i<maObjectDisplays.size(); i++)
170             {
171                 IAccessibleObjectDisplay aDisplay =
172                     (IAccessibleObjectDisplay)maObjectDisplays.get(i);
173                 if (aDisplay != null)
174                     aDisplay.setAccessibleObject (null);
175             }
176             mxFocusedObject = null;
177         }
178         else
179             MessageArea.println ("focusLost: null");
180     }
181 
182 
183 
184 
185     /** Handle a change of the caret position.  Ignore this on all objects
186         but the one currently focused.
187     */
handleCaretEvent(XAccessibleContext xContext, long nOldPosition, long nNewPosition)188     private void handleCaretEvent (XAccessibleContext xContext,
189         long nOldPosition, long nNewPosition)
190     {
191         if (xContext == mxFocusedObject)
192             MessageArea.println ("caret moved from " + nOldPosition + " to " + nNewPosition);
193     }
194 
195 
196 
197 
198     /** Print a message that a state has been changed.
199         @param xContext
200             The accessible context of the object whose state has changed.
201         @param nOldState
202             When not zero then this value describes a state that has been reset.
203         @param nNewValue
204             When not zero then this value describes a state that has been set.
205     */
handleStateChange(XAccessibleContext xContext, short nOldState, short nNewState)206     private void handleStateChange (XAccessibleContext xContext, short nOldState, short nNewState)
207     {
208         // Determine which state has changed and what is its new value.
209         short nState;
210         boolean aNewValue;
211         if (nOldState >= 0)
212         {
213             nState = nOldState;
214             aNewValue = false;
215         }
216         else
217         {
218             nState = nNewState;
219             aNewValue = true;
220         }
221 
222         // Print a message about the changed state.
223         MessageArea.print ("setting state " + NameProvider.getStateName(nState)
224             + " to " + aNewValue);
225         if (xContext != null)
226         {
227             MessageArea.println (" at " + xContext.getAccessibleName() + " with role "
228                 + NameProvider.getRoleName(xContext.getAccessibleRole()));
229         }
230         else
231             MessageArea.println (" at null");
232 
233         // Further handling of some states
234         switch (nState)
235         {
236             case AccessibleStateType.FOCUSED:
237                 if (aNewValue)
238                     focusGained (xContext);
239                 else
240                     focusLost (xContext);
241         }
242     }
243 
244 
245 
246 
247     /** Handle a child event that describes the creation of removal of a
248         single child.
249     */
handleChildEvent( XAccessibleContext aOldChild, XAccessibleContext aNewChild)250     private void handleChildEvent (
251         XAccessibleContext aOldChild,
252         XAccessibleContext aNewChild)
253     {
254         if (aOldChild != null)
255             // Remove event listener from the child and all of its descendants.
256             new RegistrationThread (maListenerProxy, aOldChild, false, false);
257         else if (aNewChild != null)
258             // Add event listener to the new child and all of its descendants.
259             new RegistrationThread (maListenerProxy, aNewChild, true, false);
260     }
261 
262 
263 
264 
265     /** Handle the change of some visible data of an object.
266     */
handleVisibleDataEvent(XAccessibleContext xContext)267     private void handleVisibleDataEvent (XAccessibleContext xContext)
268     {
269         // The given object may affect the visible appearance of the focused
270         // object even when the two are not identical when the given object
271         // is an ancestor of the focused object.
272         // In order to not check this we simply call an update on the
273         // focused object.
274         if (mxFocusedObject != null)
275             for (int i=0; i<maObjectDisplays.size(); i++)
276             {
277                 IAccessibleObjectDisplay aDisplay =
278                     (IAccessibleObjectDisplay)maObjectDisplays.get(i);
279                 if (aDisplay != null)
280                     aDisplay.updateAccessibleObject (mxFocusedObject);
281             }
282     }
283 
284 
285 
286 
287     /** Print some information about an event that is not handled by any
288         more specialized handler.
289     */
handleGenericEvent( int nEventId, Object aSource, Object aOldValue, Object aNewValue)290     private void handleGenericEvent (
291         int nEventId,
292         Object aSource,
293         Object aOldValue,
294         Object aNewValue)
295     {
296         // Print event to message area.
297         MessageArea.print ("received event "
298             + NameProvider.getEventName (nEventId) + " from ");
299         XAccessibleContext xContext = objectToContext (aSource);
300         if (xContext != null)
301             MessageArea.print (xContext.getAccessibleName());
302         else
303             MessageArea.print ("null");
304         MessageArea.println (" / "
305             + NameProvider.getRoleName(xContext.getAccessibleRole()));
306     }
307 
308 
309 
310     /** This is the main method for handling accessibility events.  It is
311         assumed that it is not called directly from the Office but from a
312         listener proxy that runs in a separate thread so that calls back to
313         the Office do not result in dead-locks.
314     */
notifyEvent(com.sun.star.accessibility.AccessibleEventObject aEvent)315     public void notifyEvent (com.sun.star.accessibility.AccessibleEventObject aEvent)
316     {
317         try // Guard against disposed objects.
318         {
319             switch (aEvent.EventId)
320             {
321                 case AccessibleEventId.CHILD:
322                     handleChildEvent (
323                         objectToContext (aEvent.OldValue),
324                         objectToContext (aEvent.NewValue));
325                     break;
326 
327                 case AccessibleEventId.STATE_CHANGED:
328                 {
329                     short nOldState = -1;
330                     short nNewState = -1;
331                     try
332                     {
333                         if (AnyConverter.isShort (aEvent.NewValue))
334                             nNewState = AnyConverter.toShort (aEvent.NewValue);
335                         if (AnyConverter.isShort (aEvent.OldValue))
336                             nOldState = AnyConverter.toShort (aEvent.OldValue);
337                     }
338                     catch (com.sun.star.lang.IllegalArgumentException e)
339                     {}
340                     handleStateChange (
341                         objectToContext (aEvent.Source),
342                         nOldState,
343                         nNewState);
344                 }
345                 break;
346 
347                 case AccessibleEventId.VISIBLE_DATA_CHANGED:
348                 case AccessibleEventId.BOUNDRECT_CHANGED:
349                     handleVisibleDataEvent (objectToContext (aEvent.Source));
350                     break;
351 
352                 case AccessibleEventId.CARET_CHANGED:
353                     try
354                     {
355                         handleCaretEvent (
356                             objectToContext (aEvent.Source),
357                             AnyConverter.toLong(aEvent.OldValue),
358                             AnyConverter.toLong(aEvent.NewValue));
359                     }
360                     catch (com.sun.star.lang.IllegalArgumentException e)
361                     {}
362                     break;
363 
364                 default:
365                     handleGenericEvent (aEvent.EventId,
366                         aEvent.Source, aEvent.OldValue, aEvent.NewValue);
367                     break;
368             }
369         }
370         catch (com.sun.star.lang.DisposedException e)
371         {}
372     }
373 
374 
375 
376 
377     /** Convert the given object into an accessible context.  The object is
378         interpreted as UNO Any and may contain either an XAccessible or
379         XAccessibleContext reference.
380         @return
381             The returned value is null when the given object can not be
382             converted to an XAccessibleContext reference.
383     */
objectToContext(Object aObject)384     private XAccessibleContext objectToContext (Object aObject)
385     {
386         XAccessibleContext xContext = null;
387         XAccessible xAccessible = null;
388         try
389         {
390             xAccessible = (XAccessible)AnyConverter.toObject(
391                 new Type(XAccessible.class), aObject);
392         }
393         catch (com.sun.star.lang.IllegalArgumentException e)
394         {}
395         if (xAccessible != null)
396             xContext = xAccessible.getAccessibleContext();
397         else
398             try
399             {
400                 xContext = (XAccessibleContext)AnyConverter.toObject(
401                     new Type(XAccessibleContext.class), aObject);
402             }
403             catch (com.sun.star.lang.IllegalArgumentException e)
404             {}
405         return xContext;
406     }
407 
408 
409 
410 
411     /** The proxy that runs in a seperate thread and allows to call back to
412         the Office without running into dead-locks.
413     */
414     private EventListenerProxy maListenerProxy;
415 
416     /** The currently focused object.  A value of null means that no object
417         has the focus.
418     */
419     private XAccessibleContext mxFocusedObject;
420 
421     /** Keep track of the currently open top windows to start a registration
422         loop when the last window (and the Office) is closed.
423     */
424     private long mnTopWindowCount;
425 
426     /** A list of objects that can display accessible objects in specific
427         ways such as showing a graphical representation or some textual
428         descriptions.
429     */
430     private Vector maObjectDisplays;
431 
432     /** The timer task that attempts in regular intervals to connect to a
433         running Office application.
434     */
435     private ConnectionTask maConnectionTask;
436 }
437