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