xref: /trunk/main/toolkit/test/accessibility/AccessibilityTreeModel.java (revision 5221cf3a962813b63a7cbddd28fd7b281ed549ef)
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 
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 
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     */
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     */
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     */
93     public void FireTreeStructureChanged (AccessibleTreeNode aNode)
94     {
95     }
96 
97 
98 
99 
100 
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     */
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 
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     */
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 
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     */
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 
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     */
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      */
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 
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 
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 
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 
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 
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     */
360     public TreeModelEvent createEvent (XAccessibleContext xParent, XAccessible xChild)
361     {
362         AccessibleTreeNode aParentNode = maNodeMap.GetNode (xParent);
363         return createEvent (aParentNode, xParent);
364     }
365 
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 
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     */
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     {
433         public void run()
434         {
435             for(int i = 0; i < maTMListeners.size(); i++)
436             {
437                 fire( (TreeModelListener)maTMListeners.get(i) );
438             }
439         }
440 
441         protected void fire( TreeModelListener l) { }
442     }
443 
444 
445 
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 
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 
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 
477     public void setCanvas (Canvas aCanvas)
478     {
479         maCanvas = aCanvas;
480     }
481 
482     public Canvas getCanvas ()
483     {
484         return maCanvas;
485     }
486 
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     */
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