2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 import java
.util
.ArrayList
;
21 import javax
.swing
.event
.TreeModelEvent
;
22 import javax
.swing
.tree
.TreePath
;
24 import com
.sun
.star
.accessibility
.XAccessible
;
25 import com
.sun
.star
.accessibility
.XAccessibleContext
;
26 import com
.sun
.star
.accessibility
.XAccessibleEventBroadcaster
;
27 import com
.sun
.star
.accessibility
.XAccessibleEventListener
;
28 import com
.sun
.star
.uno
.UnoRuntime
;
30 public class AccessibilityTreeModel
31 extends AccessibilityTreeModelBase
33 private boolean mbVerbose
= false;
35 public AccessibilityTreeModel (AccessibleTreeNode aRoot
)
37 // create default node (unless we have a 'proper' node)
40 maNodeMap
= new NodeMap();
42 mxListener
= new QueuedListener(new EventListener (this));
50 /** Lock the tree. While the tree is locked, events from the outside are
51 not processed. Lock the tree when you change its internal structure.
58 /** Unlock the tree. After unlocking the tree as many times as locking
59 it, a treeStructureChange event is sent to the event listeners.
61 If not null and treeStructureChange events are thrown then this
62 node is used as root of the modified subtree.
64 public void unlock (AccessibleTreeNode aNodeHint
)
68 fireTreeStructureChanged (
69 new TreeModelEvent (this,
70 new TreePath (aNodeHint
.createPath())));
75 public synchronized void setRoot (AccessibleTreeNode aRoot
)
77 if (getRoot() == null)
78 super.setRoot (aRoot
);
82 maNodeMap
.ForEach (new NodeMapCallback () {
84 public void Apply (AccTreeNode aNode
)
87 maCanvas
.removeNode (aNode
);
88 removeAccListener (aNode
);
105 /** Delegate the request to the parent and then register listeners at
106 the child and add the child to the canvas.
109 public Object
getChild (Object aParent
, int nIndex
)
111 AccessibleTreeNode aChild
= (AccessibleTreeNode
)super.getChild (aParent
, nIndex
);
114 System
.out
.println ("getChild: child not found");
116 // Keep translation table up-to-date.
123 public Object
getChildNoCreate (Object aParent
, int nIndex
)
125 AccessibleTreeNode aChild
= (AccessibleTreeNode
)super.getChildNoCreate (aParent
, nIndex
);
133 /** Remove a node (and all children) from the tree model.
135 private boolean removeChild (AccessibleTreeNode aNode
)
141 System
.out
.println ("can't remove null node");
146 // depth-first removal of children
147 while (aNode
.getChildCount() > 0) {
148 if ( ! removeChild (aNode
.getChildNoCreate (0)))
152 // Remove node from its parent.
153 AccessibleTreeNode aParent
= aNode
.getParent();
156 int nIndex
= aParent
.indexOf(aNode
);
157 aParent
.removeChild (nIndex
);
160 maNodeMap
.RemoveNode (aNode
);
165 System
.out
.println ("caught exception while removing child "
166 + aNode
+ " : " + e
);
167 e
.printStackTrace ();
173 public void removeNode (XAccessibleContext xNode
)
177 AccessibleTreeNode aNode
= maNodeMap
.GetNode (xNode
);
178 AccessibleTreeNode aRootNode
= (AccessibleTreeNode
)getRoot();
179 TreeModelEvent aEvent
= createEvent (aRootNode
, aNode
);
182 System
.out
.println (aNode
);
183 fireTreeNodesRemoved (aEvent
);
189 /** Add add a new child to a parent.
191 Returns the new or existing representation of the specified
194 private AccessibleTreeNode
addChild (AccTreeNode aParentNode
, XAccessible xNewChild
)
196 AccessibleTreeNode aChildNode
= null;
199 // First make sure that the accessible object does not already have
201 aChildNode
= maNodeMap
.GetNode(xNewChild
);
202 if (aChildNode
== null)
203 aChildNode
= aParentNode
.addAccessibleChild (xNewChild
);
205 System
.out
.println ("node already present");
209 System
.out
.println ("caught exception while adding child "
210 + xNewChild
+ " to parent " + aParentNode
+ ": " + e
);
211 e
.printStackTrace ();
216 public void addChild (XAccessibleContext xParent
, XAccessible xChild
)
218 AccessibleTreeNode aParentNode
= maNodeMap
.GetNode (xParent
);
219 if (aParentNode
instanceof AccTreeNode
)
221 AccessibleTreeNode aChild
= addChild ((AccTreeNode
)aParentNode
, xChild
);
222 if (addNode (aChild
))
224 if (maCanvas
!= null)
225 maCanvas
.updateNode ((AccTreeNode
)aParentNode
);
227 // A call to fireTreeNodesInserted for xNew
228 // should be sufficient but at least the
229 // StringNode object that contains the number of
230 // children also changes and we do not know its
231 // index relative to its parent. Therefore the
232 // more expensive fireTreeStructureChanged is
234 fireTreeNodesInserted (createChildUpdateEvent (xParent
));
235 updateNode (xParent
, AccessibleTreeHandler
.class);
242 /** Add the child node to the internal tree structure.
244 The node to insert into the internal tree structure.
246 private boolean addNode (AccessibleTreeNode aNode
)
248 boolean bRet
= false;
251 if ( ! maNodeMap
.ValueIsMember (aNode
))
253 if (aNode
instanceof AccTreeNode
)
255 AccTreeNode aChild
= (AccTreeNode
)aNode
;
256 XAccessibleContext xChild
= aChild
.getContext();
257 registerAccListener (aChild
);
258 if (maCanvas
!= null)
259 maCanvas
.addNode (aChild
);
260 maNodeMap
.InsertNode (xChild
, aChild
);
268 System
.out
.println ("caught exception while adding node "
270 e
.printStackTrace ();
278 /** create path to node, suitable for TreeModelEvent constructor
279 * @see javax.swing.event.TreeModelEvent#TreeModelEvent
281 private Object
[] createPath (AccessibleTreeNode aNode
)
283 ArrayList
<AccessibleTreeNode
> aPath
= new ArrayList
<AccessibleTreeNode
>();
284 aNode
.createPath (aPath
);
285 return aPath
.toArray();
289 // listeners (and helper methods)
291 // We are registered with listeners as soon as objects are in the
292 // tree cache, and we should get removed as soon as they are out.
295 private void fireTreeNodesChanged(TreeModelEvent e
)
297 for(int i
= 0; i
< maTMListeners
.size(); i
++)
299 maTMListeners
.get(i
).treeNodesChanged(e
);
303 protected void fireTreeNodesInserted(final TreeModelEvent e
)
305 for(int i
= 0; i
< maTMListeners
.size(); i
++)
307 maTMListeners
.get(i
).treeNodesInserted(e
);
311 private void fireTreeNodesRemoved(final TreeModelEvent e
)
313 for(int i
= 0; i
< maTMListeners
.size(); i
++)
315 maTMListeners
.get(i
).treeNodesRemoved(e
);
319 private void fireTreeStructureChanged(final TreeModelEvent e
)
321 for(int i
= 0; i
< maTMListeners
.size(); i
++)
323 maTMListeners
.get(i
).treeStructureChanged(e
);
327 /** Create a TreeModelEvent object that informs listeners that one child
328 has been removed from or inserted into its parent.
330 private TreeModelEvent
createChildUpdateEvent (XAccessibleContext xParent
)
332 AccessibleTreeNode aParentNode
= maNodeMap
.GetNode (xParent
);
333 return createEvent (aParentNode
, xParent
);
336 private TreeModelEvent
createEvent (AccessibleTreeNode aParentNode
, XAccessibleContext xChild
)
338 AccessibleTreeNode aChildNode
= null;
340 aChildNode
= maNodeMap
.GetNode (xChild
);
341 return createEvent (aParentNode
, aChildNode
);
346 protected TreeModelEvent
createEvent (
347 AccessibleTreeNode aParentNode
,
348 AccessibleTreeNode aChildNode
)
350 Object
[] aPathToParent
= createPath (aParentNode
);
352 int nIndexInParent
= -1;
353 if (aChildNode
!= null)
354 nIndexInParent
= aParentNode
.indexOf (aChildNode
);
356 System
.out
.println (aChildNode
+ " " + nIndexInParent
);
358 if (nIndexInParent
== -1)
359 // This event may be passed only to treeStructureChanged of the listeners.
360 return new TreeModelEvent (this,
363 // General purpose event for removing or inserting known nodes.
364 return new TreeModelEvent (this,
366 new int[] {nIndexInParent
},
367 new Object
[] {aChildNode
} );
373 /** Create a TreeModelEvent that indicates changes at those children of
374 the specified node with the specified indices.
376 private TreeModelEvent
createChangeEvent (AccTreeNode aNode
, java
.util
.List
<Integer
> aChildIndices
)
378 // Build a list of child objects that are indicated by the given indices.
379 int nCount
= aChildIndices
.size();
380 Object aChildObjects
[] = new Object
[nCount
];
381 int nChildIndices
[] = new int[nCount
];
382 for (int i
=0; i
<nCount
; i
++)
384 int nIndex
= aChildIndices
.get(i
);
385 aChildObjects
[i
] = aNode
.getChild (nIndex
);
386 nChildIndices
[i
] = nIndex
;
389 return new TreeModelEvent (this,
397 private XAccessibleEventBroadcaster
getBroadcaster (Object aObject
)
399 if (aObject
instanceof AccTreeNode
)
400 return UnoRuntime
.queryInterface (
401 XAccessibleEventBroadcaster
.class, ((AccTreeNode
)aObject
).getContext());
406 private void registerAccListener( Object aObject
)
408 // register this as listener for XAccessibleEventBroadcaster
410 XAccessibleEventBroadcaster xBroadcaster
= getBroadcaster( aObject
);
411 if (xBroadcaster
!= null)
413 xBroadcaster
.addAccessibleEventListener( mxListener
);
417 private void removeAccListener( Object aObject
)
419 XAccessibleEventBroadcaster xBroadcaster
= getBroadcaster( aObject
);
420 if (xBroadcaster
!= null)
422 xBroadcaster
.removeAccessibleEventListener( mxListener
);
428 public void setCanvas (Canvas aCanvas
)
433 public Canvas
getCanvas ()
438 public void updateNode (XAccessibleContext xSource
, java
.lang
.Class class1
)
440 updateNode (xSource
, class1
,null);
443 /** Get a list of children of the node associated with xSource that are
444 affected by the given handlers. Fire events that these children may
445 have changed in the tree view. Update the canvas representation of
448 public AccTreeNode
updateNode (XAccessibleContext xSource
,
449 java
.lang
.Class class1
, java
.lang
.Class
<AccessibleExtendedComponentHandler
> class2
)
451 AccessibleTreeNode aTreeNode
= maNodeMap
.GetNode (xSource
);
452 AccTreeNode aNode
= null;
454 System
.out
.println ("updating node " + xSource
+ " " + aTreeNode
);
455 if (aTreeNode
instanceof AccTreeNode
)
457 aNode
= (AccTreeNode
) aTreeNode
;
458 // Get list of affected children.
459 java
.util
.List
<Integer
> aChildIndices
= aNode
.updateChildren (
461 // Fire events that these children may have changed.
462 fireTreeNodesChanged (
463 createChangeEvent (aNode
, aChildIndices
));
468 /** The listener to be registered with the accessible objects.
469 * Could be set to 'this' for same-thread event delivery, or to an
470 * instance of QueuedListener for multi-threaded delivery. May
471 * not be changed, since this would trip the
472 * register/removeAccListener logic. */
473 private final XAccessibleEventListener mxListener
;
475 // Map to translate from accessible object to corresponding tree node.
476 private final NodeMap maNodeMap
;
478 // If the lock count is higher then zero, then no events are processed.
479 private int mnLockCount
;
481 private Canvas maCanvas
;