/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* 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:
* Del Myers -- initial API and implementation
*******************************************************************************/
package org.eclipse.zest.custom.sequence.visuals;
import java.util.Iterator;
import org.eclipse.draw2d.AbstractRouter;
import org.eclipse.draw2d.Animation;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.PolylineDecoration;
import org.eclipse.draw2d.RotatableDecoration;
import org.eclipse.draw2d.Shape;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.zest.custom.sequence.figures.AligningBendpointLocator;
import org.eclipse.zest.custom.sequence.figures.CircleDecoration;
import org.eclipse.zest.custom.sequence.figures.internal.AnimatedDeferredLayoutPolylineConnection;
import org.eclipse.zest.custom.sequence.figures.internal.DeferredLayoutPolylineConnection;
import org.eclipse.zest.custom.sequence.visuals.interactions.ActivationHoverInteraction;
import org.eclipse.zest.custom.sequence.widgets.Activation;
import org.eclipse.zest.custom.sequence.widgets.Message;
import org.eclipse.zest.custom.sequence.widgets.PropertyChangeListener;
import org.eclipse.zest.custom.sequence.widgets.UMLItem;
import org.eclipse.zest.custom.sequence.widgets.internal.IWidgetProperties;
/**
* Visual part for UML calls in sequence charts.
* @author Del Myers
*/
public class MessageVisual extends ConnectionVisualPart implements PropertyChangeListener {
private Label hover;
private Label label;
private RotatableDecoration targetDec;
private RotatableDecoration sourceDec;
private ActivationHoverInteraction mouseOverInteraction;
private boolean needsRouting;
public static final PointList DIAMOND_TIP = new PointList();
public static final PointList SQUARE_TIP = new PointList();
private Image localImage;
static {
DIAMOND_TIP.addPoint(0, 0);
DIAMOND_TIP.addPoint(-1, 1);
DIAMOND_TIP.addPoint(-2, 0);
DIAMOND_TIP.addPoint(-1, -1);
SQUARE_TIP.addPoint(-1, -1);
SQUARE_TIP.addPoint(-1, 1);
SQUARE_TIP.addPoint(1, 1);
SQUARE_TIP.addPoint(1, -1);
}
private class CallConnectionRouter extends AbstractRouter {
/* (non-Javadoc)
* @see org.eclipse.draw2d.ConnectionRouter#route(org.eclipse.draw2d.Connection)
*/
public void route(Connection connection) {
try {
if (!isActive() || (!needsRouting && !Animation.isAnimating()))
return;
Activation source = getMessage().getSource();
Activation target = getMessage().getTarget();
Point start = getStartPoint(connection);
Point end = getEndPoint(connection);
connection.getPoints().removeAllPoints();
PointList points = new PointList();
if (source.getVisibleLifeline().equals(target.getVisibleLifeline())) {
points.addPoint(start);
int width = 0;
for (Iterator<?> i = connection.getChildren().iterator(); i.hasNext();) {
IFigure child = (IFigure) i.next();
int localWidth = child.getPreferredSize().width;
if (localWidth > width)
width = localWidth;
}
points.addPoint(new Point(end.x+width, start.y));
points.addPoint(new Point(end.x+width, end.y));
} else {
points.addPoint(start);
}
points.addPoint(end);
connection.setPoints(points);
needsRouting = Animation.isAnimating();
} catch (Exception e) {e.printStackTrace();}
}
}
/**
* @param item
* @param key
* @param parentFigure
*/
public MessageVisual(UMLItem item, String key) {
super(item, key);
}
/* (non-Javadoc)
* @see org.eclipse.mylar.zest.custom.sequence.visuals.ConnectionVisualPart#createConnection()
*/
@Override
protected Connection createConnection() {
PolylineConnection conn = new AnimatedDeferredLayoutPolylineConnection();
hover = new Label();
conn.setToolTip(hover);
conn.setForegroundColor(getMessage().getForeground());
label = new Label();
label.setFont(getWidget().getFont());
label.setForegroundColor(getMessage().getTextForeground());
label.setBorder(new MarginBorder(1,1,1,1));
label.setIcon(getWidget().getImage());
conn.add(label, new AligningBendpointLocator(conn, AligningBendpointLocator.BEGINNING, AligningBendpointLocator.ABOVE));
///conn.addRoutingListener(RoutingAnimator.getDefault());
needsRouting = true;
return conn;
}
/**
* @return
*/
private Message getMessage() {
return (Message) getWidget();
}
/* (non-Javadoc)
* @see org.eclipse.mylar.zest.custom.sequence.visuals.WidgetVisualPart#refreshVisuals()
*/
@Override
public void refreshVisuals() {
label.setText(getMessage().getText());
String toolTipText = getWidget().getTooltipText();
if (toolTipText == null) {
toolTipText = getWidget().getText();
}
hover.setText(toolTipText);
label.setForegroundColor(getMessage().getTextForeground());
((DeferredLayoutPolylineConnection)getConnection()).setDirty(true);
if (getMessage().getTextBackground() != null) {
label.setOpaque(true);
label.setBackgroundColor(getMessage().getTextBackground());
} else {
label.setOpaque(false);
}
boolean highlight = Boolean.TRUE.equals(getWidget().getData("pin")) ||
getWidget().isHighlighted();
Object layout = getWidget().getData(IWidgetProperties.LAYOUT);
Color fg = getWidget().getForeground();
if (fg != null) {
((PolylineConnection)getConnection()).setForegroundColor(fg);
}
NodeVisualPart target = getTarget();
if (layout == null) {
label.setVisible(false);
getConnection().setVisible(false);
} else if (target == null){// || !target.isActive()){
label.setVisible(false);
label.setText("");
} else if (!target.isActive()) {
//see if there is enough room for the label
Font font = getMessage().getFont();
String text = getMessage().getText();
PointList points = (PointList) layout;
int width = getConnection().getBounds().width;
if (points.size() >=2) {
width = Math.abs(points.getFirstPoint().x - points.getLastPoint().x);
}
FontMetrics metrics = FigureUtilities.getFontMetrics(font);
int availableCharacters = width/(metrics.getAverageCharWidth()+1);
if (availableCharacters < 3) {
label.setVisible(false);
} else if (availableCharacters < text.length()) {
label.setVisible(true);
label.setText(text.substring(0, availableCharacters-3) + "...");
} else {
label.setVisible(true);
label.setText(text);
}
if (!(getConnection().getConnectionRouter() instanceof CallConnectionRouter))
getConnection().setConnectionRouter(new CallConnectionRouter());
getConnection().setVisible(true);
} else {
getConnection().setVisible(true);
if (highlight) {
label.setFont(getMessage().getChart().getFont(SWT.BOLD));
((Shape)getFigure()).setLineWidth(2);
if (targetDec != null) {
((Shape)targetDec).setLineWidth(2);
}
if (sourceDec != null) {
((Shape)sourceDec).setLineWidth(2);
}
label.setVisible(true);
} else {
Font f = getMessage().getChart().getFont();
label.setFont(f);
Dimension size = label.getTextBounds().getSize();
label.getParent().translateToAbsolute(size);
((Shape)getFigure()).setLineWidth(1);
if (size.height < 8) {
label.setVisible(false);
} else {
label.setVisible(true);
}
if (!(getConnection().getConnectionRouter() instanceof CallConnectionRouter))
getConnection().setConnectionRouter(new CallConnectionRouter());
if (targetDec != null) {
((Shape)targetDec).setLineWidth(1);
}
if (sourceDec != null) {
((Shape)sourceDec).setLineWidth(1);
}
}
}
((PolylineConnection)getConnection()).setLineStyle(getMessage().getLineStyle());
((DeferredLayoutPolylineConnection)getConnection()).setDirty(true);
}
/**
*
*/
private void resetLabelImage() {
if (localImage != null && !localImage.isDisposed()) {
localImage.dispose();
localImage = null;
}
Image icon = getWidget().getImage();
if (icon == null) {
label.setIcon(null);
} else if (icon.getImageData().height > 10) {
//scale the image.
ImageData iconData = icon.getImageData();
float scale = ((float)10)/iconData.height;
int newWidth = (int)(scale*iconData.width);
ImageData data = icon.getImageData().scaledTo(newWidth, 10);
localImage = new Image(icon.getDevice(), data);
label.setIcon(localImage);
} else {
label.setIcon(getWidget().getImage());
}
}
private void updateDecorations() {
updateSourceDecoration();
updateTargetDecoration();
}
private RotatableDecoration getDecoration(int style) {
int unmasked = ((style | Message.FILL_MASK) ^ Message.FILL_MASK);
boolean filled = (style & Message.FILL_MASK) != 0;
RotatableDecoration dec = null;
switch (unmasked) {
case Message.NONE:
dec = null;
break;
case Message.CLOSED_ARROW:
dec = new PolygonDecoration();
((PolygonDecoration)dec).setFill(filled);
((PolygonDecoration)dec).setScale(4,3);
break;
case Message.OPEN_ARROW:
if (filled) {
dec = new PolygonDecoration();
((PolygonDecoration)dec).setScale(4,3);
((PolygonDecoration)dec).setFill(filled);
} else {
dec = new PolylineDecoration();
((PolylineDecoration)dec).setScale(4,3);
}
break;
case Message.CIRCLE:
dec = new CircleDecoration();
((CircleDecoration)dec).setFill(filled);
((CircleDecoration)dec).setSize(new Dimension(4,4));
break;
case Message.DIAMOND:
dec = new PolygonDecoration();
((PolygonDecoration)dec).setPoints(DIAMOND_TIP);
((PolygonDecoration)dec).setFill(filled);
((PolygonDecoration)dec).setScale(4,3);
break;
case Message.SQUARE:
dec = new PolygonDecoration();
((PolygonDecoration)dec).setPoints(SQUARE_TIP);
((PolygonDecoration)dec).setFill(filled);
((PolygonDecoration)dec).setScale(3,3);
break;
}
return dec;
}
/**
*
*/
private void updateSourceDecoration() {
sourceDec = getDecoration(getMessage().getSourceStyle());
((PolylineConnection)getConnection()).setSourceDecoration(sourceDec);
}
/**
*
*/
private void updateTargetDecoration() {
targetDec = getDecoration(getMessage().getTargetStyle());
((PolylineConnection)getConnection()).setTargetDecoration(targetDec);
}
/* (non-Javadoc)
* @see org.eclipse.mylar.zest.custom.sequence.visuals.ConnectionVisualPart#activate()
*/
@Override
public void activate() {
super.activate();
if (this.mouseOverInteraction == null) {
this.mouseOverInteraction = new ActivationHoverInteraction();
}
updateDecorations();
resetLabelImage();
mouseOverInteraction.hookInteraction(this);
getMessage().addPropertyChangeListener(this);
//getMessage().getChart().addPropertyChangeListener(this);
}
/* (non-Javadoc)
* @see org.eclipse.mylar.zest.custom.sequence.visuals.ConnectionVisualPart#deactivate()
*/
@Override
public void deactivate() {
if (!isActive()) return;
super.deactivate();
mouseOverInteraction.unhookInteraction();
if (localImage != null && !localImage.isDisposed()) {
localImage.dispose();
}
getMessage().removePropertyChangeListener(this);
if (!getWidget().isDisposed())
getWidget().setData(IWidgetProperties.LAYOUT, null);
//getMessage().getChart().removePropertyChangeListener(this);
}
/* (non-Javadoc)
* @see org.eclipse.mylar.zest.custom.sequence.widgets.PropertyChangeListener#propertyChanged(java.lang.Object, java.lang.String, java.lang.Object, java.lang.Object)
*/
public void propertyChanged(Object source, String property, Object oldValue, Object newValue) {
if (IWidgetProperties.LAYOUT.equals(property)) {
if (getChartVisuals().refreshing) return; //early return
needsRouting = true;
((DeferredLayoutPolylineConnection)getConnection()).setDirty(true);
refreshVisuals();
} else if ("pin".equals(property) || IWidgetProperties.HIGHLIGHT.equals(property)) {
needsRouting = true;
((DeferredLayoutPolylineConnection)getConnection()).setDirty(true);
refreshVisuals();
} else if (IWidgetProperties.TOOLTIP.equals(property)) {
Label tooltipFigure = (Label) getFigure().getToolTip();
String tooltipText = getWidget().getTooltipText();
if (tooltipText == null) {
tooltipText = getWidget().getText();
}
tooltipFigure.setText(tooltipText);
} else if (IWidgetProperties.DECORATION.equals(property)) {
updateDecorations();
} else if (IWidgetProperties.IMAGE.equals(property)) {
resetLabelImage();
} else if (source == getWidget()) {
if (IWidgetProperties.BACKGROUND_COLOR.equals(property) ||
IWidgetProperties.FOREGROUND_COLOR.equals(property) ||
IWidgetProperties.TEXT.equals(property) ||
IWidgetProperties.TEXT_BACKGROUND.equals(property) ||
IWidgetProperties.TEXT_FOREGROUND.equals(property))
refreshVisuals();
}
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.visuals.ConnectionVisualPart#nodePropertyChanged(org.eclipse.zest.custom.sequence.visuals.NodeVisualPart, java.lang.String, java.lang.Object, java.lang.Object)
*/
@Override
protected void nodePropertyChanged(NodeVisualPart nodeVisualPart,
String property, Object oldValue, Object newValue) {
if (IWidgetProperties.LAYOUT.equals(property) || IWidgetProperties.EXPANDED.equals(property)) {
needsRouting = true;
((DeferredLayoutPolylineConnection)getConnection()).setDirty(true);
getConnection().invalidate();
}
}
}