/*
Violet - A program for editing UML diagrams.
Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.horstmann.violet;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.horstmann.violet.framework.Direction;
import com.horstmann.violet.framework.Edge;
import com.horstmann.violet.framework.Graph;
import com.horstmann.violet.framework.Grid;
import com.horstmann.violet.framework.Node;
import com.horstmann.violet.framework.RectangularNode;
/**
A method call node in a scenario diagram.
*/
public class CallNode extends RectangularNode
{
/**
Construct a call node with a default size
*/
public CallNode()
{
setBounds(new Rectangle2D.Double(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));
}
public void draw(Graphics2D g2)
{
super.draw(g2);
Color oldColor = g2.getColor();
g2.setColor(Color.WHITE);
g2.fill(getBounds());
g2.setColor(oldColor);
if (openBottom)
{
Rectangle2D b = getBounds();
double x1 = b.getX();
double x2 = x1 + b.getWidth();
double y1 = b.getY();
double y3 = y1 + b.getHeight();
double y2 = y3 - CALL_YGAP;
g2.draw(new Line2D.Double(x1, y1, x2, y1));
g2.draw(new Line2D.Double(x1, y1, x1, y2));
g2.draw(new Line2D.Double(x2, y1, x2, y2));
Stroke oldStroke = g2.getStroke();
g2.setStroke(new BasicStroke(1.0f,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND,
0.0f,
new float[] { 5.0f, 5.0f }, 0.0f));
g2.draw(new Line2D.Double(x1, y2, x1, y3));
g2.draw(new Line2D.Double(x2, y2, x2, y3));
g2.setStroke(oldStroke);
}
else
g2.draw(getBounds());
}
/**
Gets the implicit parameter of this call.
@return the implicit parameter node
*/
public ImplicitParameterNode getImplicitParameter()
{
return implicitParameter;
}
/**
Sets the implicit parameter of this call.
@param newValue the implicit parameter node
*/
public void setImplicitParameter(ImplicitParameterNode newValue)
{
implicitParameter = newValue;
}
public Point2D getConnectionPoint(Direction d)
{
if (d.getX() > 0)
return new Point2D.Double(getBounds().getMaxX(),
getBounds().getMinY());
else
return new Point2D.Double(getBounds().getX(),
getBounds().getMinY());
}
public boolean addEdge(Edge e, Point2D p1, Point2D p2)
{
Node end = e.getEnd();
if (end == null) return false;
if (e instanceof ReturnEdge) // check that there is a matching call
return end == getParent();
if (!(e instanceof CallEdge)) return false;
Node n = null;
if (end instanceof CallNode)
{
// check for cycles
Node parentNode = this;
while (parentNode != null && end != parentNode)
parentNode = parentNode.getParent();
if (end.getParent() == null && end != parentNode)
{
n = end;
}
else
{
CallNode c = new CallNode();
c.implicitParameter = ((CallNode)end).implicitParameter;
e.connect(this, c);
n = c;
}
}
else if (end instanceof ImplicitParameterNode)
{
if (((ImplicitParameterNode)end).getTopRectangle().contains(p2))
{
n = end;
((CallEdge)e).setMiddleLabel("\u00ABcreate\u00BB");
}
else
{
CallNode c = new CallNode();
c.implicitParameter = (ImplicitParameterNode)end;
e.connect(this, c);
n = c;
}
}
else return false;
int i = 0;
List calls = getChildren();
while (i < calls.size() && ((Node)calls.get(i)).getBounds().getY() <= p1.getY())
i++;
addChild(i, n);
return true;
}
public void removeEdge(Graph g, Edge e)
{
if (e.getStart() == this)
removeChild(e.getEnd());
}
public void removeNode(Graph g, Node n)
{
if (n == getParent() || n == implicitParameter)
g.removeNode(this);
}
private static Edge findEdge(Graph g, Node start, Node end)
{
Collection edges = g.getEdges();
Iterator iter = edges.iterator();
while (iter.hasNext())
{
Edge e = (Edge) iter.next();
if (e.getStart() == start && e.getEnd() == end) return e;
}
return null;
}
public void layout(Graph g, Graphics2D g2, Grid grid)
{
if (implicitParameter == null) return;
double xmid = implicitParameter.getBounds().getCenterX();
for (CallNode c = (CallNode)getParent();
c != null; c = (CallNode)c.getParent())
if (c.implicitParameter == implicitParameter)
xmid += getBounds().getWidth() / 2;
translate(xmid - getBounds().getCenterX(), 0);
double ytop = getBounds().getY() + CALL_YGAP;
List calls = getChildren();
for (int i = 0; i < calls.size(); i++)
{
Node n = (Node) calls.get(i);
if (n instanceof ImplicitParameterNode) // <<create>>
{
n.translate(0, ytop - ((ImplicitParameterNode) n).getTopRectangle().getCenterY());
ytop += ((ImplicitParameterNode)n).getTopRectangle().getHeight() / 2 + CALL_YGAP;
}
else if (n instanceof CallNode)
{
Edge callEdge = findEdge(g, this, n);
// compute height of call edge
if (callEdge != null)
{
Rectangle2D edgeBounds = callEdge.getBounds(g2);
ytop += edgeBounds.getHeight() - CALL_YGAP;
}
n.translate(0, ytop - n.getBounds().getY());
n.layout(g, g2, grid);
if (((CallNode) n).signaled)
ytop += CALL_YGAP;
else
ytop += n.getBounds().getHeight() + CALL_YGAP;
}
}
if (openBottom) ytop += 2 * CALL_YGAP;
Rectangle2D b = getBounds();
double minHeight = DEFAULT_HEIGHT;
Edge returnEdge = findEdge(g, this, getParent());
if (returnEdge != null)
{
Rectangle2D edgeBounds = returnEdge.getBounds(g2);
minHeight = Math.max(minHeight, edgeBounds.getHeight());
}
setBounds(new Rectangle2D.Double(b.getX(), b.getY(), b.getWidth(),
Math.max(minHeight, ytop - b.getY())));
}
public boolean addNode(Node n, Point2D p)
{
return n instanceof PointNode;
}
/**
Sets the signaled property.
@param newValue true if this node is the target of a signal edge
*/
public void setSignaled(boolean newValue) { signaled = newValue; }
/**
Gets the openBottom property.
@return true if this node is the target of a signal edge
*/
public boolean isOpenBottom() { return openBottom; }
/**
Sets the openBottom property.
@param newValue true if this node is the target of a signal edge
*/
public void setOpenBottom(boolean newValue) { openBottom = newValue; }
private ImplicitParameterNode implicitParameter;
private boolean signaled;
private boolean openBottom;
private static int DEFAULT_WIDTH = 16;
private static int DEFAULT_HEIGHT = 30;
public static int CALL_YGAP = 20;
}