// 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.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import net.sf.sdedit.diagram.Diagram;
import net.sf.sdedit.message.ConstructorMessage;
import net.sf.sdedit.message.Message;
import net.sf.sdedit.util.Pair;
/**
* A <tt>Fragment</tt> consists of a sequence of messages occuring in a
* diagram. It appears as a frame surrounding all of the messages. A fragment
* has a type, appearing in the top-left corner of the frame and a text,
* commonly interpreted as a condition. It appears in square brackets. Fragments
* can have sections, separated by dashed lines. Each section has its own
* condition text.
*
* @author Markus Strauch
*
*/
public class Fragment extends Drawable
{
/**
* The type of this fragment, appears in the top-left corner.
*/
private String type;
/**
* The text of the condition to appear in square brackets below the type.
*/
private String condition;
/**
* The diagram of which the fragment is a part.
*/
private Diagram diagram;
/**
* The sequence elements that are members of the fragment.
*/
private Set<SequenceElement> includedElements;
/**
* The space between the left border of the frame and the text representing
* the type.
*/
private int typeTextPadding;
/**
* The number of fragments that surround this fragment.
*/
private int level;
/**
* A list of pairs of vertical positions and texts of dashed separator
* lines, indicating new sections of the fragment.
*/
private List<Pair<Integer, String>> separators;
/**
* Creates a new <tt>Fragment</tt>. If <tt>type</tt> is empty, the
* condition appears in the top-left corner of the fragment, so it should
* rather be interpreted as a comment on the elements inside the fragment
* frame.
*
* @param type
* the type of the fragment, for example "alt",
* "opt" or "loop"
* @param condition
* the condition of the fragment
* @param diagram
* the diagram of which the fragment is a part
*/
public Fragment(String type, String condition, Diagram diagram) {
if (type.equals("")) {
this.type = condition + " ";
this.condition = "";
} else {
this.type = type + " ";
this.condition = condition.equals("") ? "" : "[" + condition + "]";
}
this.diagram = diagram;
includedElements = new HashSet<SequenceElement>();
typeTextPadding = diagram.getConfiguration().getFragmentTextPadding();
level = 0;
separators = new LinkedList<Pair<Integer, String>>();
}
/**
* Sets the level of this fragment (the number of fragments that this
* fragment is a part of) to the maximum of the current level and the given
* one.
*
* @param level
* the new level of this fragment, if it does not already have a
* higher one
*/
public void setLevel(int level) {
this.level = Math.max(this.level, level);
}
/**
* Returns the condition of this fragment (to be precise, of the first
* section of the fragment).
*
* @return the condition of this fragment
*/
public String getCondition() {
return condition;
}
/**
* Adds a sequence element that is a member of this fragment.
*
* @param element
* a member of this fragment
*/
public void addElement(SequenceElement element) {
includedElements.add(element);
}
/**
* Adds a section to this fragment with its own condition. It will be
* represented by a dashed line appearing at the current vertical position.
*
* @param sectionCondition
* the condition of the section, appearing in square brackets
* below the dashed line
*/
public void addSection(String sectionCondition) {
int h = diagram.getPaintDevice().getTextHeight(true);
diagram.extendLifelines(diagram.getConfiguration()
.getSeparatorTopMargin());
separators.add(new Pair<Integer, String>(diagram.getVerticalPosition(),
sectionCondition));
diagram.extendLifelines(h
+ diagram.getConfiguration().getSeparatorBottomMargin() + 5);
}
/**
* Returns true if the given sequence element is a member of this fragment.
*
* @param element
* a sequence element
* @return true if the element is a member of this fragment
*/
public boolean containsElement(SequenceElement element) {
return includedElements.contains(element);
}
/**
* @see net.sf.sdedit.drawable.Drawable#computeLayoutInformation()
*/
public void computeLayoutInformation() {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (SequenceElement element : includedElements) {
int left = element.getLeft();
int right = element.getLeft() + element.getWidth();
if (element instanceof Arrow) {
Message msg = ((Arrow) element).getMessage();
if (msg instanceof ConstructorMessage) {
if (msg.getCallee().getPosition() < msg.getCaller()
.getPosition()) {
left = msg.getCallee().getHead().getLeft();
} else {
right = msg.getCallee().getHead().getLeft()
+ msg.getCallee().getHead().getWidth();
}
}
if (((Arrow) element).getStroke() != ArrowStroke.NONE) {
right += element.getRightEndpoint().getWidth();
} // nothing to add for pseudo arrows (primitive messages)
// with
// ArrowStroke.NONE
}
min = Math.min(min, left);
max = Math.max(max, right);
}
int width = max - min;
int padding = diagram.getConfiguration().getFragmentPadding();
int margin = diagram.getConfiguration().getFragmentMargin();
setLeft(min - padding - level * padding);
setWidth(2 * level * padding + width + 2 * padding + margin);
}
/**
* Computes the height of the label of the fragment.
*
* @return the height of the label of the fragment
*/
public int getLabelHeight () {
return diagram.getPaintDevice().getTextHeight(true) + 2;
}
/**
* @see net.sf.sdedit.drawable.Drawable#draw(java.awt.Graphics2D)
*/
public void draw(Graphics2D g2d) {
int typeWidth = diagram.getPaintDevice().getTextWidth(type, true);
int textWidth = diagram.getPaintDevice().getTextWidth(condition, true);
int textHeight = diagram.getPaintDevice().getTextHeight(true);
int leftMargin = typeWidth + 4 + typeTextPadding;
g2d.setColor(Color.WHITE);
// clear the type corner
g2d.fillRect(getLeft(), getTop(), typeWidth + 4, textHeight + 2);
g2d.setColor(Color.BLACK);
g2d.setStroke(thick);
g2d.setFont(diagram.getPaintDevice().getFont(true));
g2d.drawRect(getLeft(), getTop(), getWidth(), getHeight());
if (!type.equals("")) {
g2d.drawLine(getLeft(), getTop() + textHeight + 2, getLeft()
+ typeWidth + typeTextPadding, getTop() + textHeight + 2);
g2d.drawLine(getLeft() + typeWidth + typeTextPadding, getTop()
+ textHeight + 2, getLeft() + typeWidth + typeTextPadding
+ 2, getTop() + textHeight - 2);
g2d.drawLine(getLeft() + typeWidth + typeTextPadding + 2, getTop()
+ textHeight - 2, getLeft() + typeWidth + typeTextPadding
+ 2, getTop());
g2d.drawString(type, getLeft() + typeTextPadding, getTop()
+ textHeight - 1);
}
if (condition.length() > 0) {
// clear the canvas under the text
g2d.setColor(Color.WHITE);
g2d.fillRect(getLeft() + leftMargin, getTop() + textHeight + 2,
textWidth, textHeight);
g2d.setColor(Color.BLACK);
g2d.drawString(condition, getLeft() + leftMargin, getTop() + 2
* textHeight - 1);
}
for (Pair<Integer, String> sep : separators) {
int y = sep.getFirst();
g2d.setStroke(thick_dashed);
g2d.drawLine(getLeft(), y, getRight(), y);
g2d.setStroke(solid);
g2d.drawString(sep.getSecond(), getLeft() + leftMargin, y
+ textHeight
+ diagram.getConfiguration().getSeparatorBottomMargin());
}
g2d.setFont(diagram.getPaintDevice().getFont(false));
}
}