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