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