/** TrakEM2 plugin for ImageJ(C). Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt ) This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact Albert Cardona at acardona at ini.phys.ethz.ch Institute of Neuroinformatics, University of Zurich / ETH, Switzerland. **/ package ini.trakem2.tree; import ij.gui.GenericDialog; import ini.trakem2.ControlWindow; import ini.trakem2.Project; import ini.trakem2.display.Display; import ini.trakem2.display.Displayable; import ini.trakem2.display.Layer; import ini.trakem2.display.LayerSet; import ini.trakem2.persistence.DBObject; import ini.trakem2.utils.IJError; import ini.trakem2.utils.Search; import ini.trakem2.utils.Utils; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; public final class LayerTree extends DNDTree implements MouseListener, ActionListener { private static final long serialVersionUID = 1L; private DefaultMutableTreeNode selected_node = null; public LayerTree(Project project, LayerThing root) { super(project, DNDTree.makeNode(root), new Color(230, 235, 255)); // Color(200, 200, 255)); setEditable(false); addMouseListener(this); // enable multiple discontiguous selection this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); } /** Get a custom, context-sensitive popup menu for the selected node. */ private JPopupMenu getPopupMenu(DefaultMutableTreeNode node) { Object ob = node.getUserObject(); LayerThing thing = null; if (ob instanceof LayerThing) { thing = (LayerThing)ob; } else { return null; } // context-sensitive popup JMenuItem[] item = thing.getPopupItems(this); if (0 == item.length) return null; JPopupMenu popup = new JPopupMenu(); for (int i=0; i<item.length; i++) { if (null == item[i] || "" == item[i].getText()) popup.addSeparator(); else popup.add(item[i]); } return popup; } public void mousePressed(MouseEvent me) { Object source = me.getSource(); if (!source.equals(this) || !project.isInputEnabled()) { return; } // ignore if doing multiple selection if (!Utils.isPopupTrigger(me) && (me.isShiftDown() || (!ij.IJ.isMacOSX() && me.isControlDown()))) { return; } int x = me.getX(); int y = me.getY(); // check if there is a multiple selection TreePath[] paths = this.getSelectionPaths(); if (null != paths && paths.length > 1) { if (Utils.isPopupTrigger(me)) { // check that all items are of the same type String type_first = ((LayerThing)((DefaultMutableTreeNode)paths[0].getLastPathComponent()).getUserObject()).getType(); for (int i=1; i<paths.length; i++) { String type = ((LayerThing)((DefaultMutableTreeNode)paths[i].getLastPathComponent()).getUserObject()).getType(); if (!type.equals(type_first)) { Utils.showMessage("All selected items must be of the same type for operations on multiple items."); return; } } // prepare popup menu JPopupMenu popup = new JPopupMenu(); JMenuItem item = null; if (type_first.equals("layer")) { item = new JMenuItem("Reverse layer Z coords"); item.addActionListener(this); popup.add(item); item = new JMenuItem("Translate layers in Z..."); item.addActionListener(this); popup.add(item); item = new JMenuItem("Scale Z and thickness..."); item.addActionListener(this); popup.add(item); item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item); } if (popup.getSubElements().length > 0) { popup.show(this, x, y); } } // disable commands depending upon a single node being selected selected_node = null; return; } // find the node and set it selected TreePath path = getPathForLocation(x, y); if (null == path) { return; } setSelectionPath(path); selected_node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (2 == me.getClickCount() && !Utils.isPopupTrigger(me) && MouseEvent.BUTTON1 == me.getButton()) { // create a new Display LayerThing thing = (LayerThing)selected_node.getUserObject(); DBObject ob = (DBObject)thing.getObject(); if (thing.getType().toLowerCase().replace('_', ' ').equals("layer set") && null == ((LayerSet)ob).getParent()) { // the top level LayerSet return; } //new Display(ob.getProject(), thing.getType().toLowerCase().equals("layer") ? (Layer)ob : ((LayerSet)ob).getParent()); Display.createDisplay(ob.getProject(), thing.getType().toLowerCase().equals("layer") ? (Layer)ob : ((LayerSet)ob).getParent()); return; } else if (Utils.isPopupTrigger(me)) { JPopupMenu popup = getPopupMenu(selected_node); if (null == popup) return; popup.show(this, x, y); return; } } public void mouseDragged(MouseEvent me) { } public void mouseReleased(MouseEvent me) { } public void mouseEntered(MouseEvent me) { } public void mouseExited(MouseEvent me) { } public void mouseClicked(MouseEvent me) { } public void actionPerformed(ActionEvent ae) { try { String command = ae.getActionCommand(); // commands for multiple selections: TreePath[] paths = this.getSelectionPaths(); if (null != paths && paths.length > 1) { if (command.equals("Reverse layer Z coords")) { // check that all layers belong to the same layer set // just do it Layer[] layer = new Layer[paths.length]; LayerSet ls = null; for (int i=0; i<paths.length; i++) { layer[i] = (Layer) ((LayerThing)((DefaultMutableTreeNode)paths[i].getLastPathComponent()).getUserObject()).getObject(); if (null == ls) ls = layer[i].getParent(); else if (!ls.equals(layer[i].getParent())) { Utils.showMessage("To reverse, all layers must belong to the same layer set"); return; } } final ArrayList<Layer> al = new ArrayList<Layer>(); for (int i=0; i<layer.length; i++) al.add(layer[i]); ls.addLayerEditedStep(al); // ASSSUMING layers are already Z ordered! CHECK for (int i=0, j=layer.length-1; i<layer.length/2; i++, j--) { double z = layer[i].getZ(); layer[i].setZ(layer[j].getZ()); layer[j].setZ(z); } updateList(ls); ls.addLayerEditedStep(al); Display.updateLayerScroller(ls); } else if (command.equals("Translate layers in Z...")) { GenericDialog gd = ControlWindow.makeGenericDialog("Range"); gd.addMessage("Translate selected range in the Z axis:"); gd.addNumericField("by: ", 0, 4); gd.showDialog(); if (gd.wasCanceled()) return; // else, displace double dz = gd.getNextNumber(); if (Double.isNaN(dz)) { Utils.showMessage("Invalid number"); return; } HashSet<LayerSet> hs_parents = new HashSet<LayerSet>(); for (int i=0; i<paths.length; i++) { Layer layer = (Layer) ((LayerThing)((DefaultMutableTreeNode)paths[i].getLastPathComponent()).getUserObject()).getObject(); layer.setZ(layer.getZ() + dz); hs_parents.add(layer.getParent()); } for (LayerSet ls : hs_parents) { updateList(ls); } // now update all profile's Z ordering in the ProjectTree ProjectThing root_pt = project.getRootProjectThing(); for (final ProjectThing pt : root_pt.findChildrenOfType("profile_list")) { pt.fixZOrdering(); project.getProjectTree().updateList(pt); } project.getProjectTree().updateUILater(); //Display.updateLayerScroller((LayerSet)((DefaultMutableTreeNode)getModel().getRoot()).getUserObject()); } else if (command.equals("Delete...")) { if (!Utils.check("Really remove all selected layers?")) return; for (int i=0; i<paths.length; i++) { DefaultMutableTreeNode lnode = (DefaultMutableTreeNode)paths[i].getLastPathComponent(); LayerThing lt = (LayerThing)lnode.getUserObject(); Layer layer = (Layer)lt.getObject(); if (!layer.remove(false)) { Utils.showMessage("Could not delete layer " + layer); this.updateUILater(); return; } if (lt.remove(false)) { ((DefaultTreeModel)this.getModel()).removeNodeFromParent(lnode); } } this.updateUILater(); } else if (command.equals("Scale Z and thickness...")) { GenericDialog gd = new GenericDialog("Scale Z"); gd.addNumericField("scale: ", 1.0, 2); gd.showDialog(); double scale = gd.getNextNumber(); if (Double.isNaN(scale) || 0 == scale) { Utils.showMessage("Imvalid scaling factor: " + scale); return; } for (int i=0; i<paths.length; i++) { DefaultMutableTreeNode lnode = (DefaultMutableTreeNode)paths[i].getLastPathComponent(); LayerThing lt = (LayerThing)lnode.getUserObject(); Layer layer = (Layer)lt.getObject(); layer.setZ(layer.getZ() * scale); layer.setThickness(layer.getThickness() * scale); } this.updateUILater(); } else { Utils.showMessage("Don't know what to do with command " + command + " for multiple selected nodes"); } return; } // commands for single selection: if (null == selected_node) return; LayerThing thing = (LayerThing)selected_node.getUserObject(); LayerThing new_thing = null; TemplateThing tt = null; Object ob = null; int i_position = -1; if (command.startsWith("new ")) { String name = command.substring(4).toLowerCase(); if (name.equals("layer")) { // Create new Layer and add it to the selected node LayerSet set = (LayerSet)thing.getObject(); Layer new_layer = Layer.create(thing.getProject(), set); if (null == new_layer) return; tt = thing.getChildTemplate("layer"); ob = new_layer; Display.updateTitle(set); } else if (name.equals("layer set")) { // with space in the middle // Create a new LayerSet and add it in the middle Layer layer = (Layer)thing.getObject(); LayerSet new_set = layer.getParent().create(layer); if (null == new_set) return; layer.add(new_set); // add it at the end of the list tt = thing.getChildTemplate("layer set"); // with space, not underscore ob = new_set; i_position = selected_node.getChildCount(); Display.update(layer); } else { Utils.log("LayerTree.actionPerformed: don't know what to do with the command: " + command); return; } } else if (command.equals("many new layers...")) { LayerSet set = (LayerSet)thing.getObject(); List<Layer> layers = Layer.createMany(set.getProject(), set); // add them to the tree as LayerThing if (null == layers) return; for (Layer la : layers) { addLayer(set, la); // null layers will be skipped } Display.updateTitle(set); return; } else if (command.equals("Show")) { // create a new Display DBObject dbo = (DBObject)thing.getObject(); if (thing.getType().equals("layer_set") && null == ((LayerSet)dbo).getParent()) return; // the top level LayerSet Display.createDisplay(dbo.getProject(), thing.getType().equals("layer") ? (Layer)dbo : ((LayerSet)dbo).getParent()); return; } else if (command.equals("Show centered in Display")) { LayerSet ls = (LayerSet)thing.getObject(); Display.showCentered(ls.getParent(), ls, false, false); } else if (command.equals("Delete...")) { remove(true, thing, selected_node); return; } else if (command.equals("Import stack...")) { if (thing.getObject() instanceof LayerSet) { LayerSet set = (LayerSet)thing.getObject(); Layer layer = null; if (0 == set.getLayers().size()) { layer = Layer.create(set.getProject(), set); if (null == layer) return; tt = thing.getChildTemplate("Layer"); ob = layer; } else return; // click on a desired, existing layer. layer.getProject().getLoader().importStack(layer, null, true); } else if (thing.getObject() instanceof Layer) { Layer layer = (Layer)thing.getObject(); layer.getProject().getLoader().importStack(layer, null, true); return; } } else if (command.equals("Import grid...")) { if (thing.getObject() instanceof Layer) { Layer layer = (Layer)thing.getObject(); layer.getProject().getLoader().importGrid(layer); } } else if (command.equals("Import sequence as grid...")) { if (thing.getObject() instanceof Layer) { Layer layer = (Layer)thing.getObject(); layer.getProject().getLoader().importSequenceAsGrid(layer); } } else if (command.equals("Import from text file...")) { if (thing.getObject() instanceof Layer) { Layer layer = (Layer)thing.getObject(); layer.getProject().getLoader().importImages(layer); } } else if (command.equals("Resize LayerSet...")) { if (thing.getObject() instanceof LayerSet) { LayerSet ls = (LayerSet)thing.getObject(); ij.gui.GenericDialog gd = ControlWindow.makeGenericDialog("Resize LayerSet"); gd.addNumericField("new width: ", ls.getLayerWidth(), 3); gd.addNumericField("new height: ",ls.getLayerHeight(),3); gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[0]); gd.showDialog(); if (gd.wasCanceled()) return; float new_width = (float)gd.getNextNumber(), new_height = (float)gd.getNextNumber(); ls.setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects } } else if (command.equals("Autoresize LayerSet")) { if (thing.getObject() instanceof LayerSet) { LayerSet ls = (LayerSet)thing.getObject(); ls.setMinimumDimensions(); } } else if (command.equals("Adjust...")) { if (thing.getObject() instanceof Layer) { Layer layer= (Layer)thing.getObject(); ij.gui.GenericDialog gd = ControlWindow.makeGenericDialog("Adjust Layer"); gd.addNumericField("new z: ", layer.getZ(), 4); gd.addNumericField("new thickness: ",layer.getThickness(),4); gd.showDialog(); if (gd.wasCanceled()) return; double new_z = gd.getNextNumber(); layer.setThickness(gd.getNextNumber()); if (new_z != layer.getZ()) { layer.setZ(new_z); // move in the tree /* DefaultMutableTreeNode child = findNode(thing, this); DefaultMutableTreeNode parent = (DefaultMutableTreeNode)child.getParent(); parent.remove(child); // reinsert int n = parent.getChildCount(); int i = 0; for (; i < n; i++) { DefaultMutableTreeNode child_node = (DefaultMutableTreeNode)parent.getChildAt(i); LayerThing child_thing = (LayerThing)child_node.getUserObject(); if (!child_thing.getType().equals("Layer")) continue; double iz = ((Layer)child_thing.getObject()).getZ(); if (iz < new_z) continue; // else, add the layer here, after this one break; } ((DefaultTreeModel)this.getModel()).insertNodeInto(child, parent, i); */ // fix tree crappiness (empty slot ?!?) /* // the fix doesn't work. ARGH TODO Enumeration e = parent.children(); parent.removeAllChildren(); i = 0; while (e.hasMoreElements()) { //parent.add((DefaultMutableTreeNode)e.nextElement()); ((DefaultTreeModel)this.getModel()).insertNodeInto(child, parent, i); i++; }*/ // easier and correct: overkill updateList(layer.getParent()); // set selected DefaultMutableTreeNode child = findNode(thing, this); TreePath treePath = new TreePath(child.getPath()); this.scrollPathToVisible(treePath); this.setSelectionPath(treePath); } } return; } else if (command.equals("Rename...")) { GenericDialog gd = ControlWindow.makeGenericDialog("Rename"); gd.addStringField("new name: ", thing.getTitle()); gd.showDialog(); if (gd.wasCanceled()) return; project.getRootLayerSet().addUndoStep(new RenameThingStep(thing)); thing.setTitle(gd.getNextString()); project.getRootLayerSet().addUndoStep(new RenameThingStep(thing)); } else if (command.equals("Translate layers in Z...")) { /// TODO: this method should use multiple selections directly on the tree if (thing.getObject() instanceof LayerSet) { LayerSet ls = (LayerSet)thing.getObject(); ArrayList<Layer> al_layers = ls.getLayers(); String[] layer_names = new String[al_layers.size()]; for (int i=0; i<layer_names.length; i++) { layer_names[i] = ls.getProject().findLayerThing(al_layers.get(i)).toString(); } GenericDialog gd = ControlWindow.makeGenericDialog("Range"); gd.addMessage("Translate selected range in the Z axis:"); gd.addChoice("from: ", layer_names, layer_names[0]); gd.addChoice("to: ", layer_names, layer_names[layer_names.length-1]); gd.addNumericField("by: ", 0, 4); gd.showDialog(); if (gd.wasCanceled()) return; // else, displace double dz = gd.getNextNumber(); if (Double.isNaN(dz)) { Utils.showMessage("Invalid number"); return; } int i_start = gd.getNextChoiceIndex(); int i_end = gd.getNextChoiceIndex(); for (int i = i_start; i<=i_end; i++) { Layer layer = (Layer)al_layers.get(i); layer.setZ(layer.getZ() + dz); } // update node labels and position updateList(ls); } } else if (command.equals("Reverse layer Z coords...")) { /// TODO: this method should use multiple selections directly on the tree if (thing.getObject() instanceof LayerSet) { LayerSet ls = (LayerSet)thing.getObject(); ArrayList<Layer> al_layers = ls.getLayers(); String[] layer_names = new String[al_layers.size()]; for (int i=0; i<layer_names.length; i++) { layer_names[i] = ls.getProject().findLayerThing(al_layers.get(i)).toString(); } GenericDialog gd = ControlWindow.makeGenericDialog("Range"); gd.addMessage("Reverse Z coordinates of selected range:"); gd.addChoice("from: ", layer_names, layer_names[0]); gd.addChoice("to: ", layer_names, layer_names[layer_names.length-1]); gd.showDialog(); if (gd.wasCanceled()) return; int i_start = gd.getNextChoiceIndex(); int i_end = gd.getNextChoiceIndex(); for (int i = i_start, j=i_end; i<i_end/2; i++, j--) { Layer layer1 = (Layer)al_layers.get(i); double z1 = layer1.getZ(); Layer layer2 = (Layer)al_layers.get(j); layer1.setZ(layer2.getZ()); layer2.setZ(z1); } // update node labels and position updateList(ls); } } else if (command.equals("Search...")) { Search.showWindow(); } else if (command.equals("Reset layer Z and thickness")) { LayerSet ls = ((LayerSet)thing.getObject()); List<Layer> layers = ls.getLayers(); ls.addLayerEditedStep(layers); int i = 0; for (final Layer la : ls.getLayers()) { la.setZ(i++); la.setThickness(1); } ls.addLayerEditedStep(layers); } else { Utils.log("LayerTree.actionPerformed: don't know what to do with the command: " + command); return; } if (null == tt) return; new_thing = new LayerThing(tt, thing.getProject(), ob); if (-1 == i_position && new_thing.getType().equals("layer")) { // find the node whose 'z' is larger than z, and add the Layer before that. // (just because there could be objects other than LayerThing with a Layer in it in the future, so set.getLayers().indexOf(layer) may not be useful) double z = ((Layer)ob).getZ(); int n = selected_node.getChildCount(); int i = 0; for (; i < n; i++) { DefaultMutableTreeNode child_node = (DefaultMutableTreeNode)selected_node.getChildAt(i); LayerThing child_thing = (LayerThing)child_node.getUserObject(); if (!child_thing.getType().equals("layer")) { continue; } double iz = ((Layer)child_thing.getObject()).getZ(); if (iz < z) { continue; } // else, add the layer here, after this one break; } i_position = i; } thing.addChild(new_thing); DefaultMutableTreeNode new_node = new DefaultMutableTreeNode(new_thing); ((DefaultTreeModel)this.getModel()).insertNodeInto(new_node, selected_node, i_position); TreePath treePath = new TreePath(new_node.getPath()); this.scrollPathToVisible(treePath); this.setSelectionPath(treePath); if (new_thing.getType().equals("layer set")) { // add the first layer to it, and open it in a Display LayerSet newls = (LayerSet)new_thing.getObject(); Layer la = new Layer(newls.getProject(), 0, 1, newls); addLayer(newls, la); new Display(newls.getProject(), la); } } catch (Exception e) { IJError.print(e); } } public boolean remove(Layer layer, boolean check) { DefaultMutableTreeNode root = (DefaultMutableTreeNode)this.getModel().getRoot(); LayerThing thing = (LayerThing)(((LayerThing)root.getUserObject()).findChild(layer)); if (null == thing) { Utils.log2("LayerTree.remove(Layer): thing not found"); return false; } DefaultMutableTreeNode node = DNDTree.findNode(thing, this); if (null == node) { Utils.log2("LayerTree.remove(Layer): node not found"); return false; } if (thing.remove(check)) { ((DefaultTreeModel)this.getModel()).removeNodeFromParent(node); this.updateUILater(); } return true; } /** Used by the Loader.importStack and the "many new layers" command. */ public void addLayer(LayerSet layer_set, Layer layer) { if (null == layer_set || null == layer) return; try { // find the node that contains the LayerSet DefaultMutableTreeNode root_node = (DefaultMutableTreeNode)this.getModel().getRoot(); LayerThing root_lt = (LayerThing)root_node.getUserObject(); Thing thing = null; if (root_lt.getObject().equals(layer_set)) thing = root_lt; else thing = root_lt.findChild(layer_set); DefaultMutableTreeNode parent_node = DNDTree.findNode(thing, this); if (null == parent_node) { Utils.log("LayerTree: LayerSet not found."); return; } LayerThing parent_thing = (LayerThing)parent_node.getUserObject(); double z = layer.getZ(); // find the node whose 'z' is larger than z, and add the Layer before that. int n = parent_node.getChildCount(); int i = 0; for (; i < n; i++) { DefaultMutableTreeNode child_node = (DefaultMutableTreeNode)parent_node.getChildAt(i); LayerThing child_thing = (LayerThing)child_node.getUserObject(); if (!child_thing.getType().equals("layer")) { continue; } double iz = ((Layer)child_thing.getObject()).getZ(); if (iz < z) { continue; } // else, add the layer here, after the 'i' layer which has a larger z break; } TemplateThing tt = parent_thing.getChildTemplate("layer"); if (null == tt) { Utils.log("LayerTree: Null template Thing!"); return; } LayerThing new_thing = new LayerThing(tt, layer.getProject(), layer); // Add the new_thing to the tree if (null != new_thing) { parent_thing.addChild(new_thing); DefaultMutableTreeNode new_node = new DefaultMutableTreeNode(new_thing); //TODO when changing the Z of a layer, the insertion is proper but an empty space is left //Utils.log("LayerTree: inserting at: " + i); ((DefaultTreeModel)this.getModel()).insertNodeInto(new_node, parent_node, i); TreePath treePath = new TreePath(new_node.getPath()); this.scrollPathToVisible(treePath); this.setSelectionPath(treePath); } } catch (Exception e) { IJError.print(e); } } public void destroy() { super.destroy(); this.selected_node = null; } /** Remove all layer nodes from the given layer_set, and add them again according to the layer's Z value. */ public void updateList(LayerSet layer_set) { // store scrolling position for restoring purposes /* Component c = this.getParent(); Point point = null; if (c instanceof JScrollPane) { point = ((JScrollPane)c).getViewport().getViewPosition(); } */ LayerThing lt = layer_set.getProject().findLayerThing(layer_set); if (null == lt) { Utils.log2("LayerTree.updateList: could not find LayerSet " + layer_set); return; } // call super updateList(lt); /* DefaultMutableTreeNode ls_node = DNDTree.findNode(lt, this); if (null == ls_node) { Utils.log2("LayerTree.updateList: could not find a node for LayerThing " + lt); return; } Hashtable ht = new Hashtable(); for (Enumeration e = ls_node.children(); e.hasMoreElements(); ) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.nextElement(); ht.put(node.getUserObject(), node); } ls_node.removeAllChildren(); for (Iterator it = lt.getChildren().iterator(); it.hasNext(); ) { Object ob = ht.remove(it.next()); ls_node.add((DefaultMutableTreeNode)ob); } if (0 != ht.size()) { Utils.log2("WARNING LayerTree.updateList: did not end up adding this nodes:"); for (Iterator it = ht.keySet().iterator(); it.hasNext(); ) { Utils.log2(it.next().toString()); } } this.updateUILater(); // restore viewport position if (null != point) { ((JScrollPane)c).getViewport().setViewPosition(point); } */ // what the hell: this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); } /** If the given node is null, it will be searched for. */ public boolean remove(boolean check, LayerThing thing, DefaultMutableTreeNode node) { if (null == thing || null == thing.getParent()) return false; // can't remove the root LayerSet return thing.remove(check) && removeNode(null != node ? node : findNode(thing, this)); } protected DNDTree.NodeRenderer createNodeRenderer() { return new LayerThingNodeRender(); } static private final Color FRONT_LAYER_COLOR = new Color(1.0f, 1.0f, 0.4f, 0.5f); protected final class LayerThingNodeRender extends DNDTree.NodeRenderer { private static final long serialVersionUID = 1L; public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { final JLabel label = (JLabel)super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); label.setText(label.getText().replace('_', ' ')); // just for display try { if (value.getClass() == DefaultMutableTreeNode.class) { final Object obb = ((DefaultMutableTreeNode)value).getUserObject(); if (!(obb instanceof LayerThing)) { Utils.log2("WARNING: not a LayerThing: obb is " + obb.getClass() + " and contains " + obb + " " + ((Thing)obb).getObject()); } final Object ob = ((Thing)obb).getObject(); final Layer layer = Display.getFrontLayer(); if (ob == layer) { label.setOpaque(true); //this label label.setBackground(FRONT_LAYER_COLOR); // this label } else if (ob.getClass() == LayerSet.class && null != layer && layer.contains((Displayable)ob)) { label.setOpaque(true); //this label label.setBackground(ProjectTree.ACTIVE_DISPL_COLOR); // this label } else { label.setOpaque(false); //this label label.setBackground(background); } } } catch (Throwable t) { t.printStackTrace(); } return label; } } /** Deselects whatever node is selected in the tree, and tries to select the one that contains the given object. */ public void selectNode(final Layer layer) { final DefaultMutableTreeNode node = DNDTree.findNode2(layer, this); SwingUtilities.invokeLater(new Runnable() { public void run() { // deselect whatever is selected setSelectionPath(null); if (null != node) { final TreePath path = new TreePath(node.getPath()); try { scrollPathToVisible(path); // involves repaint, so must be set through invokeAndWait. Why it doesn't do so automatically is beyond me. setSelectionPath(path); } catch (Exception e) { IJError.print(e, true); } } }}); } @Override protected Thing getRootThing() { return project.getRootLayerThing(); } }