/*
* org.openmicroscopy.shoola.agents.measurement.view.ROITable
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2007 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.measurement.view;
//Java imports
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;
import javax.swing.JPopupMenu;
import javax.swing.ToolTipManager;
import javax.swing.table.TableColumn;
import javax.swing.tree.TreePath;
//Third-party libraries
import org.jdesktop.swingx.JXTreeTable;
import org.jhotdraw.draw.Figure;
//Application-internal dependencies
import org.openmicroscopy.shoola.agents.measurement.util.roimenu.ROIPopupMenu;
import org.openmicroscopy.shoola.agents.measurement.util.roitable.ROIActionController;
import org.openmicroscopy.shoola.agents.measurement.util.roitable.ROINode;
import org.openmicroscopy.shoola.agents.measurement.util.roitable.ROITableCellRenderer;
import org.openmicroscopy.shoola.agents.measurement.util.roitable.ROITableModel;
import org.openmicroscopy.shoola.agents.measurement.util.ui.ShapeRenderer;
import org.openmicroscopy.shoola.util.roi.figures.ROIFigure;
import org.openmicroscopy.shoola.util.roi.model.ROI;
import org.openmicroscopy.shoola.util.roi.model.ROIShape;
import org.openmicroscopy.shoola.util.roi.model.annotation.AnnotationKeys;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.model.util.Coord3D;
import org.openmicroscopy.shoola.util.ui.graphutils.ShapeType;
import org.openmicroscopy.shoola.util.ui.treetable.OMETreeTable;
/**
* The ROITable is the class extending the JXTreeTable, this shows the
* ROI as the parent object and the ROIShapes belonging to the ROI as the
* child objects.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* <small>
* (<b>Internal version:</b> $Revision: $Date: $)
* </small>
* @since OME3.0
*/
public class ROITable
extends OMETreeTable
implements ROIActionController
{
/** The root node of the tree. */
private ROINode root;
/** Column names of the table. */
private Vector<String> columnNames;
/** The map to relate ROI to ROINodes. */
private Map<ROI, ROINode> ROIMap;
/** The tree model. */
private ROITableModel model;
/** The roi popup menu creation class .*/
private ROIPopupMenu popupMenu;
/** Reference to the object manager. */
private ObjectManager manager;
/** Flag indicating to reset the component when loading locally.*/
private boolean reset;
/**
* Returns <code>true</code> if all the roishapes in the shapelist
* have the same id, <code>false</code> otherwise.
*
* @param shapeList The list to handle.
* @return See above.
*/
private boolean haveSameID(List<ROIShape> shapeList)
{
TreeMap<Long, ROIShape> shapeMap = new TreeMap<Long, ROIShape>();
for (ROIShape shape : shapeList)
{
if (!shapeMap.containsKey(shape.getID()))
{
if (shapeMap.size() == 0)
shapeMap.put(shape.getID(), shape);
else
return false;
}
}
return true;
}
/**
* Returns the id that the shapes in the list contain, if they
* do not contain the same id return -1;
*
* @param shapeList The list to handle.
* @return See above.
*/
private long getSameID(List<ROIShape> shapeList)
{
TreeMap<Long, ROIShape> shapeMap = new TreeMap<Long, ROIShape>();
if (shapeList.size() == 0) return -1;
for (ROIShape shape : shapeList)
{
if (!shapeMap.containsKey(shape.getID()))
{
if (shapeMap.size() == 0)
shapeMap.put(shape.getID(), shape);
else
return -1;
}
}
return shapeList.get(0).getID();
}
/**
* Are all the roishapes in the shapelist on separate planes.
*
* @param shapeList The list to handle.
* @return See above.
*/
private boolean onSeparatePlanes(List<ROIShape> shapeList)
{
TreeMap<Coord3D, ROIShape>
shapeMap = new TreeMap<Coord3D, ROIShape>(new Coord3D());
for (ROIShape shape : shapeList)
{
if (shapeMap.containsKey(shape.getCoord3D()))
return false;
else
shapeMap.put(shape.getCoord3D(), shape);
}
return true;
}
/**
* The constructor for the ROITable, taking the root node and
* column names as parameters.
*
* @param model the table model.
* @param columnNames the column names.
* @param manager Reference to the manager.
*/
ROITable(ROITableModel model, Vector columnNames, ObjectManager manager)
{
super(model);
this.model = model;
this.manager = manager;
this.root = (ROINode) model.getRoot();
this.columnNames = columnNames;
ToolTipManager.sharedInstance().registerComponent(this);
this.setAutoResizeMode(JXTreeTable.AUTO_RESIZE_ALL_COLUMNS);
ROIMap = new HashMap<ROI, ROINode>();
for (int i = 0 ; i < model.getColumnCount() ; i++)
getColumn(i).setResizable(true);
setDefaultRenderer(ShapeType.class, new ShapeRenderer());
setTreeCellRenderer(new ROITableCellRenderer());
popupMenu = new ROIPopupMenu(this);
reset = false;
}
/**
* Invokes when new figures are selected.
*
* @param figures The selected figures.
*/
void onSelectedFigures(Collection<Figure> figures)
{
popupMenu.setActionsEnabled(figures);
}
/**
* Select the ROIShape in the TreeTable and move the view port of the
* table to the shape selected.
* @param shape
*/
void selectROIShape(ROIShape shape)
{
ROINode parent = findParent(shape.getROI());
if (parent == null)
return;
expandROIRow(parent);
ROINode child = parent.findChild(shape);
int row = this.getRowForPath(child.getPath());
this.selectionModel.addSelectionInterval(row, row);
}
/**
* Scroll to the selected ROIShape.
* @param shape see above.
*/
void scrollToROIShape(ROIShape shape)
{
ROINode parent = findParent(shape.getROI());
if (parent == null)
return;
expandROIRow(parent);
ROINode child = parent.findChild(shape);
this.scrollPathToVisible(child.getPath());
}
/**
* Displays the menu at the specified location if not already visible.
*
* @param x The x-coordinate of the mouse click.
* @param y The y-coordinate of the mouse click.
*/
void showROIManagementMenu(Component c, int x, int y)
{
JPopupMenu menu = popupMenu.getPopupMenu();
if (menu.isVisible()) return;
menu.show(c, x, y);
}
/** Refreshes the data in the table. */
void refresh()
{
this.setTreeTableModel(new ROITableModel(root, columnNames));
}
/** Clears the table. */
void clear()
{
int childCount = root.getChildCount();
for (int i = 0 ; i < childCount ; i++ )
root.remove(0);
this.setTreeTableModel(new ROITableModel(root, columnNames));
ROIMap = new HashMap<ROI, ROINode>();
this.invalidate();
this.repaint();
}
/**
* Set the value of the object at row and column to value object,
* this will also expand the row of the object set, unless the ROI
* of the object is not visible it will then collapse the row.
*
* @param obj see above.
* @param row see above.
* @param column see above.
*/
public void setValueAt(Object obj, int row, int column)
{
ROINode node = (ROINode) getNodeAtRow(row);
super.setValueAt(obj, row, column);
ROINode expandNode;
if (node.getUserObject() instanceof ROI)
{
ROI roi = (ROI) node.getUserObject();
expandNode = node;
if (roi.isVisible())
expandROIRow(expandNode);
else
collapseROIRow(expandNode);
}
else
{
expandNode = (ROINode) node.getParent();
ROIShape roiShape = (ROIShape) node.getUserObject();
if (roiShape.getROI().isVisible())
expandROIRow(expandNode);
else
collapseROIRow(expandNode);
}
}
/**
* Adds the ROIShape to the table, placing it under the ROI the ROIShape
* belongs to. This method will create the ROI if it does not exist
* already. This method will also collapse all the ROI in the table
* and expand the ROI of the ROIShape, moving the viewport to
* the ROIShape.
*
* @param shape The shape to add.
*/
void addROIShape(ROIShape shape)
{
List<ROIShape> shapeList = new ArrayList<ROIShape>();
shapeList.add(shape);
addROIShapeList(shapeList);
}
/**
* Adds the ROIShapes in the shapeList to the table, placing it under the
* ROI the ROIShape belongs to. This method will create the ROI if it does
* not exist already. This method will also collapse all the ROI in the
* table and expand the ROI of the ROIShape, moving the viewport to
* the ROIShape.
*
* @param shapeList The collection to add.
*/
void addROIShapeList(List<ROIShape> shapeList)
{
ROINode parent = null;
int childCount;
ROINode roiShapeNode;
ROINode newNode;
int index;
for (ROIShape shape : shapeList)
{
parent = findParent(shape.getROI());
if (parent == null)
{
parent = new ROINode(shape.getROI());
parent.setExpanded(true);
ROIMap.put(shape.getROI(), parent);
childCount = root.getChildCount();
root.insert(parent, childCount);
}
roiShapeNode = parent.findChild(shape.getCoord3D());
newNode = new ROINode(shape);
newNode.setExpanded(true);
if (roiShapeNode != null)
{
index = parent.getIndex(roiShapeNode);
parent.remove(shape.getCoord3D());
parent.insert(newNode, index);
}
else
parent.insert(newNode,
parent.getInsertionPoint(shape.getCoord3D()));
}
model = new ROITableModel(root, columnNames);
this.setTreeTableModel(model);
if (parent != null) expandROIRow(parent);
}
/**
* Expands the row of the node.
*
* @param node The selected node.
*/
void expandNode(ROINode node)
{
if (node.getUserObject() instanceof ROI)
expandROIRow((ROI)node.getUserObject());
}
/**
* Returns the ROI Shape at row index.
*
* @param index The index of the row.
* @return See above.
*/
ROIShape getROIShapeAtRow(int index)
{
TreePath path = this.getPathForRow(index);
if (path == null) return null;
ROINode node = (ROINode)path.getLastPathComponent();
if (node.getUserObject() instanceof ROIShape)
return (ROIShape) node.getUserObject();
return null;
}
/**
* Returns the ROI at row index.
*
* @param index see above.
* @return see above.
*/
ROI getROIAtRow(int index)
{
TreePath path = this.getPathForRow(index);
ROINode node = (ROINode) path.getLastPathComponent();
if (node.getUserObject() instanceof ROI)
return (ROI)node.getUserObject();
return null;
}
/**
* Expands the row with node parent.
*
* @param parent see above.
*/
void expandROIRow(ROINode parent)
{
int addedNodeIndex = root.getIndex(parent);
parent.setExpanded(true);
this.expandRow(addedNodeIndex);
ROINode node;
for (int i = 0; i < root.getChildCount(); i++)
{
node = (ROINode) root.getChildAt(i);
if (node.isExpanded())
expandPath(node.getPath());
}
}
/**
* Collapses the row with node parent.
* @param parent see above.
*/
void collapseROIRow(ROINode parent)
{
int addedNodeIndex = root.getIndex(parent);
this.collapseRow(addedNodeIndex);
parent.setExpanded(false);
ROINode node;
for (int i = 0; i < root.getChildCount(); i++)
{
node = (ROINode) root.getChildAt(i);
if (node.isExpanded())
expandROIRow((ROINode) node);
}
}
/**
* Expands the row with ROI.
*
* @param roi see above.
*/
void expandROIRow(ROI roi)
{
ROINode selectedNode = findParent(roi);
this.expandROIRow(selectedNode);
// this.scrollCellToVisible(selectedNodeIndex, 0);
}
/**
* Removes the ROIShape from the table, and delete the ROI if the
* ROIShape was the last ROIShape in the ROI.
*
* @param shape see above.
*/
void removeROIShape(ROIShape shape)
{
ROINode parent = findParent(shape.getROI());
if (parent == null) return;
ROINode child = parent.findChild(shape);
parent.remove(child);
if (parent.getChildCount() == 0)
root.remove(parent);
this.setTreeTableModel(new ROITableModel(root, columnNames));
}
/**
* Removes the ROI from the table.
*
* @param roi see above.
*/
void removeROI(ROI roi)
{
ROINode roiNode = findParent(roi);
root.remove(roiNode);
this.setTreeTableModel(new ROITableModel(root, columnNames));
}
/**
* Finds the parent ROINode of the ROI.
* @param roi see above.
* @return see above.
*/
ROINode findParent(ROI roi)
{
if (ROIMap.containsKey(roi))
return ROIMap.get(roi);
return null;
}
/**
* Invokes when a ROIShape has changed its properties.
*
* @param shape the roiShape which has to be updated.
*/
void setROIAttributesChanged(ROIShape shape)
{
ROINode parent = findParent(shape.getROI());
ROINode child = parent.findChild(shape);
model.nodeUpdated(child);
}
/**
* Returns <code>true</code> if the column the shapeType column,
* <code>false</code> otherwise.
*
* @param column see above
* @return see above
*/
boolean isShapeTypeColumn(int column)
{
TableColumn col = this.getColumn(column);
return (col.getModelIndex() == (ROITableModel.SHAPE_COLUMN+1));
}
/**
* Create a list of all the roi and roishapes selected in the table.
* This will only list an roi even if the roi and roishapes are selected.
* @return see above.
*/
List getSelectedObjects()
{
int [] selectedRows = this.getSelectedRows();
TreeMap<Long, Object> roiMap = new TreeMap<Long, Object>();
List selectedList = new ArrayList();
Object nodeObject;
ROI roi;
for (int i = 0 ; i < selectedRows.length ; i++)
{
nodeObject = getNodeAtRow(selectedRows[i]).getUserObject();
if (nodeObject instanceof ROI)
{
roi = (ROI) nodeObject;
roiMap.put(roi.getID(), roi);
selectedList.add(roi);
}
}
ROIShape roiShape;
for (int i = 0 ; i < selectedRows.length ; i++)
{
nodeObject = this.getNodeAtRow(selectedRows[i]).getUserObject();
if (nodeObject instanceof ROIShape)
{
roiShape = (ROIShape) nodeObject;
if (!roiMap.containsKey(roiShape.getID()))
selectedList.add(roiShape);
}
}
return selectedList;
}
/**
* Build the plane map from the selected object list. This builds a map
* of all the planes that have objects reside on them.
* @param objectList see above.
* @return see above.
*/
TreeMap<Coord3D, ROIShape> buildPlaneMap(ArrayList objectList)
{
TreeMap<Coord3D, ROIShape> planeMap =
new TreeMap<Coord3D, ROIShape>(new Coord3D());
ROI roi;
TreeMap<Coord3D, ROIShape> shapeMap;
Iterator i;
Coord3D coord;
ROIShape shape;
Entry entry;
for (Object node : objectList)
{
if (node instanceof ROI)
{
roi = (ROI) node;
shapeMap = roi.getShapes();
i = shapeMap.entrySet().iterator();
while (i.hasNext())
{
entry = (Entry) i.next();
coord = (Coord3D) entry.getKey();
if (planeMap.containsKey(coord))
return null;
planeMap.put(coord, (ROIShape) entry.getValue());
}
} else if (node instanceof ROIShape)
{
shape = (ROIShape)node;
if (planeMap.containsKey(shape.getCoord3D()))
return null;
else
planeMap.put(shape.getCoord3D(), shape);
}
}
return planeMap;
}
/**
* Returns the id of objects in the selected list.
*
* @param selectedObjects
* @return see above.
*/
List<Long> getIDList(List selectedObjects)
{
TreeMap<Long,ROI> idMap = new TreeMap<Long, ROI>();
List<Long> idList = new ArrayList<Long>();
ROI roi;
for (Object node : selectedObjects)
{
if (node instanceof ROI) roi = (ROI) node;
else roi = ((ROIShape) node).getROI();
if (!idMap.containsKey(roi.getID()))
{
idMap.put(roi.getID(), roi);
idList.add(roi.getID());
}
}
return idList;
}
/**
* Returns the roishapes of the selected objects, this method will split ROI
* into their respective ROIshapes.
*
* @return see above.
*/
List<ROIShape> getSelectedROIShapes()
{
int [] selectedRows = this.getSelectedRows();
TreeMap<Long, Object> roiMap = new TreeMap<Long, Object>();
List<ROIShape> selectedList = new ArrayList<ROIShape>();
Object nodeObject;
ROI roi;
Iterator<ROIShape> shapeIterator;
for (int i = 0 ; i < selectedRows.length ; i++)
{
nodeObject = this.getNodeAtRow(selectedRows[i]).getUserObject();
if (nodeObject instanceof ROI)
{
roi = (ROI) nodeObject;
if (roi.isClientSide()) reset = true;
roiMap.put(roi.getID(), roi);
shapeIterator = roi.getShapes().values().iterator();
while (shapeIterator.hasNext())
selectedList.add(shapeIterator.next());
}
}
ROIShape roiShape;
for (int i = 0 ; i < selectedRows.length ; i++)
{
nodeObject = this.getNodeAtRow(selectedRows[i]).getUserObject();
if (nodeObject instanceof ROIShape)
{
roiShape = (ROIShape) nodeObject;
roi = roiShape.getROI();
if (roi.getShapes().size() == 1) reset = true;
if (!roiMap.containsKey(roiShape.getID()))
selectedList.add(roiShape);
}
}
if (selectedList.size() == 0) {//check model
Collection<Figure> figures = manager.getSelectedFigures();
if (figures != null && figures.size() > 0) {
Iterator<Figure> i = figures.iterator();
Figure figure;
while (i.hasNext()) {
figure = i.next();
if (figure instanceof ROIFigure) {
selectedList.add(((ROIFigure) figure).getROIShape());
}
}
}
}
return selectedList;
}
/**
* Duplicates the ROI
* @see ROIActionController#duplicateROI()
*/
public void duplicateROI()
{
manager.showReadyMessage();
List<ROIShape> selectedObjects = getSelectedROIShapes();
if (onSeparatePlanes(selectedObjects) && haveSameID(selectedObjects))
manager.duplicateROI(getSameID(selectedObjects), selectedObjects );
else
manager.showMessage("Duplicate: ROIs must be from the same ROI " +
"and on separate planes.");
}
/**
* Merges the ROI.
* @see ROIActionController#mergeROI()
*/
public void mergeROI()
{
manager.showReadyMessage();
List<ROIShape> selectedObjects = getSelectedROIShapes();
if (onSeparatePlanes(selectedObjects) && selectedObjects.size() > 1)
{
manager.mergeROI(getIDList(selectedObjects), selectedObjects);
} else
manager.showMessage("Merge: ROIs must be on separate " +
"planes and must include more than one.");
}
/**
* Propagates the ROI.
*
* @see ROIActionController#propagateROI()
*/
public void propagateROI()
{
manager.showReadyMessage();
if (this.getSelectedRows().length != 1)
{
manager.showMessage("Propagate: Only one ROI may be " +
"propagated at a time.");
return;
}
ROINode node = (ROINode) this.getNodeAtRow(this.getSelectedRow());
Object nodeObject = node.getUserObject();
if (nodeObject instanceof ROI)
manager.propagateROI(((ROI)nodeObject));
if (nodeObject instanceof ROIShape)
manager.propagateROI(((ROIShape)nodeObject).getROI());
}
/**
* Splits the ROI.
*
* @see ROIActionController#splitROI()
*/
public void splitROI()
{
manager.showReadyMessage();
List<ROIShape> selectedObjects = getSelectedROIShapes();
if (onSeparatePlanes(selectedObjects) && haveSameID(selectedObjects))
manager.splitROI(getSameID(selectedObjects), selectedObjects);
else
manager.showMessage("Split: ROIs must be from the same ROI and " +
"on separate planes.");
}
/**
* Deletes the ROIs.
*
* @see ROIActionController#deleteROI()
*/
public void deleteROI()
{
List<ROIShape> selectionList = getSelectedROIShapes();
manager.deleteROIShapes(selectionList);
if (reset) manager.reset();
reset = false;
}
/**
* Calculates statistics.
* @see ROIActionController#calculateStats()
*/
public void calculateStats()
{
manager.showReadyMessage();
List<ROIShape> selectedObjects = getSelectedROIShapes();
if (onSeparatePlanes(selectedObjects) && haveSameID(selectedObjects))
manager.calculateStats(selectedObjects);
else
manager.showMessage("Calculate: ROIs must be from the same ROI " +
"and on separate planes.");
}
/**
* Extending the mouse pressed event to show menu.
*
* @param e mouse event.
*/
protected void onMousePressed(MouseEvent e)
{
if (MeasurementViewerControl.isRightClick(e)) {
Collection l = getSelectedObjects();
if (l == null || l.size() == 0) return;
Iterator i = l.iterator();
Object o;
ROI roi;
ROIShape shape;
List<Figure> list = new ArrayList<Figure>();
while (i.hasNext()) {
o = i.next();
if (o instanceof ROI) {
roi = (ROI) o;
list.addAll(roi.getAllFigures());
} else if (o instanceof ROIShape) {
shape = (ROIShape) o;
list.add(shape.getFigure());
}
}
onSelectedFigures(list);
showROIManagementMenu(this, e.getX(), e.getY());
}
}
/**
* Overridden to display the tool tip.
* @see JXTreeTable#getToolTipText(MouseEvent)
*/
public String getToolTipText(MouseEvent e)
{
TreePath path = getPathForLocation(e.getX(), e.getY());
if (path == null) return "";
int row = getRowForPath(path);
if (row < 0) return "";
ROINode node = (ROINode) getNodeAtRow(row);
if (node == null) return "";
Object object = node.getUserObject();
if (object instanceof ROI) {
ROI roi = (ROI) object;
return AnnotationKeys.TEXT.get(roi);
} else if (object instanceof ROIShape) {
ROIShape s = (ROIShape) object;
return ""+s.getFigure().getAttribute(MeasurementAttributes.TEXT);
}
return "";
}
/**
* Loads the tags.
*
* @see ROIActionController#loadTags()
*/
public void loadTags() {
manager.loadTags();
}
}