Major cleanup of Utils class.
[trakem2.git] / ini / trakem2 / tree / DNDTree.java
blobaa0ffea40e52f6f010a8a9c41bb37941e0168f97
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005,2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.tree;
26 import ini.trakem2.Project;
27 import ini.trakem2.display.Display;
28 import ini.trakem2.display.Displayable;
29 import ini.trakem2.display.Layer;
30 import ini.trakem2.display.LayerSet;
31 import ini.trakem2.utils.Utils;
32 import ini.trakem2.utils.Dispatcher;
33 import ini.trakem2.utils.IJError;
34 import ini.trakem2.persistence.DBObject;
35 import ini.trakem2.tree.ProjectTree;
37 import javax.swing.JTree;
38 import java.util.*;
39 import java.awt.*;
40 import java.awt.event.KeyEvent;
41 import java.awt.event.KeyListener;
42 import javax.swing.*;
43 import javax.swing.tree.*;
44 import javax.swing.event.TreeExpansionEvent;
45 import javax.swing.event.TreeExpansionListener;
46 import java.awt.dnd.*;
48 /** A JTree which has a built-in drag and drop feature.
50 * Adapted from freely available code by DeuDeu from http://forum.java.sun.com/thread.jspa?threadID=296255&start=0&tstart=0
52 public class DNDTree extends JTree implements TreeExpansionListener, KeyListener {
54 Insets autoscrollInsets = new Insets(20, 20, 20, 20); // insets
56 DefaultTreeTransferHandler dtth = null;
58 protected final Dispatcher dispatcher = new Dispatcher();
60 public DNDTree(final Project project, final DefaultMutableTreeNode root, final Color background) {
61 this(project, root);
62 this.setScrollsOnExpand(true);
63 this.addKeyListener(this);
64 if (null != background) {
65 final DefaultTreeCellRenderer renderer = new NodeRenderer(background); // new DefaultTreeCellRenderer();
66 renderer.setBackground(background);
67 renderer.setBackgroundNonSelectionColor(background);
68 // I hate swing, I really do. And java has no closures, no macros, and reflection is nearly as verbose as the code below!
69 SwingUtilities.invokeLater(new Runnable() { public void run() {
70 DNDTree.this.setCellRenderer(renderer);
71 }});
72 SwingUtilities.invokeLater(new Runnable() { public void run() {
73 DNDTree.this.setBackground(background);
74 }});
78 /** Extends the DefaultTreeCellRenderer to paint the nodes that contain Thing objects present in the current layer with a distinctive orange background; also, attribute nodes are painted with a different icon. */
79 private class NodeRenderer extends DefaultTreeCellRenderer {
81 // this is a crude hack that needs much cleanup and proper break down to subclasses.
83 final Color bg;
84 final Color active_displ_color = new Color(1.0f, 1.0f, 0.0f, 0.5f);
85 final Color front_layer_color = new Color(1.0f, 1.0f, 0.4f, 0.5f);
87 NodeRenderer(final Color bg) {
88 this.bg = bg;
91 /** Override to set a yellow background color to elements that belong to the currently displayed layer. The JTree nodes are painted as a JLabel that is transparent (no background, it has a setOpque(true) by default), so the code is insane.*/
92 public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
93 final JLabel label = (JLabel)super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
94 label.setText(label.getText().replace('_', ' ')); // just for display
95 if (value.getClass() == DefaultMutableTreeNode.class) {
96 final Object obb = ((DefaultMutableTreeNode)value).getUserObject();
97 final Class clazz = obb.getClass();
98 if (ProjectThing.class == clazz) { // must be, but checking ...
99 final Object ob = ((ProjectThing)obb).getObject();
100 if (ob.getClass().getSuperclass() == Displayable.class) {
101 final Displayable displ = (Displayable)ob;
102 final Layer layer = Display.getFrontLayer();
103 if (null != layer && (displ == Display.getFront().getActive() || layer.contains(displ))) {
104 label.setOpaque(true); //this label
105 label.setBackground(active_displ_color); // this label
106 //Utils.log(" -- setting background");
107 } else {
108 label.setOpaque(false); //this label
109 label.setBackground(bg);
110 //Utils.log(" not contained ");
112 } else {
113 label.setOpaque(false); //this label
114 label.setBackground(bg);
115 //Utils.log("ob is " + ob);
117 } else if (clazz == LayerThing.class) {
118 final Object ob = ((LayerThing)obb).getObject();
119 final Layer layer = Display.getFrontLayer();
120 if (ob == layer) {
121 label.setOpaque(true); //this label
122 label.setBackground(front_layer_color); // this label
123 } else if (ob.getClass() == LayerSet.class && null != layer && layer.contains((Displayable)ob)) {
124 label.setOpaque(true); //this label
125 label.setBackground(active_displ_color); // this label
126 } else {
127 label.setOpaque(false); //this label
128 label.setBackground(bg);
130 } else {
131 label.setOpaque(false); //this label
132 label.setBackground(bg);
133 //Utils.log("obb is " + obb);
135 } else {
136 label.setOpaque(false);
137 label.setBackground(bg);
138 //Utils.log("value is " + value);
140 return label;
143 /** Override to show tooptip text as well. */
144 public void setText(final String text) {
145 super.setText(text);
146 setToolTipText(text); // TODO doesn't work ??
150 public DNDTree(Project project, DefaultMutableTreeNode root) {
151 setAutoscrolls(true);
152 DefaultTreeModel treemodel = new DefaultTreeModel(root);
153 setModel(treemodel);
154 setRootVisible(true);
155 setShowsRootHandles(false);//to show the root icon
156 getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); //set single selection for the Tree
157 setEditable(true);
158 //DNDTree.expandAllNodes(this, root);
159 // so weird this instance below does not need to be kept anywhere: where is Java storing it?
160 dtth = new DefaultTreeTransferHandler(project, this, DnDConstants.ACTION_COPY_OR_MOVE);
163 public void autoscroll(Point cursorLocation) {
164 Insets insets = getAutoscrollInsets();
165 Rectangle outer = getVisibleRect();
166 Rectangle inner = new Rectangle(outer.x+insets.left, outer.y+insets.top, outer.width-(insets.left+insets.right), outer.height-(insets.top+insets.bottom));
167 if (!inner.contains(cursorLocation)) {
168 Rectangle scrollRect = new Rectangle(cursorLocation.x-insets.left, cursorLocation.y-insets.top, insets.left+insets.right, insets.top+insets.bottom);
169 scrollRectToVisible(scrollRect);
173 public Insets getAutoscrollInsets() {
174 return autoscrollInsets;
178 public static DefaultMutableTreeNode makeDeepCopy(DefaultMutableTreeNode node) {
179 DefaultMutableTreeNode copy = new DefaultMutableTreeNode(node.getUserObject());
180 for (Enumeration e = node.children(); e.hasMoreElements();) {
181 copy.add(makeDeepCopy((DefaultMutableTreeNode)e.nextElement()));
183 return copy;
187 /** Expand all nodes recursively starting at the given node. It checks whether the node is a non-leaf first. Adapted quite literally from: http://www.koders.com/java/fid4BF9016D39E1A9EEDBB7875D3D2E1FE4DA9F726D.aspx a GPL TreeTool.java by Dirk Moebius (or so it says). */
188 static public void expandAllNodes(final JTree tree, final TreePath path) {
189 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
190 final TreeModel tree_model = tree.getModel();
191 if (tree_model.isLeaf(node)) return;
192 tree.expandPath(path);
193 final int n_children = tree_model.getChildCount(node);
194 for (int i=0; i<n_children; i++) {
195 expandAllNodes(tree, path.pathByAddingChild(tree_model.getChild(node, i)));
199 static public boolean expandNode(final DNDTree tree, final DefaultMutableTreeNode node) {
200 final TreeModel tree_model = tree.getModel();
201 if (tree_model.isLeaf(node)) return false;
202 tree.expandPath(new TreePath(node.getPath()));
203 tree.updateUILater();
204 return true;
207 /** Convenient method.*/
208 static public void expandAllNodes(DNDTree tree, DefaultMutableTreeNode root_node) {
209 expandAllNodes(tree, new TreePath(root_node.getPath()));
210 tree.updateUILater();
213 static public DefaultMutableTreeNode makeNode(Thing thing) {
214 return makeNode(thing, false);
216 /** Returns a DefaultMutableTreeNode with all its children. If this is called on a root node, it will fill in the whole tree. The Attribute nodes are only added it their value is non-null, and at the top of the list. */ //This method is designed as functional programming.
217 static public DefaultMutableTreeNode makeNode(Thing thing, boolean childless_nested) {
218 //make a new node
219 DefaultMutableTreeNode node = new DefaultMutableTreeNode(thing);
220 // add attributes and children only if nested are allowed (for ProjectThing)
222 if (childless_nested) {
223 // check if the given thing has a parent (or parent of parent, etc.) of the same type as itself (in which case the node will be returned as is, and thus added childless and attributeless)
224 Thing parent = thing.getParent();
225 String type = thing.getType();
226 while (null != parent) {
227 if (type.equals(parent.getType())) {
228 // finish!
229 return node;
231 parent = parent.getParent();
235 // add the attributes first, only for ProjectThing or TemplateThing instances (not LayerThing)
236 if (thing instanceof ProjectThing || thing instanceof TemplateThing) {
237 HashMap ht_attributes = thing.getAttributes();
238 if (null != ht_attributes) {
239 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
240 Map.Entry entry = (Map.Entry)it.next();
241 String key = (String)entry.getKey();
242 if (key.equals("id") || key.equals("title") || key.equals("index") || key.equals("expanded")) {
243 // ignore: the id is internal, and the title is shown in the node itself. The index is ignored.
244 continue;
246 DefaultMutableTreeNode attr_node = new DefaultMutableTreeNode(entry.getValue());
247 node.add(attr_node);
251 //fill in with children
252 ArrayList al_children = thing.getChildren();
253 if (null == al_children) return node; // end
254 Iterator it = al_children.iterator();
255 while (it.hasNext()) {
256 Thing child = (Thing)it.next();
257 node.add(makeNode(child, childless_nested)); // recursive call
259 return node;
262 /** Find the node in the tree that contains the Thing of the given id.*/
263 /* // EQUALS Project.find(long id)
264 static public DefaultMutableTreeNode findNode(final long thing_id, final JTree tree) {
265 // find which node contains the thing_ob
266 DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getModel().getRoot();
267 if (((DBObject)node.getUserObject()).getId() == thing_id) return node; // the root itself
268 Enumeration e = node.depthFirstEnumeration();
269 while (e.hasMoreElements()) {
270 node = (DefaultMutableTreeNode)e.nextElement();
271 if (((DBObject)node.getUserObject()).getId() == thing_id) {
272 //gotcha
273 return node;
276 return null;
280 /** Find the node in the tree that contains the given Thing.*/
281 static public DefaultMutableTreeNode findNode(final Object thing_ob, final JTree tree) {
282 if (null != thing_ob) {
283 // find which node contains the thing_ob
284 DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getModel().getRoot();
285 if (node.getUserObject().equals(thing_ob)) return node; // the root itself
286 final Enumeration e = node.depthFirstEnumeration();
287 while (e.hasMoreElements()) {
288 node = (DefaultMutableTreeNode)e.nextElement();
289 if (node.getUserObject().equals(thing_ob)) {
290 //gotcha
291 return node;
295 return null;
297 // TODO this could be improved by checking while searching for nodes, not first getting all then checking.
299 /** Find the node in the tree that contains a Thing which contains the given project_ob. */
300 static public DefaultMutableTreeNode findNode2(final Object project_ob, final JTree tree) {
301 if (null != project_ob) {
302 DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getModel().getRoot();
303 // check if it's the root itself
304 Object o = node.getUserObject();
305 if (null != o && o instanceof Thing && project_ob.equals(((Thing)o).getObject())) {
306 return node; // the root itself
308 Enumeration e = node.depthFirstEnumeration();
309 while (e.hasMoreElements()) {
310 node = (DefaultMutableTreeNode)e.nextElement();
311 o = node.getUserObject();
312 if (null != o && o instanceof Thing && project_ob.equals(((Thing)o).getObject())) {
313 //gotcha
314 return node;
318 return null;
321 /** Deselects whatever node is selected in the tree, and tries to select the one that contains the given object. */
322 static public void selectNode(final Object ob, final JTree tree) {
323 if (null == ob) {
324 Utils.log2("DNDTree.selectNode: null ob?");
325 return;
327 // deselect whatever is selected
328 tree.setSelectionPath(null);
329 // check first:
330 if (null == ob) return;
331 final Runnable run = new Runnable() {
332 public void run() {
333 final DefaultMutableTreeNode node = DNDTree.findNode(ob, tree);
334 if (null != node) {
335 final TreePath path = new TreePath(node.getPath());
336 try {
337 tree.scrollPathToVisible(path); // involves repaint, so must be set through invokeAndWait. Why it doesn't do so automatically is beyond me.
338 } catch (Exception e) {
339 Utils.log2("Error in DNDTree.selectNode tree.scrollPathToVisible(path). Java is buggy, see for yourself: " + e);
341 tree.setSelectionPath(path);
342 } else {
343 // Not found. But also occurs when adding a new profile/pipe/ball, because it is called 'setActive' on before adding it to the project tree.
344 //Utils.log("DNDTree.selectNode: not found for ob: " + ob);
347 new Thread() {
348 public void run() {
349 SwingUtilities.invokeLater(run);
351 }.start(); // can't even do it from the EventDispatchThread! Pitiful!
354 public void destroy() {
355 this.dtth.destroy();
356 this.dtth = null;
357 this.dispatcher.quit();
360 /** Overriding to fix synchronization issues: the path changes while the multithreaded swing attempts to repaint it, so we "invoke later". Hilarious. */
361 public void updateUILater() {
362 final DNDTree tree = this;
363 final Runnable updater = new Runnable() {
364 public void run() {
365 //try { Thread.sleep(200); } catch (InterruptedException ie) {}
366 try {
367 tree.updateUI();
368 } catch (Exception e) {
369 IJError.print(e);
373 //javax.swing.SwingUtilities.invokeLater(updater); // generates random lock ups at start up
374 new Thread() {
375 public void run() {
376 // avoiding "can't call invokeAndWait from the EventDispatch thread" error
377 try {
378 javax.swing.SwingUtilities.invokeLater(updater);
379 } catch (Exception e) {
380 Utils.log2("ERROR: " + e);
383 }.start();
386 /** Rebuilds the entire tree, starting at the root Thing object. */
387 public void rebuild() {
388 rebuild((DefaultMutableTreeNode)this.getModel().getRoot(), false);
389 updateUILater();
392 /** Rebuilds the entire tree, starting at the given Thing object. */
393 public void rebuild(final Thing thing) {
394 rebuild(DNDTree.findNode(thing, this), false);
395 updateUILater();
398 /** Rebuilds the entire tree, from the given node downward. */
399 public void rebuild(final DefaultMutableTreeNode node, final boolean repaint) {
400 if (null == node) return;
401 if (0 != node.getChildCount()) node.removeAllChildren();
402 final Thing thing = (Thing)node.getUserObject();
403 final ArrayList al_children = thing.getChildren();
404 if (null == al_children) return;
405 for (Iterator it = al_children.iterator(); it.hasNext(); ) {
406 Thing child = (Thing)it.next();
407 DefaultMutableTreeNode childnode = new DefaultMutableTreeNode(child);
408 node.add(childnode);
409 rebuild(childnode, repaint);
411 if (repaint) updateUILater();
414 /** Rebuilds the part of the tree under the given node, one level deep only, for reordering purposes. */
415 public void updateList(Thing thing) {
416 updateList(DNDTree.findNode(thing, this));
419 /** Rebuilds the part of the tree under the given node, one level deep only, for reordering purposes. */
420 public void updateList(DefaultMutableTreeNode node) {
421 if (null == node) return;
422 Thing thing = (Thing)node.getUserObject();
423 // store scrolling position for restoring purposes
424 Component c = this.getParent();
425 Point point = null;
426 if (c instanceof JScrollPane) {
427 point = ((JScrollPane)c).getViewport().getViewPosition();
429 // collect all current nodes
430 HashMap ht = new HashMap();
431 for (Enumeration e = node.children(); e.hasMoreElements(); ) {
432 DefaultMutableTreeNode child_node = (DefaultMutableTreeNode)e.nextElement();
433 ht.put(child_node.getUserObject(), child_node);
435 // clear node
436 node.removeAllChildren();
437 // re-add nodes in the order present in the contained Thing
438 for (Iterator it = thing.getChildren().iterator(); it.hasNext(); ) {
439 Object ob_thing = it.next();
440 Object ob = ht.remove(ob_thing);
441 if (null == ob) {
442 Utils.log2("Adding missing node for " + ob_thing);
443 node.add(new DefaultMutableTreeNode(ob_thing));
444 continue;
446 node.add((DefaultMutableTreeNode)ob);
448 // consistency check: that all nodes have been re-added
449 if (0 != ht.size()) {
450 Utils.log2("WARNING DNDTree.updateList: did not end up adding this nodes:");
451 for (Iterator it = ht.keySet().iterator(); it.hasNext(); ) {
452 Utils.log2(it.next().toString());
455 this.updateUILater();
456 // restore viewport position
457 if (null != point) {
458 ((JScrollPane)c).getViewport().setViewPosition(point);
462 public DefaultMutableTreeNode getRootNode() {
463 return (DefaultMutableTreeNode)this.getModel().getRoot();
466 /** Does not incur in firing a TreeExpansion event, and affects the node only, not any of its parents. */
467 public void setExpandedSilently(final Thing thing, final boolean b) {
468 DefaultMutableTreeNode node = findNode(thing, this);
469 if (null == node) return;
470 setExpandedSilently(node, b);
472 public void setExpandedSilently(final DefaultMutableTreeNode node, final boolean b) {
473 try {
474 java.lang.reflect.Field f = JTree.class.getDeclaredField("expandedState");
475 f.setAccessible(true);
476 Hashtable ht = (Hashtable)f.get(this);
477 ht.put(new TreePath(node.getPath()), new Boolean(b)); // this queries directly the expandedState transient private HashMap of the JTree
478 } catch (Exception e) {
479 Utils.log2("ERROR: " + e); // no IJError, potentially lots of text printed in failed applets
483 /** Check if there is a node holding the given Thing, and whether such node is expanded. */
484 public boolean isExpanded(final Thing thing) {
485 DefaultMutableTreeNode node = findNode(thing, this);
486 if (null == node) return false;
487 return isExpanded(node);
490 public boolean isExpanded(final DefaultMutableTreeNode node) {
491 try {
492 java.lang.reflect.Field f = JTree.class.getDeclaredField("expandedState");
493 f.setAccessible(true);
494 Hashtable ht = (Hashtable)f.get(this);
495 return Boolean.TRUE.equals(ht.get(new TreePath(node.getPath()))); // this queries directly the expandedState transient private HashMap of the JTree
496 } catch (Exception e) {
497 Utils.log2("ERROR: " + e); // no IJError, potentially lots of text printed in failed applets
498 return false;
501 /* // Java's idiotic API confuses isExpanded with isVisible, making them equal! The JTree.isVisible() check should not be done within the JTree.isExpanded() method!
502 DefaultMutableTreeNode node = findNode(thing, this);
503 Utils.log2("node is " + node);
504 if (null == node) return false;
505 TreePath path = new TreePath(node.getPath());
506 //return this.isExpanded(new TreePath(node.getPath())); // the API is ludicrous: isExpanded, isCollapsed and isVisible return a value relative to whether the node is visible, not its specific expanded state.
507 Utils.log("contains: " + hs_expanded_paths.contains(path));
508 //return hs_expanded_paths.contains(node.getPath());
509 for (Iterator it = hs_expanded_paths.iterator(); it.hasNext(); ) {
510 if (path.equals(it.next())) return true;
512 Utils.log2("thing not expanded: " + thing);
513 return false;
517 //private HashSet hs_expanded_paths = new HashSet();
519 /** Sense node expansion events (the method name is missleading). */
520 public void treeCollapsed(TreeExpansionEvent tee) {
521 TreePath path = tee.getPath();
522 //hs_expanded_paths.remove(path);
523 updateInDatabase(path);
524 //Utils.log2("collapsed " + path);
526 public void treeExpanded(TreeExpansionEvent tee) {
527 TreePath path = tee.getPath();
528 //hs_expanded_paths.add(path);
529 updateInDatabase(path);
530 //Utils.log2("expanded " + path);
532 private void updateInDatabase(final TreePath path) {
533 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
534 ProjectThing thing = (ProjectThing)node.getUserObject(); // the Thing
535 thing.updateInDatabase(new StringBuffer("expanded='").append(isExpanded(thing)).append('\'').toString());
538 public String getInfo() {
539 DefaultMutableTreeNode node = (DefaultMutableTreeNode)this.getModel().getRoot();
540 int n_basic = 0, n_abstract = 0;
541 for (Enumeration e = node.depthFirstEnumeration(); e.hasMoreElements(); ) {
542 DefaultMutableTreeNode child = (DefaultMutableTreeNode)e.nextElement();
543 Object ob = child.getUserObject();
544 if (ob instanceof Thing && Project.isBasicType(((Thing)ob).getType())) n_basic++;
545 else n_abstract++;
547 return this.getClass().getName() + ": \n\tAbstract nodes: " + n_abstract + "\n\tBasic nodes: " + n_basic + "\n";
549 // TODO: all these non-DND methods should go into an abstract child class that would be super of the trees proper
551 public final DefaultMutableTreeNode getRoot() {
552 return (DefaultMutableTreeNode)this.getModel().getRoot();
555 /** Appends at the end of the parent_node child list, and waits until the tree's UI is updated. */
556 protected DefaultMutableTreeNode addChild(final Thing child, final DefaultMutableTreeNode parent_node) {
557 try {
558 final DefaultMutableTreeNode node_child = new DefaultMutableTreeNode(child);
559 ((DefaultTreeModel)getModel()).insertNodeInto(node_child, parent_node, parent_node.getChildCount());
560 try { DNDTree.this.updateUI(); } catch (Exception e) { IJError.print(e, true); }
561 return node_child;
562 } catch (Exception e) { IJError.print(e, true); }
563 return null;
566 /** Will add only those for which a node doesn't exist already. */
567 public void addLeafs(final java.util.List<Thing> leafs) {
568 javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() {
569 for (final Thing th : leafs) {
570 // find parent node
571 final DefaultMutableTreeNode parent = DNDTree.findNode(th.getParent(), DNDTree.this);
572 if (null == parent) {
573 Utils.log("Ignoring node " + th + " : null parent!");
574 continue;
576 // see if it exists already as a child of that node
577 boolean exists = false;
578 if (parent.getChildCount() > 0) {
579 final Enumeration e = parent.children();
580 while (e.hasMoreElements()) {
581 DefaultMutableTreeNode child = (DefaultMutableTreeNode)e.nextElement();
582 if (child.getUserObject().equals(th)) {
583 exists = true;
584 break;
588 // otherwise add!
589 if (!exists) addChild(th, parent);
591 }});
594 protected boolean removeNode(DefaultMutableTreeNode node) {
595 if (null == node) return false;
596 ((DefaultTreeModel)this.getModel()).removeNodeFromParent(node);
597 this.updateUILater();
598 return true;
601 /** Shallow copy of the tree: returns a clone of the root node and cloned children, recursively, with all Thing cloned as well, but the Thing object is the same. */
602 public Thing duplicate(final HashMap<Thing,Boolean> expanded_state) {
603 DefaultMutableTreeNode root_node = (DefaultMutableTreeNode) this.getModel().getRoot();
604 // Descend both the root_copy tree and the root_node tree, and build shallow copies of Thing with same expanded state
605 return duplicate(root_node, expanded_state);
608 /** Returns the copy of the node's Thing. */
609 private Thing duplicate(final DefaultMutableTreeNode node, final HashMap<Thing,Boolean> expanded_state) {
610 Thing thing = (Thing) node.getUserObject();
611 Thing copy = thing.shallowCopy();
612 if (null != expanded_state) {
613 expanded_state.put(copy, isExpanded(node));
615 final Enumeration e = node.children();
616 while (e.hasMoreElements()) {
617 DefaultMutableTreeNode child = (DefaultMutableTreeNode) e.nextElement();
618 copy.addChild(duplicate(child, expanded_state));
620 return copy;
623 /** For restoring purposes from an undo step. */
624 public void set(final Thing root, final HashMap<Thing,Boolean> expanded_state) {
625 // rebuild all nodes, restore their expansion state.
626 DefaultMutableTreeNode root_node = (DefaultMutableTreeNode) this.getModel().getRoot();
627 root_node.removeAllChildren();
628 set(root_node, root, expanded_state);
629 updateUILater();
632 /** Recursive */
633 private void set(final DefaultMutableTreeNode root, final Thing root_thing, final HashMap<Thing,Boolean> expanded_state) {
634 root.setUserObject(root_thing);
635 final ArrayList<Thing> al_children = root_thing.getChildren();
636 if (null != al_children) {
637 for (final Thing thing : al_children) {
638 DefaultMutableTreeNode child = new DefaultMutableTreeNode(thing);
639 root.add(child);
640 set(child, thing, expanded_state);
643 if (null != expanded_state) {
644 final Boolean b = expanded_state.get(root_thing);
645 if (null != b) setExpandedSilently(root, b.booleanValue());
649 public void keyPressed(final KeyEvent ke) {
650 dispatcher.exec(new Runnable() { public void run() {
651 if (!ke.getSource().equals(DNDTree.this) || !Project.getInstance(DNDTree.this).isInputEnabled()) {
652 ke.consume();
653 return;
655 int key_code = ke.getKeyCode();
656 switch (key_code) {
657 case KeyEvent.VK_S:
658 Project p = Project.getInstance(DNDTree.this);
659 p.getLoader().save(p);
660 ke.consume();
661 break;
663 }});
665 public void keyReleased(KeyEvent ke) {}
666 public void keyTyped(KeyEvent ke) {}