FigClassifierRole.java
/* $Id$
*****************************************************************************
* Copyright (c) 2009-2010 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bobtarling
* Michiel van der Wulp
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2007-2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.sequence2.diagram;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.argouml.notation.NotationProviderFactory2;
import org.argouml.uml.diagram.DiagramSettings;
import org.argouml.uml.diagram.ui.FigEmptyRect;
import org.argouml.uml.diagram.ui.FigNodeModelElement;
import org.tigris.gef.base.Geometry;
import org.tigris.gef.base.Selection;
import org.tigris.gef.presentation.Fig;
import org.tigris.gef.presentation.FigEdge;
import org.tigris.gef.presentation.FigRect;
/**
* The Fig that represents a Classifier Role
* @author penyaskito
*/
public class FigClassifierRole extends FigNodeModelElement {
/**
* This is an empty rectangle placed above HeadFig. It creates a space
* between where the classifierRole would normally start and where it
* starts as the result of a create message.
*/
private FigEmptyRect emptyFig;
/**
* This is the box head of the classifierRole containing the notation
* and stereotypes.
*/
private FigHead headFig;
/**
* This is the dashed lifeline under FigHead that contain the activation
* blocks.
*/
private FigLifeLine lifeLineFig;
// TODO: Do we need this? Is this the same as emptyFig.getHeight()?
private int offset = 0;
/**
* The minimum height of the classifier role.
*/
private int minimumHeight;
/**
* Construct a use case figure with the given owner, bounds, and rendering
* settings. This constructor is used by the PGML parser.
*
* @param owner owning model element
* @param bounds position and size
* @param settings rendering settings
*/
public FigClassifierRole(Object owner, Rectangle bounds,
DiagramSettings settings) {
super(owner, bounds, settings);
initialize();
if (bounds != null) {
setLocation(bounds.x, bounds.y);
}
}
@Override
protected Fig createBigPortFig() {
return new FigClassifierRolePort();
}
/**
* Initialization which is common to multiple constructors.
*/
private void initialize() {
emptyFig = new FigEmptyRect(getX(), getY(), getWidth(), offset);
emptyFig.setLineWidth(0);
headFig = new FigHead(getOwner(), getSettings(), getStereotypeFig(),
getNameFig());
headFig.setBounds(getX(), getY() + offset,
getWidth(), headFig.getHeight());
lifeLineFig = new FigLifeLine(getOwner(), new Rectangle(headFig.getX(),
getY() + offset + headFig.getHeight() - getLineWidth(), 0, 0),
getSettings());
addFig(getBigPort());
getBigPort().setVisible(false);
// TODO: Move magic number 10 to descriptive constant
minimumHeight = headFig.getMinimumHeight() + 10;
addFig(emptyFig);
addFig(lifeLineFig);
addFig(headFig);
createActivations();
}
/**
* The NotationProvider for the ClassifierRole. <p>
*
* The syntax is for UML is:
* <pre>
* baselist := [base] [, base]*
* classifierRole := [name] [/ role] [: baselist]
* </pre></p>
*
* The <code>name</code> is the Instance name, not used currently.
* See ClassifierRoleNotationUml for details.<p>
*
* This syntax is compatible with the UML 1.4 specification.
* @return TYPE_CLASSIFIERROLE
*/
@Override
protected int getNotationProviderType() {
return NotationProviderFactory2.TYPE_CLASSIFIERROLE;
}
@Override
protected void setBoundsImpl(
final int x, final int y,
final int w, final int h) {
final Rectangle oldBounds = getBounds();
final int ww = Math.max(w, headFig.getMinimumSize().width);
emptyFig.setBounds(x, y, ww, offset);
headFig.setBounds(x, y + offset, ww, headFig.getMinimumHeight());
lifeLineFig.setBounds(x,
y + offset + headFig.getHeight() - lifeLineFig.getLineWidth(),
ww, h - offset - headFig.getHeight());
getBigPort().setBounds(x, y, ww, h);
_x = x;
_y = y;
_w = w;
_h = h;
//TODO: I suspect this isn't needed call isn't needed but don't remove
// till out of alpha/beta stage
updateEdges();
//
firePropChange("bounds", oldBounds, getBounds());
}
/*
* This method is overridden in order to ignore change of the y coordinate
* during dragging.
*
* @see org.tigris.gef.presentation.FigNode#superTranslate(int, int)
*/
public void superTranslate(int dx, int dy) {
super.superTranslate(dx, 0);
}
/**
* Updates the head offset, looking for the create messages.
*/
private void updateHeadOffset() {
FigMessage createMessage = getFirstCreateFigMessage();
if (createMessage != null) {
int y = createMessage.getFirstPoint().y;
if (y > 0) {
offset = y - (getY() + headFig.getHeight() / 2);
}
} else {
offset = 0;
}
}
/**
* Gets the first create message received by the classifier role
* @return a figmessage.
*/
private FigMessage getFirstCreateFigMessage() {
List<FigMessage> messages = getFigMessages();
FigMessage createMessage = null;
for (FigMessage message : messages) {
if (message.getDestFigNode().equals(this)
&& message.isCreateMessage()) {
createMessage = message;
break;
}
}
return createMessage;
}
/**
* Gets the minimum size of the Fig.<p>
*
* The width is restricted by the notation making sure that the full
* classifier role description is displayed.<p>
*
* The minimum height is restricted so that the all attached message will
* remain in the same position relative to the Fig. If no messages are
* attached then the minimum height will ensure box is shown plus at least
* 10 pixels of the lifeline.
*
* @return dimensions of the minimum size
*/
public Dimension getMinimumSize() {
/**
* TODO: minimum height should not be calculated every time, but only
* when an FigMessage has been added or removed.
* Currently doing that doesn't work because of an unknown problem.
* How to test: create only two CRs and a create message between them.
* Then move the create message to the bottom!
* Until that is fixed the workaround is to call updateMinimumHeight()
* every time the minimum size is needed
*/
updateMinimumHeight();
return new Dimension(headFig.getMinimumWidth(), minimumHeight);
}
/**
* Updates the minimum height of the classifier role when a FigMessage
* is added or removed.
*/
private void updateMinimumHeight() {
int yMax = getY();
List<FigEdge> figsEdges = getFigEdges();
FigMessage createMessage = getFirstCreateFigMessage();
// TODO: Is this next line safe? What happens if there is just one
// comment edge or a comment edge and a single message?
if (figsEdges.size() == 1 && createMessage != null) {
// TODO: Move magic number 10 to descriptive constant
minimumHeight = headFig.getMinimumSize().height + offset + 10;
} else {
for (Fig fig : figsEdges) {
if ( fig instanceof FigMessage
// we need the edge to be complete
&& ((FigMessage) fig).getDestFigNode() != null
&& ((FigMessage) fig).getSourceFigNode() != null
&& ((FigMessage) fig).getY() > yMax) {
yMax = ((FigMessage) fig).getY();
}
}
// TODO: Move magic number 10 to descriptive constant
minimumHeight = yMax - getY() + 10;
}
}
/**
* Override ancestor behaviour by always calling setBounds even if the
* size hasn't changed. Without this override the Package bounds draw
* incorrectly. This is not the best fix but is a workaround until the
* true cause is known. See issue 6135.
*
* @see org.argouml.uml.diagram.ui.FigNodeModelElement#updateBounds()
*/
protected void updateBounds() {
if (!isCheckSize()) {
return;
}
Rectangle bbox = getBounds();
Dimension minSize = getMinimumSize();
bbox.width = Math.max(bbox.width, minSize.width);
bbox.height = Math.max(bbox.height, minSize.height);
setBounds(bbox.x, bbox.y, bbox.width, bbox.height);
}
@Override
public void removeFigEdge(FigEdge edge) {
super.removeFigEdge(edge);
if (edge instanceof FigMessage) {
final FigMessage figMessage = (FigMessage) edge;
positionHead(figMessage);
createActivations();
}
}
@Override
public void addFigEdge(FigEdge edge) {
super.addFigEdge(edge);
if (edge instanceof FigMessage) {
FigMessage mess = (FigMessage) edge;
if (mess.isSelfMessage()) {
mess.convertToArc();
}
positionHead(mess);
}
}
/**
* Position the head at the top of the lifeline so that it is at the top
* of the FigClassifierRole or where the create message enters the
* FigClassifierRole
*/
void positionHead(final FigMessage message) {
// if the added edge is a Create Message it will affect the position
// of the ClassifierRole so it should be repositioned
if (message.isCreateMessage()
&& equals(message.getDestFigNode())
&& !equals(message.getSourceFigNode())) {
updateHeadOffset();
setBounds(getX(), getY(), getWidth(), getHeight());
}
}
/**
* Return an ordered list of message edges that are complete (ie the user
* has finished drawing). Messages are ordered from top to bottom.
* @return A list with all the messages that are complete
*/
public List<FigMessage> getFigMessages() {
final List<FigMessage> completeMessages = new ArrayList<FigMessage>(10);
for (Object o : getFigEdges()) {
if (o instanceof FigMessage) {
final FigMessage fm = (FigMessage) o;
if (fm.getPoints().length > 1
&& fm.getDestFigNode() != null
&& fm.getSourceFigNode() != null) {
completeMessages.add(fm);
}
}
}
Collections.sort(completeMessages, new FigMessageComparator());
return completeMessages;
}
void createActivations() {
lifeLineFig.createActivations(getFigMessages());
forceRepaintShadow();
}
@Override
public void setFillColor(Color color) {
headFig.setFillColor(color);
lifeLineFig.setFillColor(color);
}
@Override
public Color getFillColor() {
return headFig.getFillColor();
}
@Override
public void setFilled(boolean filled) {
headFig.setFilled(filled);
}
@Override
public void setLineWidth(int w) {
headFig.setLineWidth(w);
getBigPort().setLineWidth(0);
}
/**
* This class represents the port of the FigClassifierRole.
* It has the logic for locating the messages.
*
* @author penyaskito
*/
class FigClassifierRolePort extends FigRect {
/**
*
*/
FigClassifierRolePort() {
super(0, 0, 0, 0, null, null);
setLineWidth(0);
}
@Override
public Point getClosestPoint(Point anotherPt) {
int width = FigActivation.DEFAULT_WIDTH;
int y = anotherPt.y;
// the initial x is the left side of the activation
int x = getX() + getWidth() / 2 - width / 2;
// put the x at the right side of the activation if needed
if (anotherPt.x > x + width) {
x = x + width;
}
// if the y coordinate is before the classifier role y,
// we place the start in the corner of the fighead.
if (y < getY()) {
y = getY();
x = Geometry.ptClosestTo(headFig.getBounds(), anotherPt).x;
}
// if the y coordinate is inside the head,
// the x must be in the border of the headFig.
else if (y < lifeLineFig.getY()) {
x = Geometry.ptClosestTo(headFig.getBounds(), anotherPt).x;
}
// else if the y coordinate is outside of the classifier role,
// we fix the y in the max y of the classifier role.
// FIXME: We should increase the height of the classifier role,
// don't???
else if (y > getY() + getHeight()) {
y = headFig.getY() + FigClassifierRole.this.getHeight();
}
return new Point(x, y);
}
}
public Selection makeSelection() {
return new SelectionClassifierRole(this);
}
}