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 separate 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