/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2010 University of Dundee. All rights reserved.
*
*
* 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; either version 2 of the License, or
* (at your option) any later version.
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.agents.util.browser;
import java.awt.Color;
import java.awt.Font;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.tree.DefaultMutableTreeNode;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import omero.gateway.model.DataObject;
import omero.gateway.model.DatasetData;
import omero.gateway.model.ExperimenterData;
import omero.gateway.model.FileAnnotationData;
import omero.gateway.model.FileData;
import omero.gateway.model.GroupData;
import omero.gateway.model.ImageData;
import omero.gateway.model.PlateData;
import omero.gateway.model.PlateAcquisitionData;
import omero.gateway.model.ProjectData;
import omero.gateway.model.ScreenData;
import omero.gateway.model.TagAnnotationData;
/**
* Represents a component in the composite structure used to visualize an
* image hierarchy.
* <p>A concrete component can be either a {@link TreeImageNode}, to represent a
* single image, or an {@link TreeImageSet}, to represent a collection of
* images. An {@link TreeImageSet} can also contain other image sets, thus
* leading to a composite structure. This is a tree whose leaf nodes are
* {@link TreeImageNode} objects and internal nodes are {@link TreeImageSet}
* objects.</p>
* <p>So we have a general purpose, set-based structure we can use to visualize
* any image hierarchy: Project/Dataset/Image, Screen/Plate/Well/Image.
* The original data hierarchy translates into
* a visualization tree as follows. Each image object corresponds to an
* {@link TreeImageNode} and an image container, such as Dataset or Plate,
* corresponds to an {@link TreeImageSet}. All {@link TreeImageNode} objects
* that are created for the images in a given image container are added to the
* {@link TreeImageSet} object created for that image container. Nested
* containers translate into nested {@link TreeImageSet}s. For example, say you
* have a Project <code>p_1</code> and two datasets in it, <code>d_1</code>
* and <code>d_2</code>. The former contains image <code>i_1</code> and
* <code>i_2</code>, as the latter only has one image, <code>i_3</code>.
* This would translate into three {@link TreeImageNode}s
* <code>in_1, in_2, in_3</code>, respectively for <code>i_1, i_2, i_3</code>.
* You would then create two {@link TreeImageSet}s <code>ds_1, ds_2</code>,
* respectively for <code>d_1, d_2</code>, and add <code>in_1, in_2</code> to
* <code>ds_1</code> and <code>in_3</code> to <code> ds_2</code>. Finally you
* would create a third {@link TreeImageSet} <code>ps_1</code> for the Project
* and add <code>ds_1, ds_2</code> to it.</p>
* <p>Operations on a visualization tree are performed through visitors. The
* {@link TreeImageDisplayVisitor} interface allows you to define arbitrary
* operations that can then be applied to the tree by calling the
* {@link #accept(TreeImageDisplayVisitor) accept} method, usually on the root
* node.
* An example of this is layout management. In fact, an {@link TreeImageSet} can
* contain other nodes this class inherits from
* {@link DefaultMutableTreeNode} and nodes are added to its internal desktop,
* which has no layout manager. In order to position the contained nodes
* properly, you can write a layout class that implements the
* {@link TreeImageDisplayVisitor} interface to lay out the contents of
* every {@link TreeImageSet} node in a visualization tree.</p>
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @version 2.2
* @since OME2.2
*/
public abstract class TreeImageDisplay
extends DefaultMutableTreeNode
{
/** Identifies the <code>plain</code> style constant. */
public static final int FONT_PLAIN = Font.PLAIN;
/** Identifies the <code>bold</code> style constant. */
public static final int FONT_BOLD = Font.BOLD;
/** The gap between the number of items and the name. */
private static final String SPACE = " ";
/**
* Back pointer to the parent node or <code>null</code> if this is the root.
*/
private TreeImageDisplay parentDisplay;
/**
* The set of nodes that have been added to this node.
* Will always be empty for a leaf node.
*/
protected List<TreeImageDisplay> childrenDisplay;
/**
* The text of the tool tip: annotation if the <code>DataObject</code>
* can be annotated and the inserted date if any.
*/
private String tooltip;
/**
* Tells if the node has to be highlighted.
* If <code>null</code>, the node will display the normal
* background. If a color is specified, the node will be highlighted
* using the specified color.
*/
private Color highlight;
/** The font style used for the node. */
private int fontStyle;
/** Indicates if the node is expanded or not. */
private boolean expanded;
/** Indicates to display a truncated name. */
private boolean partialName;
/** Flag indicating if the node can be selected. */
protected boolean selectable;
/** The number of items. */
protected long numberItems;
/** Flag indicating that the node has to be refreshed. */
protected boolean toRefresh;
/** Indicates to display or not the number of items.*/
private boolean displayItems;
/** The annotation count.*/
private int count;
/**
* Checks if the algorithm to visit the tree is one of the constants
* defined by {@link TreeImageDisplayVisitor}.
*
* @param type The algorithm type.
* @return Returns <code>true</code> if the type is supported,
* <code>false</code> otherwise.
*/
private boolean checkAlgoType(int type)
{
switch (type) {
case TreeImageDisplayVisitor.TREEIMAGE_NODE_ONLY:
case TreeImageDisplayVisitor.TREEIMAGE_SET_ONLY:
case TreeImageDisplayVisitor.ALL_NODES:
return true;
default:
return false;
}
}
/**
* Constructor used by subclasses.
*
* @param hierarchyObject The original object in the image hierarchy which
* is visualized by this node.
* Never pass <code>null</code>.
*/
protected TreeImageDisplay(Object hierarchyObject)
{
super();
if (hierarchyObject == null)
throw new NullPointerException("No hierarchy object.");
setUserObject(hierarchyObject);
selectable = true;
childrenDisplay = new ArrayList<TreeImageDisplay>();
numberItems = -1;
partialName = true;
fontStyle = FONT_PLAIN;
displayItems = true;
count = 0;
}
/**
* Sets to <code>true</code> if the partial name of the node is shown, to
* <code>false</code> otherwise.
*
* @param b The value to set.
*/
void setPartialName(boolean b) { partialName = b; }
/**
* Returns <code>true</code> if the partial name of the node is shown,
* <code>false</code> otherwise.
*
* @return See above.
*/
public boolean isPartialName() { return partialName; }
/**
* Sets to <code>true</code> if the node is expanded,
* <code>false</code> otherwise.
*
* @param expanded The value to set.
*/
public void setExpanded(boolean expanded) { this.expanded = expanded; }
/**
* Returns <code>true</code> is the node is expanded,
* <code>false</code> otherwise.
*
* @return See above
*/
public boolean isExpanded() { return expanded; }
/**
* Returns the parent node to this node in the visualization tree.
*
* @return The parent node or <code>null</code> if this node has no parent.
* This can happen if this node hasn't been linked yet or if it's
* the root node.
*/
public TreeImageDisplay getParentDisplay() { return parentDisplay; }
/**
* Returns all the child nodes to this node in the visualization tree.
* Note that, although never <code>null</code>, the returned set may be
* empty. In particular, this is always the case for a leaf node
* that is an {@link TreeImageNode}.
*
* @return A <i>read-only</i> set containing all the child nodes.
*/
public List getChildrenDisplay()
{
return Collections.unmodifiableList(childrenDisplay);
}
/**
* Returns <code>true</code> if the nodes contain children,
* <code>false</code> otherwise.
*
* @return See above.
*/
public boolean hasChildrenDisplay()
{
return (childrenDisplay.size() != 0);
}
/**
* Adds a node to the visualization tree as a child of this node.
* The <code>child</code>'s parent is set to be this node. If <code>
* child</code> is currently a child to another node <code>n</code>,
* then <code>child</code> is first
* {@link #removeChildDisplay(TreeImageDisplay) removed} from <code>n</code>
* and then added to this node.
*
* @param child The node to add. Mustn't be <code>null</code>.
* @see DefaultMutableTreeNode
*/
public void addChildDisplay(TreeImageDisplay child)
{
if (child == null) throw new NullPointerException("No child.");
if (childrenDisplay.contains(child)) return;
if (child.parentDisplay != null) //Was the child of another node.
child.parentDisplay.removeChildDisplay(child);
child.parentDisplay = this;
childrenDisplay.add(child);
numberItems = childrenDisplay.size();
}
/**
* Removes the specified <code>child</code> node.
* If <code>child</code> is not among the children of this node, no action
* is taken. Otherwise, it is removed from the children set and orphaned.
* That is, its parent (which is this node) is set to <code>null</code>.
*
* @param child The node to remove. Mustn't be <code>null</code>.
*/
public void removeChildDisplay(TreeImageDisplay child)
{
if (child == null) throw new NullPointerException("No child.");
if (childrenDisplay.contains(child)) {
//NOTE: parentDisplay != null b/c child has been added through
//the add method.
child.parentDisplay.childrenDisplay.remove(child);
child.parentDisplay = null;
numberItems = childrenDisplay.size();
}
}
/** Removes all <code>children</code> nodes from the children set. */
public void removeAllChildrenDisplay()
{
Iterator i = childrenDisplay.iterator();
Set<Object> toRemove = new HashSet<Object>(childrenDisplay.size());
while (i.hasNext())
toRemove.add(i.next());
i = toRemove.iterator();
while (i.hasNext())
removeChildDisplay((TreeImageDisplay) i.next());
}
/**
* Removes the children contained in the passed collection.
*
* @param children The collection to handle.
*/
public void removeChildrenDisplay(List children)
{
if (children == null) return;
Iterator i = children.iterator();
while (i.hasNext())
removeChildDisplay((TreeImageDisplay) i.next());
}
/**
* Has the specified object visit this node and all nodes below this one
* in the visualization tree.
* For each node, the <code>visit</code> method is called passing in the
* node being visited.
*
* @param visitor The visitor. Mustn't be <code>null</code>.
* @see TreeImageDisplayVisitor
*/
public void accept(TreeImageDisplayVisitor visitor)
{
if (visitor == null) throw new NullPointerException("No visitor.");
accept(visitor, TreeImageDisplayVisitor.ALL_NODES);
}
/**
* Has the specified object visit this node and all nodes below this one
* in the visualization tree.
* According to the specified <code>algoType</code>,
* the <code>visit</code> method is called passing in the
* node being visited.
*
* @param visitor The visitor. Mustn't be <code>null</code>.
* @param algoType The algorithm selected. Must be one of the constants
* defined by {@link TreeImageDisplayVisitor}.
* @see TreeImageDisplayVisitor
*/
public void accept(TreeImageDisplayVisitor visitor, int algoType)
{
if (visitor == null) throw new NullPointerException("No visitor.");
if (!checkAlgoType(algoType))
throw new IllegalArgumentException("Algorithm not supported.");
Iterator i = childrenDisplay.iterator();
TreeImageDisplay child;
switch (algoType) {
case TreeImageDisplayVisitor.TREEIMAGE_NODE_ONLY:
while (i.hasNext()) {
child = (TreeImageDisplay) i.next();
child.accept(visitor, algoType);
}
if (this instanceof TreeImageNode) doAccept(visitor);
break;
case TreeImageDisplayVisitor.TREEIMAGE_SET_ONLY:
while (i.hasNext()) {
child = (TreeImageDisplay) i.next();
if (child instanceof TreeImageSet)
child.accept(visitor, algoType);
}
if (this instanceof TreeImageSet) doAccept(visitor);
break;
case TreeImageDisplayVisitor.ALL_NODES:
while (i.hasNext()) {
child = (TreeImageDisplay) i.next();
child.accept(visitor, algoType);
}
doAccept(visitor);
break;
}
}
/**
* Sets the text displayed in a tool tip.
*
* @param tooltip The text to set.
*/
public void setToolTip(String tooltip) { this.tooltip = tooltip; }
/**
* Returns the text displayed in a tool tip.
*
* @return See above.
*/
public String getToolTip() { return tooltip; }
/**
* Returns <code>true</code> if the node can be selected,
* <code>false</code> otherwise.
*
* @return See above.
*/
public boolean isSelectable() { return selectable; }
/**
* Returns <code>true</code> if the node can be selected,
* <code>false</code> otherwise.
*
* @param selectable The value to set.
*/
public void setSelectable(boolean selectable)
{
this.selectable = selectable;
}
/**
* Returns the name of the node.
*
* @return See above.
*/
public String getNodeName()
{
Object obj = getUserObject();
if (obj instanceof ProjectData) return ((ProjectData) obj).getName();
else if (obj instanceof DatasetData)
return ((DatasetData) obj).getName();
else if (obj instanceof ImageData)
return ((ImageData) obj).getName();
else if (obj instanceof ExperimenterData) {
return EditorUtil.formatExperimenter((ExperimenterData) obj);
} else if (obj instanceof GroupData) {
return ((GroupData) obj).getName();
} else if (obj instanceof TagAnnotationData)
return ((TagAnnotationData) obj).getTagValue();
else if (obj instanceof ScreenData)
return ((ScreenData) obj).getName();
else if (obj instanceof PlateData) {
PlateData plate = (PlateData) obj;
return plate.getName()+" ("+plate.getPlateType()+")";
} else if (obj instanceof FileAnnotationData) {
String name = ((FileAnnotationData) obj).getFileName();
if (name.trim().length() == 0)
return "ID: "+((FileAnnotationData) obj).getId();
return name;
} else if (obj instanceof File)
return ((File) obj).getName();
else if (obj instanceof FileData)
return ((FileData) obj).getName();
else if (obj instanceof PlateAcquisitionData)
return ((PlateAcquisitionData) obj).getLabel();
else if (obj instanceof String) return (String) obj;
return "";
}
/**
* Returns the name of the node and the number of items
* in the specified containers.
*
* @return See above.
*/
public String getNodeText()
{
String name = getNodeName();
Object uo = getUserObject();
if (uo instanceof ImageData) { return name;
} else if (uo instanceof ExperimenterData) return name;
else if (uo instanceof FileAnnotationData) return name;
else if (uo instanceof File) return name;
else if (uo instanceof FileData) return name;
else if (uo instanceof PlateAcquisitionData) return name;
else if (uo instanceof String && numberItems < 0)
return name;
else if ((uo instanceof PlateData) && !hasChildrenDisplay())
return name;
if (!displayItems) return name;
if (numberItems < 0) return (name+SPACE+"[...]");
return (name+SPACE+"["+numberItems+"]");
}
/**
* Returns <code>true</code> if the object has been annotated,
* <code>false</code> otherwise.
*
* @return See above.
*/
public boolean isAnnotated()
{
return EditorUtil.isAnnotated(getUserObject(), count);
}
/**
* Returns <code>true</code> if the data object has children,
* <code>false</code> otherwise.
*
* @return See above.
*/
public boolean hasChildren()
{
Object uo = getUserObject();
if ((uo instanceof ProjectData) || (uo instanceof ScreenData) ||
(uo instanceof PlateData) || (uo instanceof DatasetData) ||
(uo instanceof TagAnnotationData) || (uo instanceof GroupData)) {
if (numberItems > 0) return true;
return hasChildrenDisplay();
}
return false;
}
/**
* Sets the font style of the node.
*
* @param style The font style to set. One out of the following constants
* {@link #FONT_BOLD} or {@link #FONT_PLAIN}.
*/
public void setFontStyle(int style)
{
switch (style) {
case FONT_BOLD:
case FONT_PLAIN:
fontStyle = style;
break;
default:
fontStyle = FONT_PLAIN;
}
}
/**
* Returns the font style for this node. One out of the following constants
* {@link #FONT_BOLD} or {@link #FONT_PLAIN}.
*
* @return See above.
*/
public int getFontStyle() { return fontStyle; }
/**
* Sets the highlight color.
*
* @param highlight The color to set.
*/
public void setHighLight(Color highlight) { this.highlight = highlight; }
/**
* Returns the highlight color.
*
* @return See above.
*/
public Color getHighLight() { return highlight; }
/**
* Returns the id of the node's user object if it is an instance of
* <code>DataObject</code> otherwise returns <code>-1</code>.
*
* @return See above.
*/
public long getUserObjectId()
{
Object uo = getUserObject();
if (uo instanceof DataObject) return ((DataObject) uo).getId();
return -1;
}
/**
* Returns the number of items.
*
* @return See above.
*/
public long getNumberOfItems() { return numberItems; }
/**
* Sets the flag indicating that the node needs to be refreshed.
*
* @param toRefresh Pass <code>true</code> to indicate that the node needs
* to be refreshed, <code>false</code> otherwise.
*/
public void setToRefresh(boolean toRefresh) { this.toRefresh = toRefresh; }
/**
* Sets the annotation count.
*
* @param count The value to set.
*/
public void setAnnotationCount(int count) { this.count = count; }
/**
* Returns <code>true</code> to indicate that the node needs
* to be refreshed, <code>false</code> otherwise.
*
* @return See above.
*/
public boolean isToRefresh() { return toRefresh; }
/**
* Sets to <code>true</code> to display the number of items,
* <code>false</code> otherwise.
*
* @param displayItems The value to set.
*/
public void setDisplayItems(boolean displayItems)
{
this.displayItems = displayItems;
}
/**
* Returns <code>true</code> if the specified user is the owner of the
* data object, <code>false</code> otherwise.
*
* @param userId The id of the user.
* @return See above.
*/
public boolean isOwner(long userId)
{
Object ho = getUserObject();
if (ho instanceof GroupData || ho instanceof ExperimenterData)
return false;
if (ho instanceof DataObject) {
DataObject data = (DataObject) ho;
if (data.getOwner() != null)
return data.getOwner().getId() == userId;
}
return false;
}
/**
* Overridden to return the name of the hierarchy object.
* @see Object#toString()
*/
public String toString() { return getNodeName(); }
/**
* Overridden to make sure that the userObject is not <code>null</code>.
* @see DefaultMutableTreeNode#setUserObject(Object)
*/
public void setUserObject(Object userObject)
{
if (userObject == null)
throw new NullPointerException("No userObject.");
super.setUserObject(userObject);
}
/**
* Made final to ensure objects are compared by reference so that the
* {@link #addChildDisplay(TreeImageDisplay) addChildDisplay} and
* {@link #removeChildDisplay(TreeImageDisplay) removeChildDisplay} methods
* will work fine.
* @see Object#equals(Object)
*/
public final boolean equals(Object x) { return (this == x); }
/**
* Implemented by subclasses to call the right version of the <code>visit
* </code> method on the specified <code>visitor</code>.
* This method is called by {@link #accept(TreeImageDisplayVisitor)} during
* the nodes iteration. Subclasses will just call the <code>visit</code>
* method passing a reference to <code>this</code>.
*
* @param visitor The visitor. Will never be <code>null</code>.
*/
protected abstract void doAccept(TreeImageDisplayVisitor visitor);
/**
* Tells if the children of this node are {@link TreeImageNode}s.
*
* @return Returns <code>true</code> if there's at least one
* {@link TreeImageNode} child, <code>false</code> otherwise.
*/
public abstract boolean containsImages();
/**
* Tells if the children of this node were requested.
*
* @return <code>true</code> if the children were requested,
* <code>false</code> otherwise.
*/
public abstract boolean isChildrenLoaded();
/**
* Indicates that the children were requested for this node or not.
*
* @param childrenLoaded The value to set.
*/
public abstract void setChildrenLoaded(Boolean childrenLoaded);
/**
* Makes a copy of the current object.
*
* @return See above.
*/
public abstract TreeImageDisplay copy();
/**
* Tells if the passed node is already a child of the node.
*
* @return Returns <code>true</code> if it is a child,
* <code>false</code> otherwise.
*/
public abstract boolean contains(TreeImageDisplay node);
}