/*******************************************************************************
* 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.ArrayList;
import java.util.List;
import org.eclipse.draw2d.AbstractConnectionAnchor;
import org.eclipse.draw2d.ActionEvent;
import org.eclipse.draw2d.ActionListener;
import org.eclipse.draw2d.AnchorListener;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.GridData;
import org.eclipse.draw2d.GridLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.ShortestPathConnectionRouter;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.zest.custom.sequence.figures.PlusMinusFigure;
import org.eclipse.zest.custom.sequence.figures.SequenceClassFigure;
import org.eclipse.zest.custom.sequence.visuals.ContainmentTreeLayout.ContainmentTreeConstraint;
import org.eclipse.zest.custom.sequence.visuals.interactions.AbstractInteraction;
import org.eclipse.zest.custom.sequence.widgets.Lifeline;
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;
/**
* @author Del Myers
*
*/
public class LifelineGroupVisual extends WidgetVisualPart implements
PropertyChangeListener, ContainmentTreeConstraint {
private class FocusInteraction extends AbstractInteraction implements
MouseMotionListener {
@Override
protected void doHook() {
getFigure().addMouseMotionListener(this);
}
@Override
protected void doUnhook() {
getFigure().removeMouseMotionListener(this);
}
public void mouseEntered(MouseEvent me) {
getWidget().setData("hover", true);
}
public void mouseExited(MouseEvent me) {
if (!expanderFigure.containsPoint(me.x, me.y)) {
getWidget().setData("hover", null);
}
}
public void mouseHover(MouseEvent me) {
}
public void mouseMoved(MouseEvent me) {
}
public void mouseDragged(MouseEvent me) {
}
}
private class WidgetBasedConnectionAnchor extends AbstractConnectionAnchor {
private Lifeline lifeline;
public WidgetBasedConnectionAnchor(Lifeline lifeline) {
this.lifeline = lifeline;
}
/**
* This method assumes that the reference point is given in
* display-relative coordinates, not system relative.
*/
public Point getLocation(Point reference) {
checkOwner();
Rectangle chartRelative = getChartVisuals().getRelativeLocation(
getConnectionWidget());
if (chartRelative == null) {
return new Point(0, 0);
}
org.eclipse.swt.graphics.Point displayPoint = getChartVisuals()
.getChart().toDisplay(chartRelative.x, chartRelative.y);
Rectangle systemDisplayBounds = getSystemDisplayBounds();
displayPoint.x += chartRelative.width / 2;
// displayPoint.y += chartRelative.height / 2;
chartRelative.x = displayPoint.x;
chartRelative.y = displayPoint.y;
int locationX = displayPoint.x;
int locationY = displayPoint.y;
// translate to the system bounds
locationY -= systemDisplayBounds.y;
locationX -= systemDisplayBounds.x;
if (reference.y > locationY) {
locationY = displayPoint.y - systemDisplayBounds.y
+ chartRelative.height;
} else if (reference.y < locationY) {
locationY = displayPoint.y - systemDisplayBounds.y;
}
if (locationY > systemDisplayBounds.height) {
locationY = systemDisplayBounds.height;
}
return new Point((int) locationX, (int) locationY);
}
@Override
public Point getReferencePoint() {
checkOwner();
Rectangle chartRelative = getChartVisuals().getRelativeLocation(
getConnectionWidget());
if (chartRelative == null) {
return new Point(0, 0);
}
org.eclipse.swt.graphics.Point p = getChartVisuals().getChart()
.toDisplay(chartRelative.x, chartRelative.y);
p.x += chartRelative.width / 2;
p.y += chartRelative.height / 2;
Rectangle systemDisplayBounds = getSystemDisplayBounds();
p.x -= systemDisplayBounds.x;
p.y -= systemDisplayBounds.y;
return new Point(p.x, p.y);
}
protected void checkOwner() {
IFigure oldOwner = getOwner();
IFigure figure = getChartVisuals().getFigure(getConnectionWidget());
if (oldOwner != figure) {
if (oldOwner != null) {
oldOwner.removeAncestorListener(this);
}
setOwner(figure);
if (figure != null) {
figure.addAncestorListener(this);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.draw2d.AbstractConnectionAnchor#addAnchorListener(org.eclipse.draw2d.AnchorListener)
*/
@SuppressWarnings("unchecked")
@Override
public void addAnchorListener(AnchorListener listener) {
listeners.add(listener);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.draw2d.AbstractConnectionAnchor#removeAnchorListener(org.eclipse.draw2d.AnchorListener)
*/
@Override
public void removeAnchorListener(AnchorListener listener) {
listeners.remove(listener);
}
/**
* @return
*/
protected UMLItem getConnectionWidget() {
return lifeline;
}
}
private List<Connection> connections;
//needed in order to keep track of when children are disposed.
private Lifeline[] widgetChildren;
private PlusMinusFigure expanderFigure;
private FocusInteraction focusser;
/**
* @param item
* @param key
*/
public LifelineGroupVisual(UMLItem item, String key) {
super(item, key);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#createFigures()
*/
@Override
public IFigure createFigures() {
int type = SequenceClassFigure.CLASS;
switch (getCastedModel().getTargetStyle()) {
case Lifeline.ACTOR:
type = SequenceClassFigure.ACTOR;
break;
case Lifeline.BOUNDARY:
type = SequenceClassFigure.BOUNDARY;
break;
case Lifeline.COLLECTION:
type = SequenceClassFigure.COLLECTION;
break;
case Lifeline.CONTROL:
type = SequenceClassFigure.CONTROL;
break;
case Lifeline.DATA_STORE:
type = SequenceClassFigure.DATA_STORE;
break;
case Lifeline.ENTITY:
type = SequenceClassFigure.ENTITY;
break;
case Lifeline.PACKAGE:
type = SequenceClassFigure.PACKAGE;
break;
}
SequenceClassFigure figure = new SequenceClassFigure(type);
GridLayout layout = new GridLayout(1, true);
layout.marginHeight = 1;
layout.marginWidth = 1;
figure.setLayoutManager(layout);
Label l = new Label();
l.setText(getWidget().getText());
figure.setToolTip(l);
expanderFigure = new PlusMinusFigure(9);
expanderFigure.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
Lifeline l = getCastedModel();
l.setExpanded(!l.isExpanded());
}
});
expanderFigure.addMouseMotionListener(new MouseMotionListener(){
public void mouseDragged(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me) {
if (!getFigure().containsPoint(me.x, me.y)) {
getWidget().setData("hover", null);
}
}
public void mouseHover(MouseEvent me) {}
public void mouseMoved(MouseEvent me) {}
});
return figure;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#activate()
*/
@Override
public void activate() {
super.activate();
getWidget().addPropertyChangeListener(this);
focusser = new FocusInteraction();
focusser.hookInteraction(this);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#deactivate()
*/
@Override
public void deactivate() {
super.deactivate();
disconnect();
if (!getWidget().isDisposed()) {
getWidget().setData("focus", null);
getWidget().setHighlight(false);
}
getWidget().removePropertyChangeListener(this);
focusser.unhookInteraction();
}
/**
* @return
*/
private Lifeline getCastedModel() {
return (Lifeline) getWidget();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#refreshVisuals()
*/
@Override
public void refreshVisuals() {
SequenceClassFigure figure = (SequenceClassFigure) getFigure();
figure.setClassName(getCastedModel().getText());
figure.setBackgroundColor(getCastedModel().getBackground());
figure.setForegroundColor(getCastedModel().getForeground());
// check to see if either the parent or the children are visible. If so,
// update the
// parent-child relationship in the layout
reconnect();
if (Boolean.TRUE.equals(getWidget().getData("pin"))) {
figure.setLineWidth(2);
} else {
figure.setLineWidth(1);
}
IFigure layer = getLayer(LayerConstants.OBJECT_GROUP_LAYER);
if (layer.getLayoutManager() instanceof ContainmentTreeLayout) {
ContainmentTreeLayout layout = (ContainmentTreeLayout) layer
.getLayoutManager();
if (Boolean.TRUE.equals(getWidget().getData("hover")) ||
Boolean.TRUE.equals(getWidget().getData("pin"))) {
((ContainmentTreeLayout)layout).addFocus(layer, figure);
} else {
layout.removeFocus(layer, figure);
}
layout.invalidate();
}
if (connections != null) {
for (Connection c : connections) {
WidgetBasedConnectionAnchor a1 = (WidgetBasedConnectionAnchor) c
.getSourceAnchor();
WidgetBasedConnectionAnchor a2 = (WidgetBasedConnectionAnchor) c
.getTargetAnchor();
a1.checkOwner();
a2.checkOwner();
// c.setVisible(a1.getConnectionWidget().isVisible() &&
// a2.getConnectionWidget().isVisible());
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#installFigures()
*/
@Override
protected void installFigures() {
IFigure layer = getLayer(LayerConstants.OBJECT_GROUP_LAYER);
layer.add(getFigure(), this);
GridData data = new GridData(GridData.CENTER, GridData.BEGINNING,
false, false);
data.widthHint = 9;
data.heightHint = 9;
getFigure().add(expanderFigure, data);
reconnect();
}
/**
*
*/
private void reconnect() {
disconnect();
IFigure layer = getLayer(LayerConstants.OBJECT_GROUP_CONNECTION_LAYER);
widgetChildren = getCastedModel().getChildren();
connections = new ArrayList<Connection>();
for (int i = 0; i < widgetChildren.length; i++) {
if (!widgetChildren[i].isDisposed() && (widgetChildren[i].isExpanded() || getCastedModel().getData(
"pin") != null || getCastedModel().getData(
"hover") != null || widgetChildren[i].isHighlighted())
&&widgetChildren[i].isVisible()) {
PolylineConnection c = new PolylineConnection();
connections.add(c);
c.setConnectionRouter(new ShortestPathConnectionRouter(
getLayer(LayerConstants.OBJECT_GROUP_LAYER)));
c.setSourceAnchor(new WidgetBasedConnectionAnchor(
getCastedModel()));
c.setTargetAnchor(new WidgetBasedConnectionAnchor(widgetChildren[i]));
layer.add(c);
if (widgetChildren[i].isHighlighted()) {
c.setLineWidth(2);
}
}
widgetChildren[i].addPropertyChangeListener(this);
}
}
private void disconnect() {
if (widgetChildren != null) {
for (Lifeline l : widgetChildren) {
l.removePropertyChangeListener(this);
}
widgetChildren = null;
}
if (connections == null)
return;
while(connections.size() > 0) {
Connection c = connections.remove(0);
IFigure layer = c.getParent();
if (layer != null) {
layer.remove(c);
}
c.setSourceAnchor(null);
c.setTargetAnchor(null);
}
}
private Rectangle getSystemDisplayBounds() {
IFigure layer = getLayer(LayerConstants.OBJECT_GROUP_CONNECTION_LAYER);
Rectangle bounds = layer.getBounds().getCopy();
layer.translateToAbsolute(bounds);
Control c = getChartVisuals().getChart().getLifelineGroupControl();
org.eclipse.swt.graphics.Point p = c.getParent().toDisplay(
c.getLocation());
int width = c.getSize().x;
int height = c.getSize().y;
if (c instanceof Scrollable) {
width = ((Scrollable)c).getClientArea().width;
height = ((Scrollable)c).getClientArea().height;
}
return new Rectangle(p.x, p.y, width, height);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.zest.custom.sequence.visuals.WidgetVisualPart#uninstallFigures()
*/
@Override
protected void uninstallFigures() {
disconnect();
connections = null;
IFigure layer = getLayer(LayerConstants.OBJECT_GROUP_LAYER);
layer.getLayoutManager().setConstraint(getFigure(), null);
super.uninstallFigures();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.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 (getChartVisuals().refreshing) return; //early return
if (source != getCastedModel() && ("pin".equals(property))) {
// will propogate up the hierarchy
getWidget().setHighlight(Boolean.TRUE.equals(newValue));
} else if (source != getCastedModel()
&& (IWidgetProperties.HIGHLIGHT.equals(property))) {
// will propogate up the hierarchy
getWidget().setHighlight(Boolean.TRUE.equals(newValue));
} else if ("child".equals(property) && newValue == null) {
//child was removed, stop listening to it
if (source instanceof Lifeline) {
((Lifeline) source).removePropertyChangeListener(this);
}
}
refreshVisuals();
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.visuals.ContainmentTreeLayout.ContainmentTreeConstraint#getParentFigure()
*/
public IFigure getParentFigure() {
if (getCastedModel().getParent() != null) {
WidgetVisualPart parentPart = getChartVisuals().getVisualPart(getCastedModel().getParent());
if (parentPart != null) {
return parentPart.getFigure();
}
}
return null;
}
}