/*******************************************************************************
* 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.beans.PropertyChangeEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Layer;
import org.eclipse.draw2d.LayeredPane;
import org.eclipse.draw2d.LayoutAnimator;
import org.eclipse.draw2d.StackLayout;
import org.eclipse.draw2d.UpdateListener;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.zest.custom.sequence.figures.RectangleZoomManager;
import org.eclipse.zest.custom.sequence.figures.internal.AntialiasingScalableFreeformLayeredPane;
import org.eclipse.zest.custom.sequence.figures.internal.AntialiasingScalableLayeredPane;
import org.eclipse.zest.custom.sequence.figures.internal.QuickClearFreeformLayer2;
import org.eclipse.zest.custom.sequence.internal.AbstractSimpleProgressRunnable;
import org.eclipse.zest.custom.sequence.internal.SimpleProgressMonitor;
import org.eclipse.zest.custom.sequence.internal.UIJobProcessor;
import org.eclipse.zest.custom.sequence.tools.IWidgetFinder;
import org.eclipse.zest.custom.sequence.widgets.Activation;
import org.eclipse.zest.custom.sequence.widgets.Lifeline;
import org.eclipse.zest.custom.sequence.widgets.Message;
import org.eclipse.zest.custom.sequence.widgets.MessageGroup;
import org.eclipse.zest.custom.sequence.widgets.PropertyChangeListener;
import org.eclipse.zest.custom.sequence.widgets.UMLItem;
import org.eclipse.zest.custom.sequence.widgets.UMLSequenceChart;
import org.eclipse.zest.custom.sequence.widgets.internal.IWidgetProperties;
/**
* A new approach to drawing the sequence chart. These visuals try and listen
* only for events that cause an item in the sequence chart to be dirty. These
* will include such things as when items are created, destroyed, or
* reconnected. These visuals no longer use a clone pain or a package pane.
* Instead, it will expose functionality to make for easier linking between
* viewers. The package pane will have to be reimplemented as a new viewer.
*
* @author Del Myers
*
*/
public class MessageBasedSequenceVisuals implements PropertyChangeListener, DisposeListener, IWidgetFinder {
/**
* The data key used to access the visual part for an item.
*/
public static final String VISUAL_KEY = "visuals";
/**
* A marker for activations to indicate that they should not be visited again during refresh.
*/
private static final String VISIT_MARKER = "rfshVisit";
public static final int OBJECT_HEIGHT = 60;
/**
* The sequence chart that these visuals work for.
*/
private UMLSequenceChart chart;
private HashMap<IFigure, UMLItem> figureItemMap;
//private FreeformLayer objectContents;
private AntialiasingScalableFreeformLayeredPane primaryPane;
private UMLItem[] currentSelection;
private ArrayList<WidgetVisualPart> visuals;
private final Object REFRESH_FAMILY = new Object();
private final Object LAYOUT_FAMILY;
private AntialiasingScalableLayeredPane objectLayers;
private SelectionDecorationLayer selectionDecorator;
private AntialiasingScalableLayeredPane objectGroupLayers;
public boolean refreshing;
private class RefreshWithProgressRunnable extends AbstractSimpleProgressRunnable {
/**
* Used to make sure that the when we are processing messages to activations that have already been
* visited, that they are reachable on the current call stack. Otherwise, the message is invalid.
*/
private HashSet<Activation> callStack;
//private TreeSet<UMLItem> visited;
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.internal.AbstractSimpleProgressRunnable#doRunInUIThread(org.eclipse.zest.custom.sequence.internal.SimpleProgressMonitor)
*/
@Override
protected void doRunInUIThread(SimpleProgressMonitor monitor)
throws InvocationTargetException {
//There are several things that have to be done:
//1: keep selection
//2: for each dirty item, reconnect it
//3: on a concurrent modification exception, quit and let the next runnable run the change.
//we must synchronize this whole process on the dirty items
chart.getSequenceControl().setCursor(chart.getDisplay().getSystemCursor(SWT.CURSOR_WAIT));
callStack = new HashSet<Activation>();
MessageBasedSequenceVisuals.this.refreshing = true;
//long time = System.currentTimeMillis();
monitor.beginTask("Refreshing Chart", 5001);
//update selection
ArrayList<UMLItem> newSelection = new ArrayList<UMLItem>();
for (UMLItem item : currentSelection) {
if (!item.isDisposed()) {
newSelection.add(item);
}
}
monitor.worked(1);
readAndDispatch();
try {
updateChart(monitor);
selectionDecorator.refresh();
} catch (ConcurrentModificationException e) {
callStack.clear();
monitor.done();
//do nothing, let the next run complete the update.
}
MessageBasedSequenceVisuals.this.refreshing = false;
//System.err.println(System.currentTimeMillis() - time);
chart.getSequenceControl().setCursor(null);
}
/**
* @param monitor
*/
private void updateChart(SimpleProgressMonitor monitor) {
//mark all the current visuals as "trashed" so that we can remove them later.
ArrayList<WidgetVisualPart> newVisuals = new ArrayList<WidgetVisualPart>();
HashSet<WidgetVisualPart> trash = new HashSet<WidgetVisualPart>();
try {
trash.addAll(visuals);
Activation root = chart.getRootActivation();
visitActivation(root, newVisuals, monitor);
//activate all the new parts
LinkedList<ConnectionVisualPart> connections = new LinkedList<ConnectionVisualPart>();
//disconnect the panes from their parents so that an update only has to be done once.
disconnectPanes();
for (WidgetVisualPart v : newVisuals) {
if (v instanceof ConnectionVisualPart) {
//add to a list of connections so that we can re-connect after the endpoints are
//activated.
connections.add((ConnectionVisualPart) v);
} else {
if (!v.isActive()) {
v.activate();
}
v.installFigures();
v.refreshVisuals();
}
trash.remove(v);
unvisit(v.getWidget());
}
while (connections.size() > 0) {
ConnectionVisualPart v = connections.removeFirst();
UMLItem widget = v.getWidget();
if (widget instanceof Message) {
Message m = (Message) widget;
NodeVisualPart oldSourceVisual = v.getSource();
NodeVisualPart sourceVisual = (NodeVisualPart) m.getSource().getData(VISUAL_KEY);
NodeVisualPart oldTargetVisual = v.getTarget();
NodeVisualPart targetVisual = (NodeVisualPart) m.getTarget().getData(VISUAL_KEY);
if ((oldSourceVisual == null) || (sourceVisual.getWidget() != oldSourceVisual.getWidget())) {
if (v.isActive()) {
v.deactivate();
}
if (oldSourceVisual != null) {
oldSourceVisual.removeSourceConnection(v);
}
sourceVisual.addSourceConnection(v);
}
if ((oldTargetVisual == null) || (targetVisual.getWidget() != oldTargetVisual.getWidget())) {
if (v.isActive()) {
v.deactivate();
}
if (oldTargetVisual != null) {
oldTargetVisual.removeSourceConnection(v);
}
if (targetVisual != null) {
targetVisual.addTargetConnection(v);
}
}
if (!v.isActive()) {
v.activate();
}
v.installFigures();
v.refreshVisuals();
}
}
//delete the trash
for (WidgetVisualPart v : visuals) {
if (!v.getWidget().isDisposed()) {
unvisit(v.getWidget());
if (trash.contains(v)) {
v.deactivate();
}
} else {
v.deactivate();
}
}
} finally {
for (WidgetVisualPart v : newVisuals) {
unvisit(v.getWidget());
}
reconnectPanes();
}
visuals = newVisuals;
}
/**
* Visits the given activation, updating its corresponding visuals.
* @param root
* @param newVisuals
*/
private void visitActivation(Activation activation,
ArrayList<WidgetVisualPart> newVisuals, SimpleProgressMonitor monitor) {
//we aren't dealing with a simple tree anymore, so we have mark each activation as
//visited, in order to avoid cycles.
if (activation.isDisposed()) {
return;
}
if (visited(activation)) {
return;
}
setVisited(activation);
addToCallStack(activation);
//check the lifeline to make sure that there is a visual part for it.
Lifeline activationLifeline = activation.getVisibleLifeline();
visitLifeline(activationLifeline, newVisuals);
//add a visual for this activation.
ActivationVisual aVisual = (ActivationVisual) activation.getData(VISUAL_KEY);
if (aVisual == null) {
aVisual = new ActivationVisual(activation, VISUAL_KEY);
}
newVisuals.add(aVisual);
Message[] messages = activation.getMessages();
MessageGroup[] groups = activation.getMessageGroups();
int groupIndex = 0;
for (int i = 0; i < messages.length; i++) {
Message m = messages[i];
boolean groupClosed = false;
int end = i;
while (groupIndex < groups.length && groups[groupIndex].getOffset() <= end) {
MessageGroup group = groups[groupIndex];
if (!groupClosed && activation.isExpanded() && group.getLength() >= 0) {
MessageGroupVisual gv = (MessageGroupVisual) group.getData(VISUAL_KEY);
if (gv == null) {
gv = new MessageGroupVisual(group, VISUAL_KEY);
}
newVisuals.add(gv);
}
if (!activation.isExpanded() || (!group.isExpanded())) {
if (group.getLength() > 0) {
groupClosed = true;
//set the next index forward, so that everything else gets hidden.
i = group.getOffset() + group.getLength() - 1;
end = i;
}
}
groupIndex++;
}
if (groupClosed) {
//don't process any more messages inside the group.
continue;
}
monitor.setSubTask(m.getText());
Activation target = m.getTarget();
if (target != null && target != activation && !m.isHidden()) {
if (!visited(target)) {
if (target.getSourceCall() != m) {
//the target isn't reachable, just continue.
MessageVisual mVisual = (MessageVisual) m.getData(VISUAL_KEY);
if (mVisual == null) {
mVisual = new MessageVisual(m, VISUAL_KEY);
}
newVisuals.add(mVisual);
continue;
}
}
if (activation.isExpanded()) {
//always show all messages if the activation is expanded.
MessageVisual mVisual = (MessageVisual) m.getData(VISUAL_KEY);
if (mVisual == null) {
mVisual = new MessageVisual(m, VISUAL_KEY);
}
newVisuals.add(mVisual);
visitActivation(target, newVisuals, monitor);
} else if (visited(target)) {
if (!isOnCallStack(target)) {
//this is a malformed sequence diagram. Just ignore the message.
//@tag sequencediagram.revisit: Maybe we should throw an exception?
continue;
}
//this message is a return of some sort, so we have to display it.
//@tag sequencediagram.revisit : maybe just don't show the return if the activation is collapsed
//we certainly shouldn't show the message groups.
MessageVisual mVisual = (MessageVisual) m.getData(VISUAL_KEY);
if (mVisual == null) {
mVisual = new MessageVisual(m, VISUAL_KEY);
}
newVisuals.add(mVisual);
}
}
}
removeFromCallStack(activation);
if (monitor.isCancelled()) {
throw new ConcurrentModificationException();
}
readAndDispatch();
}
/**
* Visits the given lifeline to create all of the visuals for it.
* @param activationLifeline
* @param newVisuals
*/
private void visitLifeline(Lifeline activationLifeline,
ArrayList<WidgetVisualPart> newVisuals) {
if (activationLifeline == null || visited(activationLifeline)) {
return;
}
setVisited(activationLifeline);
WidgetVisualPart o = (WidgetVisualPart) activationLifeline.getData(VISUAL_KEY);
LifelineVisual llVisual = null;
if (!(o instanceof LifelineVisual)) {
if (o != null) {
o.deactivate();
}
llVisual = new LifelineVisual(activationLifeline, VISUAL_KEY);
llVisual.activate();
} else {
llVisual = (LifelineVisual) o;
}
newVisuals.add(llVisual);
//walk up the hierarchy, adding all the parents.
Lifeline parent = activationLifeline.getParent();
while (parent != null) {
if (visited(parent)) {
break;
}
o = (WidgetVisualPart) parent.getData(VISUAL_KEY);
if (!(o instanceof LifelineGroupVisual)) {
if (o != null) {
o.deactivate();
}
o = new LifelineGroupVisual(parent, VISUAL_KEY);
o.activate();
}
newVisuals.add(o);
setVisited(parent);
parent = parent.getParent();
}
}
private void addToCallStack(Activation a) {
callStack.add(a);
}
private boolean isOnCallStack(Activation a) {
return callStack.contains(a);
}
private void removeFromCallStack(Activation a) {
callStack.remove(a);
}
private void setVisited(Widget a) {
a.setData(VISIT_MARKER, Boolean.TRUE);
}
private boolean visited(Widget a) {
return a.getData(VISIT_MARKER) != null;
}
private void unvisit(Widget a) {
a.setData(VISIT_MARKER, null);
}
}
public MessageBasedSequenceVisuals(UMLSequenceChart chart) {
this.chart = chart;
chart.addPropertyChangeListener(this);
chart.addDisposeListener(this);
figureItemMap = new HashMap<IFigure, UMLItem>();
currentSelection = new UMLItem[0];
visuals = new ArrayList<WidgetVisualPart>();
this.LAYOUT_FAMILY = SequenceLayoutRunnable.getFamily(chart);
}
/**
*
*/
protected void reconnectPanes() {
FigureCanvas groupCanvas = (FigureCanvas)chart.getLifelineGroupControl();
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
FigureCanvas objectCanvas = (FigureCanvas)chart.getLifelineControl();
groupCanvas.getViewport().setContents(objectGroupLayers);
sequenceCanvas.getViewport().setContents(primaryPane);
objectCanvas.getViewport().setContents(objectLayers);
}
/**
*
*/
protected void disconnectPanes() {
quickClear(primaryPane);
quickClear(objectLayers);
quickClear(objectGroupLayers);
FigureCanvas groupCanvas = (FigureCanvas)chart.getLifelineGroupControl();
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
FigureCanvas objectCanvas = (FigureCanvas)chart.getLifelineControl();
groupCanvas.getViewport().setContents(null);
sequenceCanvas.getViewport().setContents(null);
objectCanvas.getViewport().setContents(null);
}
/*
* (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) {
}
/* (non-Javadoc)
* @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
*/
public void widgetDisposed(DisposeEvent e) {
primaryPane.removeAll();
objectLayers.removeAll();
visuals.clear();
currentSelection = null;
figureItemMap.clear();
}
/**
* Rebuilds the panels used in these visuals.
*/
public void createFigures() {
objectGroupLayers = new AntialiasingScalableLayeredPane(){
/* (non-Javadoc)
* @see org.eclipse.draw2d.Figure#setBounds(org.eclipse.draw2d.geometry.Rectangle)
*/
@Override
public void setBounds(Rectangle rect) {
// TODO Auto-generated method stub
super.setBounds(rect);
}
};
objectGroupLayers.setFont(chart.getFont());
objectGroupLayers.setAntialiasing(SWT.ON);
objectGroupLayers.addFigureListener(new FigureListener(){
public void figureMoved(IFigure source) {
source.getBounds();
}
});
objectGroupLayers.setLayoutManager(new StackLayout(){
/* (non-Javadoc)
* @see org.eclipse.draw2d.StackLayout#calculatePreferredSize(org.eclipse.draw2d.IFigure, int, int)
*/
@Override
protected Dimension calculatePreferredSize(IFigure figure,
int wHint, int hHint) {
org.eclipse.swt.graphics.Point s =
getChart().getLifelineGroupControl().getSize();
Dimension preferred = super.calculatePreferredSize(figure, wHint, hHint);
return new Dimension(Math.max(s.x, preferred.width), Math.max(s.y, preferred.height));
}
/* (non-Javadoc)
* @see org.eclipse.draw2d.StackLayout#calculateMinimumSize(org.eclipse.draw2d.IFigure, int, int)
*/
@Override
protected Dimension calculateMinimumSize(IFigure figure, int hint,
int hint2) {
org.eclipse.swt.graphics.Point s =
getChart().getLifelineGroupControl().getSize();
return new Dimension(s.x, s.y);
}
});
Layer objectGroupContents = new FreeformLayer();
objectGroupContents.setFont(chart.getFont());
objectGroupContents.setLayoutManager(new ContainmentTreeLayout());
objectGroupLayers.add(objectGroupContents, LayerConstants.OBJECT_GROUP_LAYER);
Layer objectGroupConnections = new ConnectionLayer();
objectGroupConnections.setEnabled(false);
objectGroupLayers.add(objectGroupConnections, LayerConstants.OBJECT_GROUP_CONNECTION_LAYER);
final Layer objectContents = new Layer() {
/* (non-Javadoc)
* @see org.eclipse.draw2d.Layer#containsPoint(int, int)
*/
@Override
public boolean containsPoint(int x, int y) {
//don't want this layer to be transparent
return getBounds().contains(x, y);
}
};
objectContents.setFont(chart.getFont());
objectContents.setLayoutManager(new XYLayout(){
@Override
protected Dimension calculatePreferredSize(IFigure figure,
int wHint, int hHint) {
Dimension size = super.calculatePreferredSize(figure, wHint, hHint);
if (primaryPane != null) {
size.width = primaryPane.getSize().width;
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
if (sequenceCanvas.getVerticalBar() != null && sequenceCanvas.getVerticalBar().isVisible()) {
size.width += sequenceCanvas.getVerticalBar().getSize().x;
}
}
//return super.calculatePreferredSize(figure, hint, hint2);
return size;
}
});
objectLayers = new AntialiasingScalableLayeredPane();
objectLayers.setFont(chart.getFont());
objectLayers.setLayoutManager(new StackLayout(){
/* (non-Javadoc)
* @see org.eclipse.draw2d.StackLayout#calculatePreferredSize(org.eclipse.draw2d.IFigure, int, int)
*/
@Override
protected Dimension calculatePreferredSize(IFigure figure,
int hint, int hint2) {
//use the actual size of the children, rather than their
//preferred size. This is a little bit of a hack.
List<?> children = figure.getChildren();
Dimension size = new Dimension();
for (Iterator<?> i = children.iterator(); i.hasNext();) {
IFigure child = (IFigure)i.next();
size.union(child.getSize());
}
if (primaryPane != null) {
size.width = primaryPane.getSize().width;
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
if (sequenceCanvas.getVerticalBar() != null && sequenceCanvas.getVerticalBar().isVisible()) {
size.width += sequenceCanvas.getVerticalBar().getSize().x;
}
}
//return super.calculatePreferredSize(figure, hint, hint2);
return size;
}
});
objectLayers.setAntialiasing(SWT.ON);
objectLayers.add(objectContents, LayerConstants.OBJECT_LAYER);
//objectContents.addLayoutListener(LayoutAnimator.getDefault());
//objectContents.setLayoutManager(new FreeformLayout());
this.primaryPane = new AntialiasingScalableFreeformLayeredPane();
primaryPane.setFont(chart.getFont());
primaryPane.setAntialiasing(SWT.ON);
//primaryPane.setLayoutManager(new StackLayout());
createPrimaryLayers();
primaryPane.addFigureListener(new FigureListener(){
public void figureMoved(IFigure source) {
//make sure that the object layers have the same width
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
Dimension d = new Dimension(source.getSize().width, OBJECT_HEIGHT);
if (sequenceCanvas.getVerticalBar() != null && sequenceCanvas.getVerticalBar().isVisible()) {
d.width += sequenceCanvas.getVerticalBar().getSize().x;
}
objectContents.setSize(d);
}
});
selectionDecorator = new SelectionDecorationLayer(this);
primaryPane.add(selectionDecorator.getLayer(), selectionDecorator.getLayerKey());
FigureCanvas groupCanvas = (FigureCanvas)chart.getLifelineGroupControl();
groupCanvas.getViewport().setContents(objectGroupLayers);
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
FigureCanvas objectCanvas = (FigureCanvas)chart.getLifelineControl();
sequenceCanvas.getViewport().setContents(primaryPane);
objectCanvas.getViewport().setContents(objectLayers);
sequenceCanvas.getViewport().getHorizontalRangeModel().addPropertyChangeListener(
new java.beans.PropertyChangeListener(){
public void propertyChange(PropertyChangeEvent evt) {
FigureCanvas sequenceCanvas = (FigureCanvas)chart.getSequenceControl();
FigureCanvas objectCanvas = (FigureCanvas)chart.getLifelineControl();
int newValue = sequenceCanvas.getViewport().getHorizontalRangeModel().getValue();
objectCanvas.getViewport().getHorizontalRangeModel().setValue(newValue);
objectCanvas.getViewport().getContents().repaint();
}
}
);
sequenceCanvas.getLightweightSystem().getUpdateManager().addUpdateListener(new UpdateListener(){
@SuppressWarnings("unchecked")
public void notifyPainting(Rectangle damage, Map dirtyRegions) {
}
public void notifyValidating() {
FigureCanvas objectCanvas = (FigureCanvas)chart.getLifelineControl();
objectCanvas.getLightweightSystem().getRootFigure().invalidateTree();
objectCanvas.getLightweightSystem().getRootFigure().revalidate();
}
});
RectangleZoomManager zoomManager = new RectangleZoomManager(primaryPane, sequenceCanvas.getViewport());
chart.setData("ZoomManager", zoomManager);
return;
}
private void createPrimaryLayers() {
IFigure l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.BACKGROUND_LAYER);
l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.LIFELINE_LAYER);
l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.PRIMARY_LAYER);
l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.CONNECTION_LAYER);
l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.FEEDBACK_LAYER);
l = newLayer();
l.setLayoutManager(new FreeformLayout());
l.addLayoutListener(LayoutAnimator.getDefault());
primaryPane.add(l, LayerConstants.ACTIVE_FEEDBACK_LAYER);
}
private IFigure newLayer() {
return new QuickClearFreeformLayer2();
//return new FreeformLayer();
}
/**
* Quickly clears all of the children on the given parent.
* @param parent
*/
protected void quickClear(IFigure parent) {
for (Object child : parent.getChildren()) {
((Figure)child).removeAll();
}
}
@SuppressWarnings("unchecked")
protected void register(WidgetVisualPart part) {
LinkedList stack = new LinkedList();
stack.addAll(part.getFigures());
while (stack.size() > 0) {
IFigure fig = (IFigure) stack.removeFirst();
figureItemMap.put(fig, part.getWidget());
stack.addAll(fig.getChildren());
}
}
@SuppressWarnings("unchecked")
protected void deregister(WidgetVisualPart part) {
LinkedList stack = new LinkedList();
stack.addAll(part.getFigures());
while (stack.size() > 0) {
IFigure fig = (IFigure) stack.removeFirst();
figureItemMap.remove(fig);
stack.addAll(fig.getChildren());
}
}
/**
* An item is visible only if it has an active visual part.
* @param item
* @return
*/
public boolean isVisible(UMLItem item) {
WidgetVisualPart part = (WidgetVisualPart) item.getData(VISUAL_KEY);
return (part != null && part.isActive());
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.sequence.tools.IWidgetFinder#getWidget(org.eclipse.draw2d.IFigure)
*/
public Widget getWidget(IFigure figure) {
return figureItemMap.get(figure);
}
public void setSelection(UMLItem[] selection) {
HashSet<UMLItem> newSelectionSet = new HashSet<UMLItem>(Arrays.asList(selection));
if (currentSelection != null) {
for (UMLItem unselected : currentSelection) {
if (!newSelectionSet.contains(unselected)) {
//unpin it.
pin(unselected, false);
}
}
}
if (selection != null) {
for (UMLItem selected : selection) {
pin(selected, true);
}
}
currentSelection = selection;
}
/**
* Sets the "pinned" state of the given item to the passed state. In the case
* of activations, all of the sub messages, and sub-activations are also set.
* @param selected
* @param pinned
*/
private void pin(UMLItem selected, boolean pinned) {
if (pinned == isPinned(selected)) {
return;
}
selected.setData("pin", ((pinned) ? true : null));
// if (selected instanceof Activation) {
// for (Message m : ((Activation)selected).getMessages()) {
// if (m.getSource() == selected) {
// pin(m, pinned);
// }
// }
// } if (selected instanceof Message) {
// Message m = (Message) selected;
// if (m instanceof Call) {
// //pin the activations on the "creator" messages.
// pin(m.getTarget(), pinned);
// }
// }
}
private boolean isPinned(UMLItem item) {
return Boolean.TRUE.equals(item.getData("pin"));
}
/**
* Returns the display location of the given item in the visuals, either in the
* package pane or in the main pain.
* @param item
* @return
*/
public Rectangle getLocation(UMLItem item) {
if (item == null || item.isDisposed()) {
return null;
}
WidgetVisualPart visual = (WidgetVisualPart) item.getData(VISUAL_KEY);
if (visual == null) return null;
Rectangle p = visual.getFigure().getBounds().getCopy();
// visual.getFigure().translateFromParent(p);
// visual.getFigure().translateToAbsolute(p);
// ((FigureCanvas)chart.getSequenceControl()).getViewport();
return p;
}
/**
* @return
*/
public Rectangle getSequencePanelArea() {
return primaryPane.getClientArea();
}
/**
* @param progressService
*/
public void scheduleLayout(UIJobProcessor processor) {
processor.cancelJobsInFamily(LAYOUT_FAMILY);
processor.runInUIThread(new SequenceLayoutRunnable(chart), chart.getDisplay(), true);
}
/**
* @param progressService
*/
public void scheduleRefresh(UIJobProcessor processor) {
processor.cancelJobsInFamily(REFRESH_FAMILY);
processor.cancelJobsInFamily(LAYOUT_FAMILY);
processor.runInUIThread(new RefreshWithProgressRunnable(), chart.getDisplay(), false);
processor.runInUIThread(new SequenceLayoutRunnable(chart), chart.getDisplay(), true);
}
IFigure getLayer(Object key) {
IFigure layer = ((LayeredPane)primaryPane).getLayer(key);
if (layer == null) {
layer = objectLayers.getLayer(key);
} if (layer == null) {
layer = objectGroupLayers.getLayer(key);
}
return layer;
}
/**
* returns the chart that these visuals are working on.
*/
public UMLSequenceChart getChart() {
return chart;
}
/**
* Returns the primary figure for the given widget.
* @param the item to get the figure for.
*/
public IFigure getFigure(UMLItem item) {
if (item == null || item.isDisposed()) {
return null;
}
WidgetVisualPart part = (WidgetVisualPart) item.getData(VISUAL_KEY);
if (part != null) {
return part.getFigure();
}
return null;
}
/**
* Returns the lifelines that are currently visible, in the order that they appear.
* @return
*/
public Lifeline[] getVisibleLifelines() {
TreeSet<Lifeline> lines = new TreeSet<Lifeline>(new Comparator<Lifeline>(){
public int compare(Lifeline o1, Lifeline o2) {
Rectangle layout1 = (Rectangle) o1.getData(IWidgetProperties.LAYOUT);
Rectangle layout2 = (Rectangle) o2.getData(IWidgetProperties.LAYOUT);
if (layout1 == null && layout2 == null) {
return 0;
} else if (layout1 == null) {
return -1;
} else if (layout2 == null) {
return 1;
}
return layout1.x - layout2.x;
}
});
IFigure layer = getLayer(LayerConstants.OBJECT_LAYER);
for (Object o : layer.getChildren()) {
IFigure child = (IFigure) o;
Widget w = getWidget(child);
if (w instanceof Lifeline) {
lines.add((Lifeline) w);
}
}
return lines.toArray(new Lifeline[lines.size()]);
}
/**
* @param parent
*/
public WidgetVisualPart getVisualPart(UMLItem item) {
return (WidgetVisualPart) item.getData(VISUAL_KEY);
}
/**
* Checks where the given item is displayed, and returns its bounds relative to the
* chart's composite.
* @param item
* @return
*/
public Rectangle getRelativeLocation(UMLItem item) {
FigureCanvas canvas = (FigureCanvas) getContainingComposite(item);
if (canvas == null) {
return null;
}
IFigure figure = getFigure(item);
if (figure.getParent() == null) {
return null;
}
Rectangle relative = figure.getBounds().getCopy();
figure.getParent().translateToAbsolute(relative);
Point p = canvas.toDisplay(relative.x, relative.y);
p = getChart().toControl(p);
relative.setLocation(p.x, p.y);
return relative;
}
/**
* Returns the low level composite that the given item is contained within.
* @param item
* @return
*/
public Control getContainingComposite(UMLItem item) {
if (!isVisible(item)) {
return null;
}
if (item instanceof Lifeline) {
WidgetVisualPart part = getVisualPart(item);
if (part instanceof LifelineGroupVisual) {
return getChart().getLifelineGroupControl();
}
return getChart().getLifelineControl();
}
return getChart().getSequenceControl();
}
}