/* $Id: FigAssociation.java 18724 2010-09-06 18:45:59Z bobtarling $
*****************************************************************************
* Copyright (c) 2009 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
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 1996-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.uml.diagram.ui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.argouml.model.AddAssociationEvent;
import org.argouml.model.AssociationChangeEvent;
import org.argouml.model.AttributeChangeEvent;
import org.argouml.model.Model;
import org.argouml.notation.NotationProviderFactory2;
import org.argouml.ui.ArgoJMenu;
import org.argouml.ui.targetmanager.TargetManager;
import org.argouml.uml.diagram.DiagramEdgeSettings;
import org.argouml.uml.diagram.DiagramSettings;
import org.tigris.gef.presentation.ArrowHead;
import org.tigris.gef.presentation.ArrowHeadComposite;
import org.tigris.gef.presentation.ArrowHeadDiamond;
import org.tigris.gef.presentation.ArrowHeadGreater;
import org.tigris.gef.presentation.ArrowHeadNone;
import org.tigris.gef.presentation.FigNode;
import org.tigris.gef.presentation.FigText;
/**
* This class represents the Fig of a binary association on a diagram.
*
*/
public class FigAssociation extends FigEdgeModelElement {
private static final Logger LOG = Logger.getLogger(FigAssociation.class);
/**
* Group for the FigTexts concerning the source association end.
*/
private EndDecoration srcEnd;
/**
* Group for the FigTexts concerning the dest association end.
*/
private EndDecoration destEnd;
/**
* Group for the FigTexts concerning the name and stereotype of the
* association itself.
*/
private FigTextGroup middleGroup;
/**
* Constructor used by PGML parser.
*
* @param diagramEdgeSettings the destination uml association-end element
* @param settings rendering settings
*/
public FigAssociation(
final DiagramEdgeSettings diagramEdgeSettings,
final DiagramSettings settings) {
super(diagramEdgeSettings.getOwner(), settings);
createNameLabel(getOwner(), settings);
Object sourceAssociationEnd =
diagramEdgeSettings.getSourceConnector();
Object destAssociationEnd =
diagramEdgeSettings.getDestinationConnector();
if (sourceAssociationEnd == null || destAssociationEnd == null) {
// If we have no source and dest connector then we assume this is
// load of an old UML1.4 diagram from before this data was saved
// in PGML. For UML1.4 we can assume the source is first connection
// and destination is last connection stored in repository for this
// association.
Iterator it =
Model.getFacade().getConnections(getOwner()).iterator();
sourceAssociationEnd = it.next();
destAssociationEnd = it.next();
}
createEndFigs(
sourceAssociationEnd,
destAssociationEnd,
settings, 45);
setBetweenNearestPoints(true);
initializeNotationProvidersInternal(getOwner());
if (Model.getFacade().getUmlVersion().charAt(0) == '2') {
Model.getPump().addModelEventListener(this, getOwner(),
new String[] {"navigableOwnedEnd"});
}
}
protected void removeFromDiagramImpl() {
if (Model.getFacade().getUmlVersion().charAt(0) == '2') {
Model.getPump().removeModelEventListener(this,
getOwner(),
new String[] {"navigableOwnedEnd"});
}
super.removeFromDiagramImpl();
}
public void propertyChange(PropertyChangeEvent pce) {
if (Model.getFacade().getUmlVersion().charAt(0) == '2'
&& pce instanceof AssociationChangeEvent
&& pce.getPropertyName().equals("navigableOwnedEnd")) {
srcEnd.getGroup().determineArrowHead();
destEnd.getGroup().determineArrowHead();
applyArrowHeads();
damage();
}
super.propertyChange(pce);
}
/**
* Called by the constructor to create the Figs at each end
* of the association.
* TODO: This is temporary during refactoring process. We should
* override setDestFigNode and setSourceFigNode and create the ends there.
* That will allow the same pattern to work for UML2 where we cannot assume
* the connection order.
*
* @param sourceAssociationEnd
* @param destAssociationEnd
* @param settings
*/
private void createEndFigs(
final Object sourceAssociationEnd,
final Object destAssociationEnd,
final DiagramSettings settings,
final int displacementAngle) {
srcEnd = createEnd(
sourceAssociationEnd,
settings, 0, 5, 180 - displacementAngle, 5);
destEnd = createEnd(
destAssociationEnd,
settings, 100, -5, displacementAngle, 5);
}
private EndDecoration createEnd(
final Object endOwner,
final DiagramSettings settings,
final int percentPostionOnLine,
final int pathDelta,
final int displacementAngle,
final int displacementDistance) {
return new EndDecoration(endOwner, settings,
percentPostionOnLine,
pathDelta,
displacementAngle,
displacementDistance);
}
/**
* Create the main draggable label for the association.
* This can be overridden in subclasses to change behaviour.
* TODO: Consider introducing this to FigEdgeModelElement and
* using throughout all edges.
*
* @param owner owning uml element
* @param settings rendering settings
*/
protected void createNameLabel(Object owner, DiagramSettings settings) {
middleGroup = new FigTextGroup(owner, settings);
// let's use groups to construct the different text sections at
// the association
if (getNameFig() != null) {
middleGroup.addFig(getNameFig());
}
middleGroup.addFig(getStereotypeFig());
addPathItem(middleGroup,
new PathItemPlacement(this, middleGroup, 50, 25));
ArgoFigUtil.markPosition(this, 50, 0, 90, 25, Color.yellow);
}
@Override
public void renderingChanged() {
super.renderingChanged();
/* This fixes issue 4987: */
srcEnd.renderingChanged();
destEnd.renderingChanged();
if (middleGroup != null) {
middleGroup.renderingChanged();
}
}
@Override
protected void initNotationProviders(Object own) {
initializeNotationProvidersInternal(own);
}
private void initializeNotationProvidersInternal(Object own) {
super.initNotationProviders(own);
srcEnd.initNotationProviders();
destEnd.initNotationProviders();
}
/*
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#updateListeners(java.lang.Object, java.lang.Object)
*/
@Override
public void updateListeners(Object oldOwner, Object newOwner) {
Set<Object[]> listeners = new HashSet<Object[]>();
if (newOwner != null) {
listeners.add(
new Object[] {newOwner,
new String[] {"isAbstract", "remove"}
});
}
updateElementListeners(listeners);
/* No further listeners required in this case - the rest is handled
* by the notationProvider and sub-Figs. */
}
/*
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#getNotationProviderType()
*/
@Override
protected int getNotationProviderType() {
return NotationProviderFactory2.TYPE_ASSOCIATION_NAME;
}
/**
* Get the source classifier that the association was drawn from.
* Note that source and destination are not necessarily meaningful
* regarding associations. Although the edge may originally have been
* drawn by the user in a certain direction it in no way indicates the
* direction of the association.
* @return the classifier at the source end of the association or null
* if the association is not yet attached (TODO can we ensure that this is
* never null?).
*/
protected Object getSource() {
if (srcEnd == null) {
return null;
}
return Model.getFacade().getClassifier(srcEnd.getOwner());
}
/**
* Get the destination classifier that the association was drawn from.
* Note that source and destination are not necessarily meaningful
* regarding associations. Although the edge may originally have been
* drawn by the user in a certain direction it in no way indicates the
* direction of the association.
* @return the classifier at the destination end of the association or null
* if the association is not yet attached (TODO can we ensure that this is
* never null?).
*/
protected Object getDestination() {
if (destEnd == null) {
return null;
}
return Model.getFacade().getClassifier(destEnd.getOwner());
}
/**
* Get the model element at the source end of the edge. This is not the
* same as the owner of the node at the source end, rather it is the
* element that connects the element of the edge to the element of the
* node.
* Mostly this returns null as the edge connects directly to the node but
* implementations such as the Fig for association will return an
* association end that connects the association to the classifier.
* @return the model element that connects the edge to the node (or null
* if the edge requires no such connector.
*/
public Object getSourceConnector() {
if (srcEnd == null) {
return null;
}
return srcEnd.getOwner();
}
/**
* Get the model element at the destination end of the edge. This is not
* the same as the owner of the node at the source end, rather it is the
* element that connects the element of the edge to the element of the
* node.
* Mostly this returns null as the edge connects directly to the node but
* implementations such as the Fig for association will return an
* association end that connects the association to the classifier.
* @return the model element that connects the edge to the node (or null
* if the edge requires no such connector.
*/
public Object getDestinationConnector() {
if (destEnd == null) {
return null;
}
return destEnd.getOwner();
}
/*
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#textEdited(org.tigris.gef.presentation.FigText)
*/
@Override
protected void textEdited(FigText ft) {
if (getOwner() == null) {
return;
}
super.textEdited(ft);
Collection conn = Model.getFacade().getConnections(getOwner());
if (conn == null || conn.size() == 0) {
return;
}
if (ft == srcEnd.getRole()) {
srcEnd.getRole().textEdited();
} else if (ft == destEnd.getRole()) {
destEnd.getRole().textEdited();
} else if (ft == srcEnd.getMult()) {
srcEnd.getMult().textEdited();
} else if (ft == destEnd.getMult()) {
destEnd.getMult().textEdited();
}
}
/*
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#textEditStarted(org.tigris.gef.presentation.FigText)
*/
@Override
protected void textEditStarted(FigText ft) {
if (ft == srcEnd.getRole()) {
srcEnd.getRole().textEditStarted();
} else if (ft == destEnd.getRole()) {
destEnd.getRole().textEditStarted();
} else if (ft == srcEnd.getMult()) {
srcEnd.getMult().textEditStarted();
} else if (ft == destEnd.getMult()) {
destEnd.getMult().textEditStarted();
} else {
super.textEditStarted(ft);
}
}
/**
* Choose the arrowhead style for each end. <p>
*
* TODO: This is called from paint(). Would it not better
* be called from renderingChanged()?
*/
protected void applyArrowHeads() {
if (srcEnd == null || destEnd == null) {
/* This only happens if model-change events arrive
* before we are completely constructed. */
return;
}
int sourceArrowType = srcEnd.getArrowType();
int destArrowType = destEnd.getArrowType();
if (!getSettings().isShowBidirectionalArrows()
&& sourceArrowType > 2
&& destArrowType > 2) {
sourceArrowType -= 3;
destArrowType -= 3;
}
setSourceArrowHead(FigAssociationEndAnnotation
.ARROW_HEADS[sourceArrowType]);
setDestArrowHead(FigAssociationEndAnnotation
.ARROW_HEADS[destArrowType]);
}
/*
* @see org.tigris.gef.ui.PopupGenerator#getPopUpActions(java.awt.event.MouseEvent)
*/
@Override
public Vector getPopUpActions(MouseEvent me) {
Vector popUpActions = super.getPopUpActions(me);
/* Check if multiple items are selected: */
boolean ms = TargetManager.getInstance().getTargets().size() > 1;
/* None of the menu-items below apply
* when multiple modelelements are selected:*/
if (ms) {
return popUpActions;
}
// x^2 + y^2 = r^2 (equation of a circle)
Point firstPoint = this.getFirstPoint();
Point lastPoint = this.getLastPoint();
int length = getPerimeterLength();
int rSquared = (int) (.3 * length);
// max distance is set at 100 pixels, (rSquared = 100^2)
if (rSquared > 100) {
rSquared = 10000;
} else {
rSquared *= rSquared;
}
int srcDeterminingFactor =
getSquaredDistance(me.getPoint(), firstPoint);
int destDeterminingFactor =
getSquaredDistance(me.getPoint(), lastPoint);
if (srcDeterminingFactor < rSquared
&& srcDeterminingFactor < destDeterminingFactor) {
ArgoJMenu multMenu =
new ArgoJMenu("menu.popup.multiplicity");
multMenu.add(ActionMultiplicity.getSrcMultOne());
multMenu.add(ActionMultiplicity.getSrcMultZeroToOne());
multMenu.add(ActionMultiplicity.getSrcMultOneToMany());
multMenu.add(ActionMultiplicity.getSrcMultZeroToMany());
popUpActions.add(popUpActions.size() - getPopupAddOffset(),
multMenu);
ArgoJMenu aggMenu = new ArgoJMenu("menu.popup.aggregation");
aggMenu.add(ActionAggregation.getSrcAggNone());
aggMenu.add(ActionAggregation.getSrcAgg());
aggMenu.add(ActionAggregation.getSrcAggComposite());
popUpActions.add(popUpActions.size() - getPopupAddOffset(),
aggMenu);
} else if (destDeterminingFactor < rSquared) {
ArgoJMenu multMenu =
new ArgoJMenu("menu.popup.multiplicity");
multMenu.add(ActionMultiplicity.getDestMultOne());
multMenu.add(ActionMultiplicity.getDestMultZeroToOne());
multMenu.add(ActionMultiplicity.getDestMultOneToMany());
multMenu.add(ActionMultiplicity.getDestMultZeroToMany());
popUpActions.add(popUpActions.size() - getPopupAddOffset(),
multMenu);
ArgoJMenu aggMenu = new ArgoJMenu("menu.popup.aggregation");
aggMenu.add(ActionAggregation.getDestAggNone());
aggMenu.add(ActionAggregation.getDestAgg());
aggMenu.add(ActionAggregation.getDestAggComposite());
popUpActions
.add(popUpActions.size() - getPopupAddOffset(), aggMenu);
}
// else: No particular options for right click in middle of line
// Options available when right click anywhere on line
Object association = getOwner();
if (association != null) {
// Navigability menu with suboptions built dynamically to
// allow navigability from atart to end, from end to start
// or bidirectional
Collection ascEnds = Model.getFacade().getConnections(association);
Iterator iter = ascEnds.iterator();
Object ascStart = iter.next();
Object ascEnd = iter.next();
if (Model.getFacade().isAClassifier(
Model.getFacade().getType(ascStart))
&& Model.getFacade().isAClassifier(
Model.getFacade().getType(ascEnd))) {
ArgoJMenu navMenu =
new ArgoJMenu("menu.popup.navigability");
navMenu.add(ActionNavigability.newActionNavigability(
ascStart,
ascEnd,
ActionNavigability.BIDIRECTIONAL));
navMenu.add(ActionNavigability.newActionNavigability(
ascStart,
ascEnd,
ActionNavigability.STARTTOEND));
navMenu.add(ActionNavigability.newActionNavigability(
ascStart,
ascEnd,
ActionNavigability.ENDTOSTART));
popUpActions.add(popUpActions.size() - getPopupAddOffset(),
navMenu);
}
}
return popUpActions;
}
/**
* Updates the multiplicity fields.
*/
protected void updateMultiplicity() {
if (getOwner() != null
&& srcEnd.getOwner() != null
&& destEnd.getOwner() != null) {
srcEnd.getMult().setText();
destEnd.getMult().setText();
}
}
/*
* @see org.tigris.gef.presentation.Fig#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
if (getOwner() == null ) {
LOG.error("Trying to paint a FigAssociation without an owner. ");
} else {
applyArrowHeads();
}
if (getSourceArrowHead() != null && getDestArrowHead() != null) {
getSourceArrowHead().setLineColor(getLineColor());
getDestArrowHead().setLineColor(getLineColor());
}
super.paint(g);
}
/*
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#paintClarifiers(java.awt.Graphics)
*/
@Override
public void paintClarifiers(Graphics g) {
indicateBounds(getNameFig(), g);
indicateBounds(srcEnd.getMult(), g);
indicateBounds(srcEnd.getRole(), g);
indicateBounds(destEnd.getMult(), g);
indicateBounds(destEnd.getRole(), g);
super.paintClarifiers(g);
}
/**
* @return Returns the middleGroup.
*/
protected FigTextGroup getMiddleGroup() {
return middleGroup;
}
/**
* Lays out the association edges as any other edge except for
* special rules for an association that loops back to the same
* class. For this it is snapped back to the bottom right corner
* if it resized to the point of not being visible.
* @see org.tigris.gef.presentation.FigEdgePoly#layoutEdge()
*/
@Override
protected void layoutEdge() {
FigNode sourceFigNode = getSourceFigNode();
Point[] points = getPoints();
if (points.length < 3
&& sourceFigNode != null
&& getDestFigNode() == sourceFigNode) {
Rectangle rect = new Rectangle(
sourceFigNode.getX() + sourceFigNode.getWidth() - 20,
sourceFigNode.getY() + sourceFigNode.getHeight() - 20,
40,
40);
points = new Point[5];
points[0] = new Point(rect.x, rect.y + rect.height / 2);
points[1] = new Point(rect.x, rect.y + rect.height);
points[2] = new Point(rect.x + rect.width , rect.y + rect.height);
points[3] = new Point(rect.x + rect.width , rect.y);
points[4] = new Point(rect.x + rect.width / 2, rect.y);
setPoints(points);
} else {
super.layoutEdge();
}
}
/**
* If the name is updated, update the bounds of the middle group.
* This makes the selection box appear correctly during prop-panel edit.
* This is a temporary solution, until a better architecture is decided
* upon, see issue 5477 and
* http://argouml.tigris.org/issues/show_bug.cgi?id=5621#desc19.
* @see org.argouml.uml.diagram.ui.FigEdgeModelElement#updateNameText()
*/
protected void updateNameText() {
super.updateNameText();
// TODO: Without the null check the following throws a NPE so many
// times when it is called from FigEdgeModelElement.modelChanged(),
// we need to think about it.
if (middleGroup != null) {
middleGroup.calcBounds();
}
}
/**
*
*/
class EndDecoration {
private FigAssociationEndAnnotation group;
private FigMultiplicity mult;
EndDecoration(
final Object endOwner,
final DiagramSettings settings,
final int percentPostionOnLine,
final int pathDelta,
final int displacementAngle,
final int displacementDistance) {
mult = new FigMultiplicity(endOwner, settings);
addPathItem(mult,
new PathItemPlacement(FigAssociation.this, mult,
percentPostionOnLine, pathDelta,
displacementAngle, displacementDistance));
ArgoFigUtil.markPosition(
FigAssociation.this, percentPostionOnLine, pathDelta,
displacementAngle, displacementDistance, Color.green);
group = new FigAssociationEndAnnotation(
FigAssociation.this, endOwner, settings);
addPathItem(group,
new PathItemPlacement(FigAssociation.this, group,
percentPostionOnLine, pathDelta,
-displacementAngle, displacementDistance));
ArgoFigUtil.markPosition(
FigAssociation.this, percentPostionOnLine, pathDelta,
-displacementAngle, displacementDistance, Color.blue);
}
public FigAssociationEndAnnotation getGroup() {
return group;
}
public FigMultiplicity getMult() {
return mult;
}
public FigRole getRole() {
return group.getRole();
}
public int getArrowType() {
return group.getArrowType();
}
public void renderingChanged() {
mult.renderingChanged();
group.renderingChanged();
}
public void initNotationProviders() {
mult.initNotationProviders();
}
public Object getOwner() {
return mult.getOwner();
}
}
} /* end class FigAssociation */
/**
* A Fig representing the multiplicity of some model element.
* This has potential reuse for other edges showing multiplicity. <p>
*
* The owner is an AssociationEnd.
*
* @author Bob Tarling
*/
class FigMultiplicity extends FigSingleLineTextWithNotation {
FigMultiplicity(Object owner, DiagramSettings settings) {
super(owner, new Rectangle(X0, Y0, 90, 20), settings, false,
"multiplicity");
setTextFilled(false);
setJustification(FigText.JUSTIFY_CENTER);
}
@Override
protected int getNotationProviderType() {
return NotationProviderFactory2.TYPE_MULTIPLICITY;
}
}
/**
* A textual Fig representing the ordering of some model element,
* i.e. "{ordered}" or nothing.
* This has potential reuse for other edges showing ordering. <p>
*
* This Fig is not editable by the user.
*
* @author Bob Tarling
*/
class FigOrdering extends FigSingleLineText {
private static final long serialVersionUID = 5385230942216677015L;
FigOrdering(Object owner, DiagramSettings settings) {
super(owner, new Rectangle(X0, Y0, 90, 20), settings, false,
"ordering");
setTextFilled(false);
setJustification(FigText.JUSTIFY_CENTER);
setEditable(false);
}
@Override
protected void setText() {
assert getOwner() != null;
if (getSettings().getNotationSettings().isShowProperties()) {
setText(getOrderingName(Model.getFacade().getOrdering(getOwner())));
} else {
setText("");
}
damage();
}
/**
* Returns the name of the OrderingKind.
*
* @param orderingKind the kind of ordering
* @return "{ordered}" or "", the latter if null or unordered
*/
private String getOrderingName(Object orderingKind) {
if (orderingKind == null) {
return "";
}
if (Model.getFacade().getName(orderingKind) == null) {
return "";
}
if ("".equals(Model.getFacade().getName(orderingKind))) {
return "";
}
if ("unordered".equals(Model.getFacade().getName(orderingKind))) {
return "";
}
// TODO: I18N
return "{" + Model.getFacade().getName(orderingKind) + "}";
}
}
/**
* A Fig representing the association end role of some model element.
* This class is designed as a composite part and should always be
* part of a FigGroup.
* @author Bob Tarling
*/
class FigRole extends FigSingleLineTextWithNotation {
FigRole(Object owner, DiagramSettings settings) {
super(owner, new Rectangle(X0, Y0, 90, 20), settings, false,
(String[]) null
// no need to listen to these property changes - the
// notationProvider takes care of this.
/*, new String[] {"name", "visibility", "stereotype"}*/
);
setTextFilled(false);
setJustification(FigText.JUSTIFY_CENTER);
setText();
}
protected int getNotationProviderType() {
return NotationProviderFactory2.TYPE_ASSOCIATION_END_NAME;
}
/**
* Property change listener to recalculate bounds of enclosing
* group whenever any properties of the FigRole get changed.
* This is only really needed for the name, see issue 5621.
*
* @param pce The property change event to process.
* @see org.argouml.uml.diagram.ui.FigSingleLineTextWithNotation#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent pce) {
super.propertyChange(pce);
assert(getGroup() != null);
this.getGroup().calcBounds();
}
}
/**
* The arrowhead and the group of labels shown at the association end:
* the role name and the ordering property.
* This does not include the multiplicity. <p>
*
* This class does not yet support arrows for a FigAssociationEnd,
* as is used for N-ary associations.
*/
class FigAssociationEndAnnotation extends FigTextGroup {
private static final long serialVersionUID = 1871796732318164649L;
private static final ArrowHead NAV_AGGR =
new ArrowHeadComposite(ArrowHeadDiamond.WhiteDiamond,
new ArrowHeadGreater());
private static final ArrowHead NAV_COMP =
new ArrowHeadComposite(ArrowHeadDiamond.BlackDiamond,
new ArrowHeadGreater());
// These are a list of arrow types. Positioning is important as we subtract
// 3 to convert a navigable arrow to a non navigable with the same
// aggregation
private static final int NONE = 0;
private static final int AGGREGATE = 1;
private static final int COMPOSITE = 2;
private static final int NAV_NONE = 3;
private static final int NAV_AGGREGATE = 4;
private static final int NAV_COMPOSITE = 5;
/**
* All the arrow head types.
*/
public static final ArrowHead[] ARROW_HEADS = new ArrowHead[6];
static {
ARROW_HEADS[NONE] = ArrowHeadNone.TheInstance;
ARROW_HEADS[AGGREGATE] = ArrowHeadDiamond.WhiteDiamond;
ARROW_HEADS[COMPOSITE] = ArrowHeadDiamond.BlackDiamond;
ARROW_HEADS[NAV_NONE] = new ArrowHeadGreater();
ARROW_HEADS[NAV_AGGREGATE] = NAV_AGGR;
ARROW_HEADS[NAV_COMPOSITE] = NAV_COMP;
}
private FigRole role;
private FigOrdering ordering;
private int arrowType = 0;
private FigEdgeModelElement figEdge;
FigAssociationEndAnnotation(FigEdgeModelElement edge, Object owner,
DiagramSettings settings) {
super(owner, settings);
figEdge = edge;
role = new FigRole(owner, settings);
addFig(role);
ordering = new FigOrdering(owner, settings);
addFig(ordering);
determineArrowHead();
Model.getPump().addModelEventListener(this, owner,
new String[] {"isNavigable", "aggregation", "participant"});
}
/*
* @see org.tigris.gef.presentation.Fig#removeFromDiagram()
*/
@Override
public void removeFromDiagram() {
Model.getPump().removeModelEventListener(this,
getOwner(),
new String[] {"isNavigable", "aggregation", "participant"});
super.removeFromDiagram();
}
/*
* @see org.tigris.gef.presentation.Fig#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (pce instanceof AttributeChangeEvent
&& (pce.getPropertyName().equals("isNavigable")
|| pce.getPropertyName().equals("aggregation"))) {
determineArrowHead();
((FigAssociation) figEdge).applyArrowHeads();
damage();
}
if (pce instanceof AddAssociationEvent
&& pce.getPropertyName().equals("participant")) {
figEdge.determineFigNodes();
}
String pName = pce.getPropertyName();
if (pName.equals("editing")
&& Boolean.FALSE.equals(pce.getNewValue())) {
// Finished editing.
// Parse the text that was edited.
// Only the role is editable, hence:
role.textEdited();
calcBounds();
endTrans();
} else if (pName.equals("editing")
&& Boolean.TRUE.equals(pce.getNewValue())) {
// figEdge.showHelp(role.getParsingHelp());
// role.setText();
role.textEditStarted();
} else {
// Pass everything else to superclass
super.propertyChange(pce);
}
}
/**
* Decide which arrow head should appear
*/
void determineArrowHead() {
assert getOwner() != null;
Object ak = Model.getFacade().getAggregation(getOwner());
boolean nav = Model.getFacade().isNavigable(getOwner());
if (nav) {
if (Model.getAggregationKind().getNone().equals(ak)
|| (ak == null)) {
arrowType = NAV_NONE;
} else if (Model.getAggregationKind().getAggregate()
.equals(ak)) {
arrowType = NAV_AGGREGATE;
} else if (Model.getAggregationKind().getComposite()
.equals(ak)) {
arrowType = NAV_COMPOSITE;
}
} else {
if (Model.getAggregationKind().getNone().equals(ak)
|| (ak == null)) {
arrowType = NONE;
} else if (Model.getAggregationKind().getAggregate()
.equals(ak)) {
arrowType = AGGREGATE;
} else if (Model.getAggregationKind().getComposite()
.equals(ak)) {
arrowType = COMPOSITE;
}
}
}
/**
* @return the current arrow type of this end of the association
*/
public int getArrowType() {
return arrowType;
}
FigRole getRole() {
return role;
}
}