/* Violet - A program for editing UML diagrams. Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com) Alexandre de Pellegrin (http://alexdp.free.fr); 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.product.diagram.sequence.node; import java.awt.*; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import com.horstmann.violet.framework.graphics.content.*; import com.horstmann.violet.framework.graphics.shape.ContentInsideCustomShape; import com.horstmann.violet.product.diagram.abstracts.Direction; import com.horstmann.violet.product.diagram.abstracts.edge.IEdge; import com.horstmann.violet.product.diagram.abstracts.node.AbstractNode; import com.horstmann.violet.product.diagram.common.node.ColorableNode; import com.horstmann.violet.product.diagram.abstracts.node.INode; import com.horstmann.violet.product.diagram.sequence.SequenceDiagramConstant; import com.horstmann.violet.product.diagram.sequence.edge.CallEdge; import com.horstmann.violet.product.diagram.sequence.edge.ReturnEdge; /** * An activation bar in a sequence diagram. This activation bar is hang on a lifeline (implicit parameter) * * @author Adrian Bobrowski <adrian071993@gmail.com> */ public class ActivationBarNode extends ColorableNode { protected static class ActivationBarShape implements ContentInsideCustomShape.ShapeCreator { @Override public Shape createShape(double contentWidth, double contentHeight) { return new Rectangle2D.Double(0,0, WIDTH, contentHeight); } } public ActivationBarNode() { super(); createContentStructure(); refreshPositionAndSize(); } protected ActivationBarNode(ActivationBarNode node) throws CloneNotSupportedException { super(node); createContentStructure(); refreshPositionAndSize(); } @Override protected void afterReconstruction() { for(INode child : getChildren()) { if (child instanceof ActivationBarNode) { child.reconstruction(); activationsGroup.add(((ActivationBarNode) child).getContent()); onChildChangeLocation(child); } } super.afterReconstruction(); } @Override protected INode copy() throws CloneNotSupportedException { return new ActivationBarNode(this); } @Override protected void createContentStructure() { activationsGroup = new RelativeLayout(); EmptyContent padding = new EmptyContent(); padding.setMinHeight(CHILD_VERTICAL_MARGIN); VerticalLayout verticalLayout = new VerticalLayout(); verticalLayout.add(padding); verticalLayout.add(activationsGroup); verticalLayout.add(padding); verticalLayout.setMinHeight(MIN_HEIGHT); verticalLayout.setMinWidth(WIDTH); ContentInsideShape contentInsideShape = new ContentInsideCustomShape(verticalLayout, new ActivationBarShape()); setBorder(new ContentBorder(contentInsideShape, getBorderColor())); setBackground(new ContentBackground(getBorder(), getBackgroundColor())); setContent(getBackground()); setTextColor(getTextColor()); } @Override public String getToolTip() { return SequenceDiagramConstant.SEQUENCE_DIAGRAM_RESOURCE.getString("tooltip.activation_bar_node"); } @Override public void removeChild(INode node) { activationsGroup.remove(((ActivationBarNode) node).getContent()); super.removeChild(node); refreshSize(); } @Override public boolean addChild(INode node, Point2D point) { if (!(node instanceof ActivationBarNode)) { return false; } addChild(node, getChildren().size()); ActivationBarNode activationBarNode = (ActivationBarNode) node; activationBarNode.setTextColor(getTextColor()); activationBarNode.setBackgroundColor(getBackgroundColor()); activationBarNode.setBorderColor(getBorderColor()); activationsGroup.add(activationBarNode.getContent()); activationBarNode.setLocation(point); activationBarNode.setGraph(getGraph()); activationBarNode.setParent(this); refreshSize(); return true; } @Override protected void onChildChangeLocation(INode child) { activationsGroup.setPosition(((AbstractNode) child).getContent(), getChildRelativeLocation(child)); } protected Point2D getChildRelativeLocation(INode node) { Point2D nodeLocation = node.getLocation(); if(CHILD_LEFT_MARGIN != nodeLocation.getX() || CHILD_VERTICAL_MARGIN > nodeLocation.getY()) { nodeLocation.setLocation(CHILD_LEFT_MARGIN, Math.max(nodeLocation.getY(), CHILD_VERTICAL_MARGIN)); node.setLocation(nodeLocation); } return new Point2D.Double(nodeLocation.getX() + CHILD_LEFT_MARGIN, nodeLocation.getY() - CHILD_VERTICAL_MARGIN); } @Override public void onConnectedEdge(IEdge connectedEdge) { refreshPositionAndSize(); } @Override public boolean addConnection(IEdge edge) { if(null == edge.getEndNode()) { return false; } if (edge instanceof CallEdge) { return isCallEdgeAcceptable((CallEdge) edge); } if (edge instanceof ReturnEdge) { return isReturnEdgeAcceptable((ReturnEdge) edge); } return false; } @Override public void removeConnection(IEdge edge) { if (edge instanceof CallEdge) { for(IEdge connectedEdge : getConnectedEdges()) { if(connectedEdge instanceof ReturnEdge && edge.getStartNode() == connectedEdge.getEndNode() && edge.getEndNode() == connectedEdge.getStartNode()) { getGraph().removeEdge(connectedEdge); break; } } } } @Override public Point2D getConnectionPoint(IEdge edge) { Direction edgeDirection = edge.getDirection(this); Point2D startingNodeLocation = getLocationOnGraph(); double x = startingNodeLocation.getX(); double y = startingNodeLocation.getY(); if (0 <= edgeDirection.getX()) { x+= WIDTH; } if(edge instanceof CallEdge) { if (edge.getEndNode() instanceof LifelineNode) { y += CALL_Y_GAP / 2; } else if (null != edge.getStartNode().getParent() && null != edge.getEndNode().getParent() && edge.getStartNode().getParents().get(0) == edge.getEndNode().getParents().get(0)) { if (0 > edgeDirection.getX()) { x += WIDTH; } if(this == edge.getStartNode()) { y += edge.getEndNode().getLocation().getY() - CALL_Y_GAP /2; } } else if(this == edge.getStartNode()) { y = edge.getEndNode().getLocationOnGraph().getY(); } } else if(edge instanceof ReturnEdge) { if(this == edge.getStartNode()) { y += getContent().getHeight(); } else if(this == edge.getEndNode()) { y = edge.getStartNode().getLocationOnGraph().getY() + edge.getStartNode().getBounds().getHeight(); } } return new Point2D.Double(x, y); } @Override public Rectangle2D getBounds() { refreshPositionAndSize(); return super.getBounds(); } private void refreshPosition() { setLocation(calculateLocation()); } private void refreshSize() { activationsGroup.setMinHeight((int)Math.max(calculateHeight(), MIN_HEIGHT)); } private void refreshPositionAndSize() { refreshPosition(); refreshSize(); } private Point2D calculateLocation() { double y = this.getLocation().getY(); for (IEdge edge : getGraph().getAllEdges()) { if (edge instanceof CallEdge && edge.getEndNode() instanceof ActivationBarNode) { if (edge.getStartNode() == this) { y = Math.min(y, edge.getEndNode().getLocationOnGraph().getY() - 5); } } } return new Point.Double(this.getLocation().getX(), y); } private double calculateHeight() { double height = 0; for (IEdge edge : getGraph().getAllEdges()) { if (edge instanceof CallEdge) { if (edge.getStartNode() == this && edge.getStartNode() != getParent()) { INode endingNode = edge.getEndNode(); if (endingNode instanceof ActivationBarNode) { Rectangle2D endingNodeBounds = endingNode.getBounds(); double newHeight = endingNodeBounds.getHeight() + (endingNode.getLocationOnGraph().getY() - this.getLocationOnGraph().getY()); height = Math.max(height, newHeight); } } } } return Math.max(MIN_HEIGHT, height); } private boolean isReturnEdgeAcceptable(ReturnEdge edge) { INode start = edge.getStartNode(); INode end = edge.getEndNode(); if (null != start.getParent() && null != end.getParent() && start.getParents().get(0) == end.getParents().get(0)) { return false; } for (IEdge connectedEdge : getConnectedEdges()) { if(start == connectedEdge.getEndNode() && end == connectedEdge.getStartNode()) { return true; } } return false; } private boolean isCallEdgeAcceptable(CallEdge edge) { INode start = edge.getStartNode(); INode end = edge.getEndNode(); for (IEdge connectedEdge : getConnectedEdges()) { if(start == connectedEdge.getStartNode() && end == connectedEdge.getEndNode() || end == connectedEdge.getStartNode() && start == connectedEdge.getEndNode() ) { return false; } } if (start instanceof ActivationBarNode && end instanceof ActivationBarNode) { if(start.getParents().get(0) != end.getParents().get(0)) { return true; } else if(start == end) { ActivationBarNode newActivationBar = new ActivationBarNode(); Point2D location = edge.getStartLocation(); Point2D newActivationBarLocation = new Point2D.Double(location.getX(), location.getY() + CALL_Y_GAP / 2); start.addChild(newActivationBar, newActivationBarLocation); edge.setEndNode(newActivationBar); return true; } else if(end.getParents().contains(start)) { return true; } else if(start.getParents().contains(end)) { return false; } } if (end instanceof LifelineNode) { if(start instanceof ActivationBarNode && end != start.getParents().get(0)) { if(edge.getEndLocation().getY() < end.getBounds().getY() + LifelineNode.TOP_HEIGHT) { edge.setCenterLabel(CENTER_LABEL); return true; } ActivationBarNode newActivationBar = new ActivationBarNode(); Point2D location = edge.getEndLocation(); Point2D newActivationBarLocation = new Point2D.Double(location.getX(), location.getY()); end.addChild(newActivationBar, newActivationBarLocation); edge.setEndNode(newActivationBar); return true; } } return false; } private transient RelativeLayout activationsGroup = null; public static final int WIDTH = 16; public static final int MIN_HEIGHT = 15; public static final int CHILD_LEFT_MARGIN = 6; public static final int CHILD_VERTICAL_MARGIN = 10; public static final int CALL_Y_GAP = 20; public static final String CENTER_LABEL = "«create»"; }