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