/* This file is part of Green.
*
* Copyright (C) 2005 The Research Foundation of State University of New York
* All Rights Under Copyright Reserved, The Research Foundation of S.U.N.Y.
*
* Green is free software, licensed under the terms of the Eclipse
* Public License, version 1.0. The license is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package edu.buffalo.cse.green.editor.controller;
import static edu.buffalo.cse.green.GreenException.GRERR_INVALID_INDEX;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipCardinality;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipSource;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.RelationshipTarget;
import static edu.buffalo.cse.green.editor.controller.PropertyChange.Size;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_COLOR_REL_ARROW_FILL;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_COLOR_REL_LINE;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_COLOR_REL_TEXT;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_DISPLAY_RELATIONSHIP_SUBTYPES;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_DRAW_LINE_WIDTH;
import static edu.buffalo.cse.green.preferences.PreferenceInitializer.P_FONT;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.BendpointConnectionRouter;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionEndpointLocator;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.LineBorder;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.RotatableDecoration;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
import org.eclipse.gef.editpolicies.NonResizableEditPolicy;
import org.eclipse.gef.requests.BendpointRequest;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Display;
import edu.buffalo.cse.green.GreenException;
import edu.buffalo.cse.green.PlugIn;
import edu.buffalo.cse.green.editor.DiagramEditor;
import edu.buffalo.cse.green.editor.controller.policies.BendableRelationshipEditPolicy;
import edu.buffalo.cse.green.editor.controller.policies.RelationshipLayoutEditPolicy;
import edu.buffalo.cse.green.editor.controller.policies.RelationshipSelectionEditPolicy;
import edu.buffalo.cse.green.editor.model.RelationshipModel;
import edu.buffalo.cse.green.editor.model.TypeModel;
import edu.buffalo.cse.green.editor.model.commands.CreateBendpointCommand;
import edu.buffalo.cse.green.editor.model.commands.DeleteCommand;
import edu.buffalo.cse.green.editor.view.GreenBendpoint;
import edu.buffalo.cse.green.editor.view.RelationshipFigure;
import edu.buffalo.cse.green.relationships.RelationshipGroup;
/**
* @author bcmartin
*
* The controller part corresponding to a <code>RelationshipModel</code>
*/
public abstract class RelationshipPart extends AbstractPart
implements ConnectionEditPart, PropertyChangeListener,
RelationshipFigure.RelationshipFigureListener {
private int _loopSize = 40;
private RootPart _root;
private Label _sourceMultiplicityLabel;
private RotatableDecoration _sourceDecoration;
private RotatableDecoration _targetDecoration;
private boolean _ignoreNextUpdateRequest;
public RelationshipPart() {
_sourceMultiplicityLabel = new Label();
_sourceMultiplicityLabel.setText("");
_sourceMultiplicityLabel.setFont(
PlugIn.getFontPreference(P_FONT, false));
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#doCreateFigure()
*/
protected final IFigure doCreateFigure() {
PolylineConnection rFigure = createConnection();
model().getSourceModel().getOutgoingEdges().add(model());
model().getTargetModel().getIncomingEdges().add(model());
rFigure.setConnectionRouter(DiagramEditor.getConnectionRouter());
TypeModel modelSource = model().getSourceModel();
TypeModel modelTarget = model().getTargetModel();
if (modelSource != null && modelTarget != null) {
_sourceDecoration = createSourceArrow();
_targetDecoration = createTargetArrow();
rFigure.setSourceDecoration(_sourceDecoration);
rFigure.setTargetDecoration(_targetDecoration);
ConnectionEndpointLocator sourceEndpointLocator = new ConnectionEndpointLocator(
rFigure, false);
sourceEndpointLocator.setVDistance(15);
_sourceMultiplicityLabel = new Label();
_sourceMultiplicityLabel.setFont(
PlugIn.getFontPreference(P_FONT, false));
rFigure.add(_sourceMultiplicityLabel, sourceEndpointLocator);
}
rFigure.setLineWidth(PlugIn.getIntegerPreference(P_DRAW_LINE_WIDTH));
rFigure.addFigureListener(new FigureListener() {
/**
* @see org.eclipse.draw2d.FigureListener#figureMoved(org.eclipse.draw2d.IFigure)
*/
public void figureMoved(IFigure source) {
if (!model().getBounds().equals(source.getBounds())) {
model().setBounds(source.getBounds());
}
}
});
return rFigure;
}
/**
* @see org.eclipse.gef.editparts.AbstractEditPart#createEditPolicies()
*/
protected void createEditPolicies() {
super.createEditPolicies();
installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE,
new RelationshipSelectionEditPolicy());
installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE,
new ConnectionEndpointEditPolicy());
installEditPolicy(EditPolicy.CONNECTION_BENDPOINTS_ROLE,
new BendableRelationshipEditPolicy());
installEditPolicy(EditPolicy.LAYOUT_ROLE,
new RelationshipLayoutEditPolicy());
}
/**
* @see org.eclipse.gef.EditPart#activate()
*/
public void activate() {
if (getParent() != null) {
super.activate();
_root = (RootPart) getRootPart();
updateSourceAnchor();
updateTargetAnchor();
if (model().getSourceModel().equals(model().getTargetModel())) {
createLoop();
}
}
}
/**
* @return the connection created by this part
*/
public abstract RelationshipFigure createConnection();
/**
* @return the arrow on the source end of this connection
*/
public abstract RotatableDecoration createSourceArrow();
/**
* @return the arrow on the target end of this connection
*/
public abstract RotatableDecoration createTargetArrow();
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#getDeleteCommand()
*/
public DeleteCommand getDeleteCommand() {
return model().getDeleteCommand(getEditor());
}
/**
* @see org.eclipse.gef.ConnectionEditPart#getSource()
*/
public EditPart getSource() {
return _root.getPartFromModel(model().getSourceModel());
}
/**
* @see org.eclipse.gef.ConnectionEditPart#getTarget()
*/
public EditPart getTarget() {
return _root.getPartFromModel(model().getTargetModel());
}
/**
* @see org.eclipse.gef.ConnectionEditPart#setSource(org.eclipse.gef.EditPart)
*/
public void setSource(EditPart source) {
updateSourceAnchor();
}
/**
* @see org.eclipse.gef.ConnectionEditPart#setTarget(org.eclipse.gef.EditPart)
*/
public void setTarget(EditPart target) {
updateTargetAnchor();
}
/**
* Sets the source anchor to the appropriate part
*/
protected void updateSourceAnchor() {
NodeEditPart part = (NodeEditPart) getSource();
if (isActive()) {
figure().setSourceAnchor(part.getSourceConnectionAnchor(this));
}
}
/**
* Sets the target anchor to the appropriate part
*/
protected void updateTargetAnchor() {
NodeEditPart part = (NodeEditPart) getTarget();
if (isActive()) {
figure().setTargetAnchor(part.getTargetConnectionAnchor(this));
}
}
/**
* @see edu.buffalo.cse.green.editor.view.RelationshipFigure.RelationshipFigureListener#relationshipFigureMoved(edu.buffalo.cse.green.editor.view.RelationshipFigure)
*/
public void relationshipFigureMoved(RelationshipFigure movedFigure) {
updateChildren();
}
/**
* @see edu.buffalo.cse.green.editor.view.RelationshipFigure.RelationshipFigureListener#relationshipFigureWasAdded(edu.buffalo.cse.green.editor.view.RelationshipFigure)
*/
public void relationshipFigureWasAdded(RelationshipFigure addedFigure) {
List<GreenBendpoint> bendpoints = model().getBendpointList();
if (bendpoints == null) {
model().setBendpointList(
bendpoints = new ArrayList<GreenBendpoint>());
}
DiagramEditor.getConnectionRouter().setConstraint(
(Connection) getFigure(), bendpoints);
}
/**
* @see edu.buffalo.cse.green.editor.view.RelationshipFigure.RelationshipFigureListener#relationshipFigureWasRemoved(edu.buffalo.cse.green.editor.view.RelationshipFigure)
*/
public void relationshipFigureWasRemoved(RelationshipFigure removedFigure) {
}
/**
* Creates a loop from a model to itself. Used for recursive relationships.
*/
public void createLoop() {
final RelationshipModel rModel = (RelationshipModel) getModel();
RelationshipFigure rFigure = (RelationshipFigure) getFigure();
rFigure.setConnectionRouter(new BendpointConnectionRouter());
BendpointRequest request;
for (int x = 0; x < 3; x++) {
request = new BendpointRequest();
request.setIndex(x);
request.setSource(this);
switch(x) {
case 0:
request.setLocation(new Point(_loopSize, 0));
break;
case 1:
request.setLocation(new Point(_loopSize, -_loopSize));
break;
case 2:
request.setLocation(new Point(0, -_loopSize));
break;
default:
GreenException.illegalOperation(GRERR_INVALID_INDEX);
}
new CreateBendpointCommand(rFigure, request).execute();
}
rFigure.setRecursive(
getPartFromModel(rModel.getSourceModel()).getFigure());
rModel.getSourceModel().addListener(Size, new PropertyListener() {
private Dimension _oldSize = rModel.getSourceModel().getSize();
public void notify(Object oValue, Object nValue) {
Dimension newSize = rModel.getSourceModel().getSize();
Dimension delta = newSize.getExpanded(
_oldSize.getNegated()).getScaled(.5);
delta.height = -delta.height;
_oldSize = newSize;
}
});
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#addPropertyListeners()
*/
protected void addPropertyListeners() {
addListener(RelationshipSource, new RelationshipSourceHandler());
addListener(RelationshipTarget, new RelationshipTargetHandler());
addListener(RelationshipCardinality,
new RelationshipCardinalityHandler());
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#onDoubleClick()
*/
protected void onDoubleClick() {
// do nothing
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#updateColors(org.eclipse.draw2d.IFigure)
*/
protected void updateColors(IFigure f) {
Color fillColor = PlugIn.getColorPreference(P_COLOR_REL_ARROW_FILL);
Color lineColor = PlugIn.getColorPreference(P_COLOR_REL_LINE);
_sourceMultiplicityLabel.setForegroundColor(
PlugIn.getColorPreference(P_COLOR_REL_TEXT));
f.setForegroundColor(lineColor);
// Is it possible for _sourceDecoration to be null in the case that a relationship
// arc does not have a decoration at the source? If not, why are we ignoring the
// null pointer here?
if (_sourceDecoration != null) {
_sourceDecoration.setBackgroundColor(fillColor);
_sourceDecoration.setForegroundColor(lineColor);
}
// See comment above.
if (_targetDecoration != null) {
_targetDecoration.setBackgroundColor(fillColor);
_targetDecoration.setForegroundColor(lineColor);
}
}
/**
* Updates the relationship's cardinality label.
*/
private void updateCardinalityLabel() {
try {
String cardinality = model().getCardinality();
if (cardinality.equals("1")) {
cardinality = "";
}
_sourceMultiplicityLabel.setText(cardinality + subtypeLabel());
} catch (JavaModelException e) {
e.printStackTrace();
}
}
/**
* @see org.eclipse.gef.editparts.AbstractEditPart#refreshVisuals()
*/
protected void refreshVisuals() {
updateCardinalityLabel();
updateFont();
ConnectionRouter desiredRouter = DiagramEditor.getConnectionRouter();
if (!figure().isRecursive()) {
if (!figure().getConnectionRouter().getClass().equals(desiredRouter.getClass())) {
figure().setConnectionRouter(desiredRouter);
}
}
//figure().getConnectionRouter().route(figure());
figure().setLineWidth(PlugIn.getIntegerPreference(
P_DRAW_LINE_WIDTH));
}
/**
* Updates the font used in the label
*/
private void updateFont() {
if (Display.getCurrent() != null) {
updateFontHelper();
} else {
Display.getDefault().asyncExec(new Runnable() {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
updateFontHelper();
}
});
}
}
/**
* edu.buffalo.cse.green.editor.controller.MethodPart#updateFont(final MethodModel model)
*/
private void updateFontHelper() {
if (_ignoreNextUpdateRequest == true) {
_ignoreNextUpdateRequest = false;
return;
}
_ignoreNextUpdateRequest = true;
// get rid of the old font
Font font = _sourceMultiplicityLabel.getFont();
font.dispose();
// create the new font
_sourceMultiplicityLabel.setFont(PlugIn.getFontPreference(P_FONT, false));
}
/**
* Auxiliary method; makes reading easier.
*/
private RelationshipFigure figure() {
return (RelationshipFigure) getFigure();
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#generateResizableEditPolicy()
*/
public EditPolicy generateResizableEditPolicy() {
NonResizableEditPolicy dragPolicy = new NonResizableEditPolicy();
dragPolicy.setDragAllowed(false);
return dragPolicy;
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#setSelectedBackgroundColor()
*/
public void setSelectedBackgroundColor() {
getFigure().setBorder(
new LineBorder(PlugIn.getColorPreference(P_COLOR_REL_LINE), 1));
}
/**
* @see edu.buffalo.cse.green.editor.controller.AbstractPart#setInitialBackgroundColor()
*/
public void setInitialBackgroundColor() {
// not necessary
}
/**
* @return The subtype label, if one is desired.
*/
private String subtypeLabel() {
if (!PlugIn.getBooleanPreference(P_DISPLAY_RELATIONSHIP_SUBTYPES))
return "";
RelationshipGroup group = PlugIn.getRelationshipGroup(getClass());
if (group.getSubtype() == null)
return "";
return " <<" + group.getSubtype() + ">>";
}
/**
* Auxiliary method; makes reading easier.
*/
private RelationshipModel model() {
return (RelationshipModel) getModel();
}
class RelationshipCardinalityHandler implements PropertyListener {
public void notify(Object oValue, Object nValue) {
updateCardinalityLabel();
updateChildren();
}
}
class RelationshipSourceHandler implements PropertyListener {
public void notify(Object oValue, Object nValue) {
RootPart rootPart = getRootPart();
TypeModel oldModel = (TypeModel) oValue;
TypeModel newModel = (TypeModel) nValue;
if (oldModel != null) {
oldModel.getOutgoingEdges().remove(getModel());
}
newModel.getOutgoingEdges().add(model());
setSource(rootPart.getPartFromModel(newModel));
updateChildren();
}
}
class RelationshipTargetHandler implements PropertyListener {
public void notify(Object oValue, Object nValue) {
RootPart rootPart = getRootPart();
TypeModel oldModel = (TypeModel) oValue;
TypeModel newModel = (TypeModel) nValue;
if (oldModel != null) {
oldModel.getIncomingEdges().remove(getModel());
}
newModel.getIncomingEdges().add(model());
setTarget(rootPart.getPartFromModel(newModel));
updateChildren();
}
}
}