// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.drawable; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import net.sf.sdedit.config.Configuration; import net.sf.sdedit.diagram.Lifeline; import net.sf.sdedit.message.BroadcastMessage; import net.sf.sdedit.message.Message; import net.sf.sdedit.message.Primitive; import net.sf.sdedit.util.Direction; /** * An arrow is the graphical representation of a message. * * @author Markus Strauch */ public class Arrow extends SequenceElement { private final ArrowStroke stroke; private final ArrowHeadType headType; private Message message; private int space; private Point[] pts; private Point textPoint; protected Arrow(Message message, Lifeline boundary0, Lifeline boundary1, ArrowStroke stroke, Direction align, int y) { super(message.getDiagram(), boundary0, boundary1, message.getText() .split("\\\\n"), align, y); this.message = message; int headSize; this.stroke = stroke; if (stroke != ArrowStroke.NONE) { headSize = diagram.arrowSize; headType = message instanceof BroadcastMessage ? ArrowHeadType.ROUNDED : message.isSynchronous() ? ArrowHeadType.CLOSED : ArrowHeadType.OPEN; } else { // does not matter, but final field must be initialized headType = ArrowHeadType.CLOSED; headSize = 0; } int totalTextHeight = textHeight(); setHeight(totalTextHeight + configuration().getMessageLabelSpace() + diagram.arrowSize / 2); setWidth(headSize + leftPadding() + rightPadding() + diagram.messagePadding + textWidth()); } /** * Creates a new <tt>Arrow</tt>. * * @param message * the message that the arrow represents * @param stroke * the type of the arrow (solid, dashed, or invisible for * {@linkplain Primitive} messages * @param align * denotes the direction of the arrow * @param y * the vertical position of the arrow */ public Arrow(Message message, ArrowStroke stroke, Direction align, int y) { this(message, message.getCaller(), message.getCallee(), stroke, align, y); } /** * Returns an array of two points, representing the end points of the arrow. * The first entry is the point where the arrow starts, the second is where * the arrow ends. * * @return an array of two points, representing the end points of the arrow */ public final Point[] getPoints() { return pts; } /** * Returns the point where the message text is written. * * @return the point where the message text is written */ public Point getTextPosition() { return textPoint; } /** * Returns the sum of the height of the text and the space between the text * and the arrow. In other words, this is the height minus the half height * of the arrow head. * * @return the sum of the height of the text and the space between the text * and the arrow */ public int getInnerHeight() { return textHeight() + diagram.messageLabelSpace; } public static int getInnerHeight(Message message) { int l = message.getText().split("\\\\n").length; return message.getDiagram().getPaintDevice().getTextHeight() * l + message.getDiagram().getConfiguration() .getMessageLabelSpace(); } /** * Returns the amount of pixels between the end point of this arrow and the * border of the lifeline it reaches. This is 0 for ordinary arrows and a * positive number for constructor and destructor arrows. * * @return the amount of pixels between the end point of this arrow and the * left border of the lifeline it reaches */ public final int getSpace() { return space; } /** * Sets the amount of pixels between the end point of this arrow and the * border of the lifeline it reaches. This is 0 for ordinary arrows and a * positive number for constructor and destructor arrows. * * @param space * the amount of pixels between the end point of this arrow and * the left border of the lifeline it reaches */ public final void setSpace(int space) { this.space = space; } protected void overrideColor (Graphics2D g2d) { String col = message.getData().getProperty("colour"); if (col != null) { Color color = diagram.getPaintDevice().getColor(col); if (color != null) { g2d.setColor(color); } } } /** * @see net.sf.sdedit.drawable.Drawable#draw(java.awt.Graphics2D) */ @Override public void draw(Graphics2D g2d) { drawText(g2d); g2d.setColor(Color.BLACK); overrideColor(g2d); int sgn = getAlign() == Direction.LEFT ? 1 : -1; if (stroke != ArrowStroke.NONE) { g2d.setStroke(stroke == ArrowStroke.DASHED ? dashed : solid); g2d.drawLine(pts[0].x, pts[0].y, pts[1].x, pts[1].y); g2d.setStroke(solid); drawArrowHead(g2d, pts[1].x, pts[1].y, sgn); if (message.getCaller().isExternal()) { int as = diagram.arrowSize; int offset = sgn == -1 ? as : 0; g2d.fillOval(pts[0].x - offset, pts[0].y - as / 2, as, as); } } } protected void drawText(Graphics2D g2d) { int t = getMessage().getData().getThread(); Color back = getMessage().getData().getMessage().length() == 0 || !configuration().isOpaqueMessageText() || t == -1 ? null : THREAD_COLORS[t]; drawMultilineString(g2d, textPoint.x, textPoint.y, true, back); } /** * Returns the point in the middle of this arrow, the one that serves as an * anchor when a connection to a {@linkplain Note} is made. * * @return the point in the middle of this arrow */ public Point getAnchor() { return new Point(pts[0].x + (pts[1].x - pts[0].x) / 2, pts[0].y); } /** * @see net.sf.sdedit.drawable.Drawable#computeLayoutInformation() */ public void computeLayoutInformation() { int left = getLeftEndpoint().getLeft() + getLeftEndpoint().getWidth(); int right = getRightEndpoint().getLeft(); Direction align = getAlign(); if (align == Direction.LEFT) { left = left + space; } else { right = right - space; } setLeft(left); setWidth(Math.max(getWidth(), right - left)); int x_from = align == Direction.LEFT ? getLeft() + getWidth() : getLeft(); int x_to = align == Direction.LEFT ? getLeft() : getLeft() + getWidth(); int sgn = align == Direction.LEFT ? 1 : -1; int text_x = sgn == 1 ? x_from - diagram.messagePadding - textWidth() - rightPadding() : x_from + diagram.messagePadding + leftPadding(); int v = getTop() + textHeight() + diagram.messageLabelSpace; pts = new Point[2]; pts[0] = new Point(x_from, v); pts[1] = new Point(x_to, v); textPoint = new Point(text_x, v - diagram.messageLabelSpace); } /** * Draws the head of a message arrow onto the diagram display. * * @param g * the graphics context of the diagram display * @param closed * flag denoting whether the arrow head is closed (filled) or not * @param x * the horizontal position where to start drawing the arrow * @param y * the vertical position of the middle point of the arrow * @param sgn * 1, if the arrow is directed from the right to the left, -1 * otherwise */ protected final void drawArrowHead(Graphics2D g, int x, int y, int sgn) { g.setStroke(solid); int size = diagram.arrowSize; switch (headType) { case CLOSED: Polygon p = new Polygon(new int[] { x, x + sgn * size, x + sgn * size, x }, new int[] { y, y - size, y + size, y }, 4); g.fillPolygon(p); break; case OPEN: g.drawLine(x, y, x + sgn * size, y - size); g.drawLine(x, y, x + sgn * size, y + size); break; case ROUNDED: int left = sgn == -1 ? x - 2 * size : x; int top = y - size; g.fillArc(left, top, size * 2, size * 2, 90, sgn * 180); } } /** * Returns the <tt>Message</tt> that this arrow represents. * * @return the <tt>Message</tt> that this arrow represents */ public final Message getMessage() { return message; } protected final ArrowStroke getStroke() { return stroke; } }