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 seperate 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