// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/OMGeometryList.java,v $
// $RCSfile: OMGeometryList.java,v $
// $Revision: 1.10 $
// $Date: 2005/08/09 20:01:45 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics;
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
/**
* This class encapsulates a List of OMGeometries. It's an OMGraphic, so it
* contains information on how to draw them. It's also a subclass to the
* OMGraphicList, and relies on many OMGraphicList methods.
*
* <p>
* The OMGeometryList assumes that all OMGeometries on it should be rendered the
* same - same fill color, same edge color and stroke, and will create one
* java.awt.Shape object from all the projected OMGeometries for more efficient
* rendering. If your individual OMGeometries have independent rendering
* characteristics, use the OMGraphicList and OMGraphics.
*
* <p>
* Because the OMGeometryList creates a single java.awt.Shape object for all of
* its contents, it needs to be generated() if an OMGeometry is added or removed
* from the list. If you don't regenerate the OMGeometryList, the list will
* iterate through its contents and render each piece separately.
*/
public class OMGeometryList extends OMList<OMGeometry> implements Serializable {
/**
* Flag to mark that the parts should be connected, making this
* OMGeometryList a combination OMGraphic that sums disparate parts. False
* by default.
*/
protected boolean connectParts = false;
/**
* Construct an OMGeometryList.
*/
public OMGeometryList() {
}
/**
* Construct an OMGeometryList with an initial capacity.
*
* @param initialCapacity the initial capacity of the list
*/
public OMGeometryList(int initialCapacity) {
super(initialCapacity);
}
public OMGeometryList(Collection<OMGeometry> c) {
graphics.addAll(c);
}
/**
* Add an OMGeometry to the GraphicList. The OMGeometry must not be null.
*
* @param g the non-null OMGeometry to add
* @exception IllegalArgumentException if OMGeometry is null
*/
public boolean add(OMGeometry g) {
setNeedToRegenerate(true);
return super.add(g);
}
/**
* For backward compatibility.
*
* @param omg
* @return true if add was successful
*/
public boolean addOMGraphic(OMGraphic omg) {
return super.add(omg);
}
/**
* Remove the geometry from the list.
*
* @param geometry the geometry to remove.
* @return true if geometry was on the list, false if otherwise.
*/
public boolean remove(OMGeometry geometry) {
setNeedToRegenerate(true);
return super.remove(geometry);
}
/**
* Set the geometry at the specified location. The OMGeometry must not be
* null.
*
* @param geometry OMGeometry
* @param index index location of the OMGeometry placement.
* @exception ArrayIndexOutOfBoundsException if index is out-of-bounds
*/
public OMGeometry set(int index, OMGeometry geometry) {
setNeedToRegenerate(true);
synchronized (graphics) {
return graphics.set(index, geometry);
}
}
/**
* Remove the geometry at the location number.
*
* @param location the location of the OMGeometry to remove
*/
public OMGeometry remove(int location) {
OMGeometry obj = super.remove(location);
if (obj != null) {
setNeedToRegenerate(true);
}
return obj;
}
/**
* Renders all the objects in the list a geometries context. This is the
* same as <code>paint()</code> for AWT components. The geometries are
* rendered in the order of traverseMode. Any geometries where
* <code>isVisible()</code> returns false are not rendered.
*
* @param gr the AWT Graphics context
*/
public void render(Graphics gr) {
if (isVague() && !isVisible())
return;
Shape shp = getShape();
if (shp != null) {
if (matted) {
if (gr instanceof Graphics2D && stroke instanceof BasicStroke) {
((Graphics2D) gr).setStroke(new BasicStroke(((BasicStroke) stroke).getLineWidth() + 2f));
setGraphicsColor(gr, mattingPaint);
((Graphics2D) gr).draw(shp);
}
}
setGraphicsForFill(gr);
((Graphics2D) gr).fill(shp);
setGraphicsForEdge(gr);
((Graphics2D) gr).draw(shp);
} else {
synchronized (graphics) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<OMGeometry> iterator = graphics.listIterator(graphics.size());
while (iterator.hasPrevious()) {
renderGeometry(iterator.previous(), gr);
}
} else {
ListIterator<OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
renderGeometry(iterator.next(), gr);
}
}
}
}
renderLabel(gr);
}
protected void renderGeometry(OMGeometry geometry, Graphics gr) {
Shape shp = geometry.getShape();
boolean isRenderable = !geometry.getNeedToRegenerate() && geometry.isVisible()
&& shp != null;
if (isRenderable) {
if (matted) {
if (gr instanceof Graphics2D && stroke instanceof BasicStroke) {
((Graphics2D) gr).setStroke(new BasicStroke(((BasicStroke) stroke).getLineWidth() + 2f));
setGraphicsColor(gr, mattingPaint);
draw(gr, shp);
}
}
setGraphicsForFill(gr);
fill(gr, shp);
setGraphicsForEdge(gr);
draw(gr, shp);
}
}
/**
* Renders all the objects in the list a geometry's context, in their
* 'selected' mode. This is the same as <code>paint()</code> for AWT
* components. The geometries are rendered in the order of traverseMode. Any
* geometries where <code>isVisible()</code> returns false are not rendered.
* All of the geometries on the list are returned to their deselected state.
*
* @param gr the AWT Graphics context
*/
public void renderAllAsSelected(Graphics gr) {
Shape shape = getShape();
if (shape != null) {
setGraphicsForFill(gr);
((Graphics2D) gr).fill(shape);
select();
setGraphicsForEdge(gr);
((Graphics2D) gr).draw(shape);
deselect();
}
}
/**
* Prepare the geometries for rendering. This must be done before calling
* <code>render()</code>! This recursively calls generate() on the
* OMGeometries on the list.
*
* @param p a <code>Projection</code>
* @param forceProjectAll if true, all the geometries on the list are
* generated with the new projection. If false they are only
* generated if getNeedToRegenerate() returns true
* @see OMGeometry#generate
* @see OMGeometry#regenerate
*/
public boolean generate(Projection p, boolean forceProjectAll) {
setNeedToRegenerate(true);
GeneralPath projectedShape = null;
synchronized (graphics) {
if (traverseMode == FIRST_ADDED_ON_TOP) {
ListIterator<OMGeometry> iterator = graphics.listIterator(size());
while (iterator.hasPrevious()) {
projectedShape = updateShape(projectedShape, (OMGeometry) iterator.previous(), p, forceProjectAll);
}
} else {
ListIterator<OMGeometry> iterator = graphics.listIterator();
while (iterator.hasNext()) {
projectedShape = updateShape(projectedShape, (OMGeometry) iterator.next(), p, forceProjectAll);
}
}
}
setShape(projectedShape);
setLabelLocation(projectedShape, p);
setNeedToRegenerate(false);
return projectedShape != null;
}
/**
* Given a OMGeometry, it calls generate/regenerate on it, and then adds the
* GeneralPath shape within it to the OMGeometryList shape object. Calls
* setShape() with the new current shape, which is a synchronized method.
*
* @param geometry the geometry to append
* @param p the current projection
* @param forceProject flag to force re-generation
* @deprecated use the new paradigm from the other updateShape
*/
protected void updateShape(OMGeometry geometry, Projection p, boolean forceProject) {
if (geometry.isVisible()) {
if (forceProject) {
geometry.generate(p);
} else {
geometry.regenerate(p);
}
setShape(appendShapeEdge(getShape(), geometry.getShape(), connectParts));
}
}
/**
* Given an OMGeometry, check its visibility and if visible, generate it if
* required and add the result to the provided current shape. Does not call
* setShape().
*
* @param currentShape the current shape
* @param geometry the geometry to test
* @param p the current projection
* @param forceProject flag to force regeneration
* @return the newly combined shape.
*/
protected GeneralPath updateShape(GeneralPath currentShape, OMGeometry geometry, Projection p,
boolean forceProject) {
GeneralPath newShapePart = null;
if (geometry != null && geometry.isVisible()) {
if (forceProject) {
geometry.generate(p);
} else {
geometry.regenerate(p);
}
newShapePart = geometry.getShape();
}
return appendShapeEdge(currentShape, newShapePart, connectParts);
}
/**
* Read a cache of OMGeometries, given a ObjectInputStream.
*
* @param objstream ObjectInputStream of geometry list.
*/
public void readGraphics(ObjectInputStream objstream) throws IOException {
Debug.message("omgraphics", "OMGeometryList: Reading cached geometries");
try {
while (true) {
try {
OMGeometry omg = (OMGeometry) objstream.readObject();
this.add(omg);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (OptionalDataException ode) {
ode.printStackTrace();
}
}
} catch (EOFException e) {
}
}
/**
* Set whether the OMGeometries on the list should be connected to make a
* one-part shape object (if true), or a multi-part shape object (if false).
*/
public void setConnectParts(boolean value) {
connectParts = value;
}
/**
* Get whether the OMGeometries on the list should be connected to make a
* one-part shape object (if true), or a multi-part shape object (if false).
*/
public boolean getConnectParts() {
return connectParts;
}
/**
* Returns a iterator of a shallow copy of the current list, to avoid
* concurrent modification exceptions if the list is being generated or
* rendered while the list is being reviewed for other reasons.
*/
public Iterator<OMGeometry> iteratorCopy() {
return new OMGeometryList(graphics).iterator();
}
/**
* Returns a iterator of a shallow copy of the current list, to avoid
* concurrent modification exceptions if the list is being generated or
* rendered while the list is being reviewed for other reasons.
*/
public ListIterator<OMGeometry> listIteratorCopy() {
return new OMGeometryList(graphics).listIterator();
}
/**
* Returns a iterator of a shallow copy of the current list, to avoid
* concurrent modification exceptions if the list is being generated or
* rendered while the list is being reviewed for other reasons.
*/
public ListIterator<OMGeometry> listIteratorCopy(int size) {
return new OMGeometryList(graphics).listIterator(size);
}
@Override
public OMList<OMGeometry> create() {
return new OMGeometryList();
}
@Override
protected com.bbn.openmap.omGraphics.OMList.OMDist<OMGeometry> createDist() {
return new OMDist<OMGeometry>();
}
public void clear() {
setNeedToRegenerate(true);
super.clear();
}
public void add(int index, OMGeometry element) {
setNeedToRegenerate(true);
super.add(index, element);
}
public boolean addAll(Collection<? extends OMGeometry> c) {
setNeedToRegenerate(true);
synchronized (graphics) {
return graphics.addAll(c);
}
}
public boolean addAll(int index, Collection<? extends OMGeometry> c) {
setNeedToRegenerate(true);
synchronized (graphics) {
return graphics.addAll(index, c);
}
}
public boolean removeAll(Collection<?> c) {
setNeedToRegenerate(true);
return super.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
setNeedToRegenerate(true);
return super.retainAll(c);
}
public OMGeometry get(int index) {
synchronized (graphics) {
return graphics.get(index);
}
}
}