PolyLineFigure.java
/*
* @(#)PolyLineFigure.java
*
* Project: JHotdraw - a GUI framework for technical drawings
* http://www.jhotdraw.org
* http://jhotdraw.sourceforge.net
* Copyright: © by the original author(s) and all contributors
* License: Lesser GNU Public License (LGPL)
* http://www.opensource.org/licenses/lgpl-license.html
*/
package CH.ifa.draw.figures;
import java.awt.*;
import java.util.*;
import java.io.IOException;
import CH.ifa.draw.framework.*;
import CH.ifa.draw.standard.*;
import CH.ifa.draw.util.*;
/**
* A poly line figure consists of a list of points.
* It has an optional line decoration at the start and end.
*
* @see LineDecoration
*
* @version <$CURRENT_VERSION$>
*/
public class PolyLineFigure extends AbstractFigure {
public final static int ARROW_TIP_NONE = 0;
public final static int ARROW_TIP_START = 1;
public final static int ARROW_TIP_END = 2;
public final static int ARROW_TIP_BOTH = 3;
protected Vector fPoints;
protected LineDecoration fStartDecoration = null;
protected LineDecoration fEndDecoration = null;
protected Color fFrameColor = Color.black;
/*
* Serialization support.
*/
private static final long serialVersionUID = -7951352179906577773L;
private int polyLineFigureSerializedDataVersion = 1;
public PolyLineFigure() {
fPoints = new Vector(4);
}
public PolyLineFigure(int size) {
fPoints = new Vector(size);
}
public PolyLineFigure(int x, int y) {
fPoints = new Vector();
fPoints.addElement(new Point(x, y));
}
public Rectangle displayBox() {
Enumeration k = points();
Rectangle r = new Rectangle((Point) k.nextElement());
while (k.hasMoreElements()) {
r.add((Point) k.nextElement());
}
return r;
}
public boolean isEmpty() {
return (size().width < 3) && (size().height < 3);
}
public Vector handles() {
Vector handles = new Vector(fPoints.size());
for (int i = 0; i < fPoints.size(); i++) {
handles.addElement(new PolyLineHandle(this, locator(i), i));
}
return handles;
}
public void basicDisplayBox(Point origin, Point corner) {
}
/**
* Adds a node to the list of points.
*/
public void addPoint(int x, int y) {
fPoints.addElement(new Point(x, y));
changed();
}
public Enumeration points() {
return fPoints.elements();
}
public int pointCount() {
return fPoints.size();
}
protected void basicMoveBy(int dx, int dy) {
Enumeration k = fPoints.elements();
while (k.hasMoreElements()) {
((Point) k.nextElement()).translate(dx, dy);
}
}
/**
* Changes the position of a node.
*/
public void setPointAt(Point p, int i) {
willChange();
fPoints.setElementAt(p, i);
changed();
}
/**
* Insert a node at the given point.
*/
public void insertPointAt(Point p, int i) {
fPoints.insertElementAt(p, i);
changed();
}
public void removePointAt(int i) {
willChange();
fPoints.removeElementAt(i);
changed();
}
/**
* Splits the segment at the given point if a segment was hit.
* @return the index of the segment or -1 if no segment was hit.
*/
public int splitSegment(int x, int y) {
int i = findSegment(x, y);
if (i != -1) {
insertPointAt(new Point(x, y), i+1);
}
return i+1;
}
public Point pointAt(int i) {
return (Point)fPoints.elementAt(i);
}
/**
* Joins to segments into one if the given point hits a node
* of the polyline.
* @return true if the two segments were joined.
*/
public boolean joinSegments(int x, int y) {
for (int i= 1; i < fPoints.size()-1; i++) {
Point p = pointAt(i);
if (Geom.length(x, y, p.x, p.y) < 3) {
removePointAt(i);
return true;
}
}
return false;
}
public Connector connectorAt(int x, int y) {
return new PolyLineConnector(this);
}
/**
* Sets the start decoration.
*/
public void setStartDecoration(LineDecoration l) {
fStartDecoration = l;
}
/**
* Returns the start decoration.
*/
public LineDecoration getStartDecoration() {
return fStartDecoration;
}
/**
* Sets the end decoration.
*/
public void setEndDecoration(LineDecoration l) {
fEndDecoration = l;
}
/**
* Returns the end decoration.
*/
public LineDecoration getEndDecoration() {
return fEndDecoration;
}
public void draw(Graphics g) {
g.setColor(getFrameColor());
Point p1, p2;
for (int i = 0; i < fPoints.size()-1; i++) {
p1 = (Point) fPoints.elementAt(i);
p2 = (Point) fPoints.elementAt(i+1);
drawLine(g, p1.x, p1.y, p2.x, p2.y);
}
decorate(g);
}
/**
* Can be overriden in subclasses to draw different types of lines
* (e.g. dotted lines)
*/
protected void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
g.drawLine(x1, y1, x2, y2);
}
public boolean containsPoint(int x, int y) {
Rectangle bounds = displayBox();
bounds.grow(4,4);
if (!bounds.contains(x, y)) {
return false;
}
Point p1, p2;
for (int i = 0; i < fPoints.size()-1; i++) {
p1 = (Point) fPoints.elementAt(i);
p2 = (Point) fPoints.elementAt(i+1);
if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, x, y)) {
return true;
}
}
return false;
}
/**
* Gets the segment of the polyline that is hit by
* the given point.
* @return the index of the segment or -1 if no segment was hit.
*/
public int findSegment(int x, int y) {
Point p1, p2;
for (int i = 0; i < fPoints.size()-1; i++) {
p1 = (Point) fPoints.elementAt(i);
p2 = (Point) fPoints.elementAt(i+1);
if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, x, y)) {
return i;
}
}
return -1;
}
private void decorate(Graphics g) {
if (getStartDecoration() != null) {
Point p1 = (Point)fPoints.elementAt(0);
Point p2 = (Point)fPoints.elementAt(1);
getStartDecoration().draw(g, p1.x, p1.y, p2.x, p2.y);
}
if (getEndDecoration() != null) {
Point p3 = (Point)fPoints.elementAt(fPoints.size()-2);
Point p4 = (Point)fPoints.elementAt(fPoints.size()-1);
getEndDecoration().draw(g, p4.x, p4.y, p3.x, p3.y);
}
}
/**
* Gets the attribute with the given name.
* PolyLineFigure maps "ArrowMode"to a
* line decoration.
*/
public Object getAttribute(String name) {
if (name.equals("FrameColor")) {
return getFrameColor();
}
else if (name.equals("ArrowMode")) {
int value = 0;
if (getStartDecoration() != null) {
value |= ARROW_TIP_START;
}
if (getEndDecoration() != null) {
value |= ARROW_TIP_END;
}
return new Integer(value);
}
return super.getAttribute(name);
}
/**
* Sets the attribute with the given name.
* PolyLineFigure interprets "ArrowMode"to set
* the line decoration.
*/
public void setAttribute(String name, Object value) {
if (name.equals("FrameColor")) {
setFrameColor((Color)value);
changed();
}
else if (name.equals("ArrowMode")) {
Integer intObj = (Integer) value;
if (intObj != null) {
int decoration = intObj.intValue();
if ((decoration & ARROW_TIP_START) != 0) {
setStartDecoration(new ArrowTip());
}
else {
setStartDecoration(null);
}
if ((decoration & ARROW_TIP_END) != 0) {
setEndDecoration(new ArrowTip());
}
else {
setEndDecoration(null);
}
}
changed();
}
else {
super.setAttribute(name, value);
}
}
public void write(StorableOutput dw) {
super.write(dw);
dw.writeInt(fPoints.size());
Enumeration k = fPoints.elements();
while (k.hasMoreElements()) {
Point p = (Point) k.nextElement();
dw.writeInt(p.x);
dw.writeInt(p.y);
}
dw.writeStorable(fStartDecoration);
dw.writeStorable(fEndDecoration);
dw.writeColor(fFrameColor);
}
public void read(StorableInput dr) throws IOException {
super.read(dr);
int size = dr.readInt();
fPoints = new Vector(size);
for (int i=0; i<size; i++) {
int x = dr.readInt();
int y = dr.readInt();
fPoints.addElement(new Point(x,y));
}
setStartDecoration((LineDecoration)dr.readStorable());
setEndDecoration((LineDecoration)dr.readStorable());
fFrameColor = dr.readColor();
}
/**
* Creates a locator for the point with the given index.
*/
public static Locator locator(int pointIndex) {
return new PolyLineLocator(pointIndex);
}
protected Color getFrameColor() {
return fFrameColor;
}
protected void setFrameColor(Color c) {
fFrameColor = c;
}
}