/** 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 ini.trakem2.Project; import ini.trakem2.display.Display; import ini.trakem2.display.Layer; import ini.trakem2.display.LayerSet; import ini.trakem2.persistence.DBObject; import ini.trakem2.utils.Utils; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import javax.swing.JMenu; import javax.swing.JMenuItem; public final class LayerThing extends DBObject implements TitledThing { /** The model for this LayerThing instance. */ private TemplateThing template; /** If null, this instance is root. */ private LayerThing parent; private Object object; // a Layer or a LayerSet /** Access to a non-null al_children is synchronized. Works because al_children is never set to null if it is already not null.*/ private ArrayList<LayerThing> al_children = null; // TODO : attributes, at all? private String title = null; /** A new copy with same template, same project, same id, same object and cloned table of same attributes, and same title, but no parent and no children. */ public Thing shallowCopy() { return new LayerThing(this); } /** For shallow copying purposes. */ private LayerThing(final LayerThing lt) { super(lt.project, lt.id); this.template = lt.template; this.title = lt.title; this.object = lt.object; } public LayerThing(TemplateThing template, Project project, Object ob) throws Exception { // call super constructor super(project); // specifics: this.template = template; if (null == template) throw new Exception("LayerThing constructor: null template!"); this.object = ob; if (null == ob) throw new Exception("LayerThing constructor: null Object!"); addToDatabase(); } /** Reconstruct from database, in combination with the setup() method. */ public LayerThing(TemplateThing template, Project project, long id, String title, Object ob, ArrayList<LayerThing> al_children) { super(project, id); this.template = template; this.object = ob; this.title = title; if (null != title && (0 == title.length() || title.toLowerCase().equals("null"))) { this.title = null; } this.al_children = al_children; } /** Tell the attributes who owns them, and the children's attributes as well, and set the parent to the children; used to finish up reconstruction from the database. */ public void setup() { if (null != al_children) { synchronized (al_children) { for (final LayerThing child : al_children) { child.parent = this; child.setup(); } } } } public Thing getParent() { return parent; } public TemplateThing getChildTemplate(String type) { return template.getChildTemplate(type); } public String toString() { final StringBuffer sb = new StringBuffer(); if (null != parent) sb.append(Integer.toString(parent.indexOf(this) + 1)).append(':').append(' '); if (null != title) sb.append(title); sb.append(' '); if (null == object) sb.append(template.getType()); else sb.append(object.toString()).append(' ').append('[').append(template.getType()).append(']'); return sb.toString(); } public void setTitle(String title) { if (null == title || title.equals(this.title)) return; this.title = title; updateInDatabase("title"); if (object instanceof Layer) { // haha, bad design ... the DBObject should have a get/setTitle method pair Display.updateTitle((Layer)object); } } /** May be null or empty; call toString() to get a textual representation. */ public String getTitle() { return this.title; } public boolean canHaveAsChild(Thing thing) { if (null == thing) return false; return template.canHaveAsChild(thing); } public boolean addChild(Thing child) { if (!template.canHaveAsChild(child)) { return false; } if (null == al_children) al_children = new ArrayList<LayerThing>(); synchronized (al_children) { if (null != child.getObject() && child.getObject() instanceof Layer) { // this is a patch, but hey, do you want to redesign the events, which are based on layer titles and toString() contents? TODO ... Layer l = (Layer)child.getObject(); int i = l.getParent().indexOf(l); //Utils.log2("al_children.size(): " + al_children.size() + ", i=" + i); if (i >= al_children.size()) { //TODO happens when importing a stack al_children.add((LayerThing)child); } else { try { al_children.add(i, (LayerThing)child); } catch (Exception e) { Utils.log2("LayerThing.addChild: " + e); al_children.add((LayerThing)child); // at the end } } } else { al_children.add((LayerThing)child); } } child.setParent(this); return true; } public boolean removeChild(LayerThing child) { if (null == al_children || null == child) return false; synchronized (al_children) { return al_children.remove(child); } } public String getType() { return template.getType(); } public ArrayList<LayerThing> getChildren() { return al_children; } public boolean hasChildren() { return !(null == al_children || 0 == al_children.size()); } public Object getObject() { return object; } public void setParent(Thing parent) { this.parent = (LayerThing)parent; updateInDatabase("parent_id"); } public JMenuItem[] getPopupItems(ActionListener listener) { JMenuItem item; ArrayList<JMenuItem> al_items = new ArrayList<JMenuItem>(); JMenu menu = new JMenu("Add..."); ArrayList<TemplateThing> tc = template.getChildren(); if (null != tc) { for (Iterator<TemplateThing> it = tc.iterator(); it.hasNext(); ) { item = new JMenuItem("new " + it.next().getType().replace('_', ' ')); // changing underscores for spaces, for the 'layer_set' type to read nice item.addActionListener(listener); menu.add(item); } if (template.getType().replaceAll("_", " ").equals("layer set")) { item = new JMenuItem("many new layers..."); item.addActionListener(listener); menu.add(item); } } if (0 != menu.getItemCount()) { al_items.add(menu); } // Add a "Show" for all except the root LayerSet if (null != parent) { item = new JMenuItem("Show"); item.addActionListener(listener); al_items.add(item); } if (template.getType().equals("layer")) { item = new JMenuItem("Adjust..."); // adjust z and thickness item.addActionListener(listener); al_items.add(item); } item = new JMenuItem("Rename..."); item.addActionListener(listener); al_items.add(item); if (template.getType().replaceAll("_", " ").equals("layer set")) { if (null != parent) { item = new JMenuItem("Show centered in Display"); item.addActionListener(listener); al_items.add(item); } item = new JMenuItem("Resize LayerSet..."); item.addActionListener(listener); al_items.add(item); LayerSet layer_set = (LayerSet)object; final boolean empty = 0 == layer_set.getLayers().size(); item = new JMenuItem("Autoresize LayerSet"); item.addActionListener(listener); al_items.add(item); if (empty) item.setEnabled(false); item = new JMenuItem("Translate layers in Z..."); item.addActionListener(listener); al_items.add(item); if (empty) item.setEnabled(false); item = new JMenuItem("Reverse layer Z coords..."); item.addActionListener(listener); al_items.add(item); if (empty) item.setEnabled(false); item = new JMenuItem("Reset layer Z and thickness"); item.addActionListener(listener); al_items.add(item); if (empty) item.setEnabled(false); item = new JMenuItem("Search..."); item.addActionListener(listener); al_items.add(item); if (empty) item.setEnabled(false); } item = new JMenuItem("Import stack..."); if (template.getType().replaceAll("_", " ").equals("layer set") && 0 != ((LayerSet)object).getLayers().size()) item.setEnabled(false); // contains layers already, wouldn't know where to put it! item.addActionListener(listener); al_items.add(item); if (template.getType().equals("layer")) { item = new JMenuItem("Import grid..."); item.addActionListener(listener); al_items.add(item); item = new JMenuItem("Import sequence as grid..."); item.addActionListener(listener); al_items.add(item); item = new JMenuItem("Import from text file..."); item.addActionListener(listener); al_items.add(item); } // add a delete to all except the root LayerSet if (null != parent) { al_items.add(new JMenuItem("")); item = new JMenuItem("Delete..."); item.addActionListener(listener); al_items.add(item); } JMenuItem[] items = new JMenuItem[al_items.size()]; al_items.toArray(items); return items; } /** Remove this instance, cascading the remove action to the children and the objects. Will also cleanup the nodes in the ProjectTree. */ public boolean remove(boolean check) { if (check && !Utils.check("Really delete " + this.toString() + (object instanceof Layer && ((Layer)object).getDisplayables().size() > 0 ? " and all its children?" : ""))) return false; // remove the children, which will propagate to their own objects if (null != al_children) { synchronized (al_children) { LayerThing[] ob = new LayerThing[al_children.size()]; al_children.toArray(ob); for (int i=0; i<ob.length; i++) { if ( ! ob[i].remove(false)) { Utils.log("Could not delete " + ob[i]); return false; } } al_children.clear(); } } // TODO the attributes are being ignored! (not even created either) // remove the object if (null != object && object instanceof DBObject) { if (!((DBObject)object).remove(false)) { Utils.log("Could not delete " + object); return false; } Display.repaint(); } // remove the Thing itself if (null != parent && !parent.removeChild(this)) { Utils.log("Could not delete LayerThing with id=" + id); return false; } removeFromDatabase(); return true; } /** Recursive search for the thing that contains the given object. */ public Thing findChild(Object ob) { if (this.object.equals(ob)) return this; if (null == al_children) return null; synchronized (al_children) { for (Iterator<LayerThing> it = al_children.iterator(); it.hasNext(); ) { Thing found = it.next().findChild(ob); if (null != found) return found; } } return null; } public int indexOf(LayerThing child) { synchronized (al_children) { return al_children.indexOf(child); } } public void debug(String indent) { StringBuffer sb_at = new StringBuffer(" (id)"); // 'id' exists regardless sb_at.append(" object: ").append(object); System.out.println(indent + template.getType() + sb_at.toString()); if (null != al_children) { if (indent.length() > 20) { System.out.println("INDENT OVER 20 !"); return; } for (final LayerThing child : al_children) { child.debug(indent + "\t"); } } } public boolean isExpanded() { return project.getLayerTree().isExpanded(this); } /** Return information on this node and its object. */ // TODO: make it recursive on all children objects, listing their attributes and their object's info public String getInfo() { return "Node: " + object + "\n" + (object instanceof DBObject ? ((DBObject)object).getInfo() : ""); } }