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 import javax.swing.event.TreeModelEvent; 23 import javax.swing.event.TreeModelListener; 24 import javax.swing.tree.TreePath; 25 26 27 import java.util.Vector; 28 import java.util.HashMap; 29 30 import com.sun.star.accessibility.*; 31 32 import com.sun.star.uno.UnoRuntime; 33 import com.sun.star.uno.XInterface; 34 import com.sun.star.uno.Any; 35 import com.sun.star.lang.EventObject; 36 import com.sun.star.lang.XServiceInfo; 37 import com.sun.star.lang.XServiceName; 38 39 public class AccessibilityTreeModel 40 extends AccessibilityTreeModelBase 41 { 42 public boolean mbVerbose = false; 43 AccessibilityTreeModel(AccessibleTreeNode aRoot)44 public AccessibilityTreeModel (AccessibleTreeNode aRoot) 45 { 46 // create default node (unless we have a 'proper' node) 47 if( ! (aRoot instanceof AccessibleTreeNode) ) 48 aRoot = new StringNode ("Root", null); 49 setRoot (aRoot); 50 51 maNodeMap = new NodeMap(); 52 53 maEventListener = new EventListener (this); 54 mxListener = new QueuedListener (maEventListener); 55 } 56 clear()57 public void clear () 58 { 59 maNodeMap.Clear(); 60 } 61 62 /** Lock the tree. While the tree is locked, events from the outside are 63 not processed. Lock the tree when you change its internal structure. 64 */ lock()65 public void lock () 66 { 67 mnLockCount += 1; 68 } 69 70 /** Unlock the tree. After unlocking the tree as many times as locking 71 it, a treeStructureChange event is sent to the event listeners. 72 @param aNodeHint 73 If not null and treeStructureChange events are thrown then this 74 node is used as root of the modified subtree. 75 */ unlock(AccessibleTreeNode aNodeHint)76 public void unlock (AccessibleTreeNode aNodeHint) 77 { 78 mnLockCount -= 1; 79 if (mnLockCount == 0) 80 fireTreeStructureChanged ( 81 new TreeModelEvent (this, 82 new TreePath (aNodeHint.createPath()))); 83 } 84 85 86 87 88 /** Inform all listeners (especially the renderer) of a change of the 89 tree's structure. 90 @param aNode This node specifies the sub tree in which all changes 91 take place. 92 */ FireTreeStructureChanged(AccessibleTreeNode aNode)93 public void FireTreeStructureChanged (AccessibleTreeNode aNode) 94 { 95 } 96 97 98 99 100 setRoot(AccessibleTreeNode aRoot)101 public synchronized void setRoot (AccessibleTreeNode aRoot) 102 { 103 if (getRoot() == null) 104 super.setRoot (aRoot); 105 else 106 { 107 lock (); 108 maNodeMap.ForEach (new NodeMapCallback () { 109 public void Apply (AccTreeNode aNode) 110 { 111 if (maCanvas != null) 112 maCanvas.removeNode (aNode); 113 removeAccListener ((AccTreeNode)aNode); 114 } 115 }); 116 maNodeMap.Clear (); 117 118 setRoot (aRoot); 119 unlock (aRoot); 120 } 121 } 122 123 124 // 125 // child management: 126 // 127 128 129 130 /** Delegate the request to the parent and then register listeners at 131 the child and add the child to the canvas. 132 */ getChild(Object aParent, int nIndex)133 public Object getChild (Object aParent, int nIndex) 134 { 135 AccessibleTreeNode aChild = (AccessibleTreeNode)super.getChild (aParent, nIndex); 136 137 if (aChild == null) 138 System.out.println ("getChild: child not found"); 139 else 140 // Keep translation table up-to-date. 141 addNode (aChild); 142 143 return aChild; 144 } 145 getChildNoCreate(Object aParent, int nIndex)146 public Object getChildNoCreate (Object aParent, int nIndex) 147 { 148 AccessibleTreeNode aChild = (AccessibleTreeNode)super.getChildNoCreate (aParent, nIndex); 149 150 return aChild; 151 } 152 153 154 155 156 /** Remove a node (and all children) from the tree model. 157 */ removeChild(AccessibleTreeNode aNode)158 protected boolean removeChild (AccessibleTreeNode aNode) 159 { 160 try 161 { 162 if( aNode == null ) 163 { 164 System.out.println ("can't remove null node"); 165 return false; 166 } 167 else 168 { 169 // depth-first removal of children 170 while (aNode.getChildCount() > 0) 171 if ( ! removeChild (aNode.getChildNoCreate (0))) 172 break; 173 174 // Remove node from its parent. 175 AccessibleTreeNode aParent = aNode.getParent(); 176 if (aParent != null) 177 { 178 int nIndex = aParent.indexOf(aNode); 179 aParent.removeChild (nIndex); 180 } 181 182 maNodeMap.RemoveNode (aNode); 183 } 184 } 185 catch (Exception e) 186 { 187 System.out.println ("caught exception while removing child " 188 + aNode + " : " + e); 189 e.printStackTrace (); 190 return false; 191 } 192 return true; 193 } 194 removeNode(XAccessibleContext xNode)195 public void removeNode (XAccessibleContext xNode) 196 { 197 if (xNode != null) 198 { 199 AccessibleTreeNode aNode = maNodeMap.GetNode (xNode); 200 AccessibleTreeNode aRootNode = (AccessibleTreeNode)getRoot(); 201 TreeModelEvent aEvent = createEvent (aRootNode, aNode); 202 removeChild (aNode); 203 if (mbVerbose) 204 System.out.println (aNode); 205 fireTreeNodesRemoved (aEvent); 206 maCanvas.repaint (); 207 } 208 } 209 210 211 /** Add add a new child to a parent. 212 @return 213 Returns the new or existing representation of the specified 214 accessible object. 215 */ addChild(AccTreeNode aParentNode, XAccessible xNewChild)216 protected AccessibleTreeNode addChild (AccTreeNode aParentNode, XAccessible xNewChild) 217 { 218 AccessibleTreeNode aChildNode = null; 219 try 220 { 221 boolean bRet = false; 222 223 // First make sure that the accessible object does not already have 224 // a representation. 225 aChildNode = maNodeMap.GetNode(xNewChild); 226 if (aChildNode == null) 227 aChildNode = aParentNode.addAccessibleChild (xNewChild); 228 else 229 System.out.println ("node already present"); 230 } 231 catch (Exception e) 232 { 233 System.out.println ("caught exception while adding child " 234 + xNewChild + " to parent " + aParentNode + ": " + e); 235 e.printStackTrace (); 236 } 237 return aChildNode; 238 } 239 addChild(XAccessibleContext xParent, XAccessible xChild)240 public void addChild (XAccessibleContext xParent, XAccessible xChild) 241 { 242 AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); 243 if (aParentNode instanceof AccTreeNode) 244 { 245 AccessibleTreeNode aChild = addChild ((AccTreeNode)aParentNode, xChild); 246 if (addNode (aChild)) 247 { 248 if (maCanvas != null) 249 maCanvas.updateNode ((AccTreeNode)aParentNode); 250 251 // A call to fireTreeNodesInserted for xNew 252 // should be sufficient but at least the 253 // StringNode object that contains the number of 254 // children also changes and we do not know its 255 // index relative to its parent. Therefore the 256 // more expensive fireTreeStructureChanged is 257 // necessary. 258 fireTreeNodesInserted (createEvent (xParent, xChild)); 259 updateNode (xParent, AccessibleTreeHandler.class); 260 } 261 maCanvas.repaint (); 262 } 263 } 264 265 266 /** Add the child node to the internal tree structure. 267 @param aNode 268 The node to insert into the internal tree structure. 269 */ addNode(AccessibleTreeNode aNode)270 protected boolean addNode (AccessibleTreeNode aNode) 271 { 272 boolean bRet = false; 273 try 274 { 275 if ( ! maNodeMap.ValueIsMember (aNode)) 276 { 277 if (aNode instanceof AccTreeNode) 278 { 279 AccTreeNode aChild = (AccTreeNode)aNode; 280 XAccessibleContext xChild = aChild.getContext(); 281 registerAccListener (aChild); 282 if (maCanvas != null) 283 maCanvas.addNode (aChild); 284 maNodeMap.InsertNode (xChild, aChild); 285 } 286 bRet = true; 287 } 288 289 } 290 catch (Exception e) 291 { 292 System.out.println ("caught exception while adding node " 293 + aNode + ": " + e); 294 e.printStackTrace (); 295 } 296 return bRet; 297 } 298 299 300 301 302 /** create path to node, suitable for TreeModelEvent constructor 303 * @see javax.swing.event.TreeModelEvent#TreeModelEvent 304 */ createPath(AccessibleTreeNode aNode)305 protected Object[] createPath (AccessibleTreeNode aNode) 306 { 307 Vector aPath = new Vector(); 308 aNode.createPath (aPath); 309 return aPath.toArray(); 310 } 311 312 // 313 // listeners (and helper methods) 314 // 315 // We are registered with listeners as soon as objects are in the 316 // tree cache, and we should get removed as soon as they are out. 317 // 318 fireTreeNodesChanged(TreeModelEvent e)319 protected void fireTreeNodesChanged(TreeModelEvent e) 320 { 321 for(int i = 0; i < maTMListeners.size(); i++) 322 { 323 ((TreeModelListener)maTMListeners.get(i)).treeNodesChanged(e); 324 } 325 } 326 fireTreeNodesInserted(final TreeModelEvent e)327 protected void fireTreeNodesInserted(final TreeModelEvent e) 328 { 329 for(int i = 0; i < maTMListeners.size(); i++) 330 { 331 ((TreeModelListener)maTMListeners.get(i)).treeNodesInserted(e); 332 } 333 } 334 fireTreeNodesRemoved(final TreeModelEvent e)335 protected void fireTreeNodesRemoved(final TreeModelEvent e) 336 { 337 for(int i = 0; i < maTMListeners.size(); i++) 338 { 339 ((TreeModelListener)maTMListeners.get(i)).treeNodesRemoved(e); 340 } 341 } 342 fireTreeStructureChanged(final TreeModelEvent e)343 protected void fireTreeStructureChanged(final TreeModelEvent e) 344 { 345 for(int i = 0; i < maTMListeners.size(); i++) 346 { 347 ((TreeModelListener)maTMListeners.get(i)).treeStructureChanged(e); 348 } 349 } 350 createEvent(XAccessibleContext xParent)351 protected TreeModelEvent createEvent (XAccessibleContext xParent) 352 { 353 AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); 354 return new TreeModelEvent (this, createPath (aParentNode)); 355 } 356 357 /** Create a TreeModelEvent object that informs listeners that one child 358 has been removed from or inserted into its parent. 359 */ createEvent(XAccessibleContext xParent, XAccessible xChild)360 public TreeModelEvent createEvent (XAccessibleContext xParent, XAccessible xChild) 361 { 362 AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent); 363 return createEvent (aParentNode, xParent); 364 } 365 createEvent(AccessibleTreeNode aParentNode, XAccessibleContext xChild)366 public TreeModelEvent createEvent (AccessibleTreeNode aParentNode, XAccessibleContext xChild) 367 { 368 AccessibleTreeNode aChildNode = null; 369 if (xChild != null) 370 aChildNode = maNodeMap.GetNode (xChild); 371 return createEvent (aParentNode, aChildNode); 372 } 373 374 375 createEvent( AccessibleTreeNode aParentNode, AccessibleTreeNode aChildNode)376 protected TreeModelEvent createEvent ( 377 AccessibleTreeNode aParentNode, 378 AccessibleTreeNode aChildNode) 379 { 380 Object[] aPathToParent = createPath (aParentNode); 381 382 int nIndexInParent = -1; 383 if (aChildNode != null) 384 nIndexInParent = aParentNode.indexOf (aChildNode); 385 if (mbVerbose) 386 System.out.println (aChildNode + " " + nIndexInParent); 387 388 if (nIndexInParent == -1) 389 // This event may be passed only to treeStructureChanged of the listeners. 390 return new TreeModelEvent (this, 391 aPathToParent); 392 else 393 // General purpose event for removing or inserting known nodes. 394 return new TreeModelEvent (this, 395 aPathToParent, 396 new int[] {nIndexInParent}, 397 new Object[] {aChildNode} ); 398 } 399 400 401 402 403 /** Create a TreeModelEvent that indicates changes at those children of 404 the specified node with the specified indices. 405 */ createChangeEvent(AccTreeNode aNode, Vector aChildIndices)406 protected TreeModelEvent createChangeEvent (AccTreeNode aNode, Vector aChildIndices) 407 { 408 // Build a list of child objects that are indicated by the given indices. 409 int nCount = aChildIndices.size(); 410 Object aChildObjects[] = new Object[nCount]; 411 int nChildIndices[] = new int[nCount]; 412 for (int i=0; i<nCount; i++) 413 { 414 int nIndex = ((Integer)aChildIndices.elementAt(i)).intValue(); 415 aChildObjects[i] = aNode.getChild (nIndex); 416 nChildIndices[i] = nIndex; 417 } 418 419 return new TreeModelEvent (this, 420 createPath(aNode), 421 nChildIndices, 422 aChildObjects); 423 } 424 425 426 427 /** 428 * broadcast a tree event in a separate Thread 429 * must override fire method 430 */ 431 class EventRunner implements Runnable 432 { run()433 public void run() 434 { 435 for(int i = 0; i < maTMListeners.size(); i++) 436 { 437 fire( (TreeModelListener)maTMListeners.get(i) ); 438 } 439 } 440 fire( TreeModelListener l)441 protected void fire( TreeModelListener l) { } 442 } 443 444 445 getBroadcaster(Object aObject)446 protected XAccessibleEventBroadcaster getBroadcaster (Object aObject) 447 { 448 if (aObject instanceof AccTreeNode) 449 return UnoRuntime.queryInterface ( 450 XAccessibleEventBroadcaster.class, ((AccTreeNode)aObject).getContext()); 451 else 452 return null; 453 } 454 registerAccListener( Object aObject )455 protected void registerAccListener( Object aObject ) 456 { 457 // register this as listener for XAccessibleEventBroadcaster 458 // implementations 459 XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); 460 if (xBroadcaster != null) 461 { 462 xBroadcaster.addEventListener( mxListener ); 463 } 464 } 465 removeAccListener( Object aObject )466 protected void removeAccListener( Object aObject ) 467 { 468 XAccessibleEventBroadcaster xBroadcaster = getBroadcaster( aObject ); 469 if (xBroadcaster != null) 470 { 471 xBroadcaster.removeEventListener( mxListener ); 472 } 473 } 474 475 476 setCanvas(Canvas aCanvas)477 public void setCanvas (Canvas aCanvas) 478 { 479 maCanvas = aCanvas; 480 } 481 getCanvas()482 public Canvas getCanvas () 483 { 484 return maCanvas; 485 } 486 updateNode(XAccessibleContext xSource, java.lang.Class class1)487 public void updateNode (XAccessibleContext xSource, java.lang.Class class1) 488 { 489 updateNode (xSource, class1,null); 490 } 491 492 /** Get a list of children of the node associated with xSource that are 493 affected by the given handlers. Fire events that these children may 494 have changed in the tree view. Update the canvas representation of 495 xSource. 496 */ updateNode(XAccessibleContext xSource, java.lang.Class class1, java.lang.Class class2)497 public AccTreeNode updateNode (XAccessibleContext xSource, 498 java.lang.Class class1, java.lang.Class class2) 499 { 500 AccessibleTreeNode aTreeNode = maNodeMap.GetNode (xSource); 501 AccTreeNode aNode = null; 502 if (mbVerbose) 503 System.out.println ("updating node " + xSource + " " + aTreeNode); 504 if (aTreeNode instanceof AccTreeNode) 505 { 506 aNode = (AccTreeNode) aTreeNode; 507 // Get list of affected children. 508 Vector aChildIndices = (aNode).updateChildren ( 509 class1, class2); 510 // Fire events that these children may have changed. 511 fireTreeNodesChanged ( 512 createChangeEvent (aNode, aChildIndices)); 513 } 514 return aNode; 515 } 516 517 /** The listener to be registered with the accessible objects. 518 * Could be set to 'this' for same-thread event delivery, or to an 519 * instance of QueuedListener for multi-threaded delivery. May 520 * not be changed, since this would trip the 521 * register/removeAccListener logic. */ 522 private final XAccessibleEventListener mxListener; 523 524 // Map to translate from accessible object to corresponding tree node. 525 private NodeMap maNodeMap; 526 527 // If the lock count is higher then zero, then no events are processed. 528 private int mnLockCount; 529 530 private Canvas maCanvas; 531 532 private EventListener maEventListener; 533 } 534