xref: /AOO41X/main/toolkit/test/accessibility/AccessibilityTreeModel.java (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
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