/******************************************************************************* * 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.widgets; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Map; 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.FreeformViewport; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.LayoutManager; import org.eclipse.draw2d.RangeModel; import org.eclipse.draw2d.UpdateListener; import org.eclipse.draw2d.XYLayout; 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.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.zest.custom.sequence.visuals.MessageBasedSequenceVisuals; /** * Creates a control that clones a UMLSequenceChart * @author Del Myers * */ class SequenceClone { /** * The canvas that will house the control. */ private UMLSequenceChart chart; private FigureCanvas localSequenceCanvas; private FigureCanvas localObjectCanvas; private Composite page; private PropertyChangeListener lifeLineScroller; private PropertyChangeListener verticalScroller; private PropertyChangeListener sourceSyncScroller; private PropertyChangeListener targetSyncScroller; private class CloneFigure extends Figure { private IFigure figureToClone; public CloneFigure(IFigure figureToClone) { this.figureToClone = figureToClone; } /* (non-Javadoc) * @see org.eclipse.draw2d.Figure#paint(org.eclipse.draw2d.Graphics) */ @Override public void paint(Graphics graphics) { figureToClone.paint(graphics); } } private class UpdateHook implements FigureListener, DisposeListener, UpdateListener { private IFigure cloneFigure; private FigureCanvas cloneCanvas; private FigureCanvas clonedCanvas; protected UpdateHook(FigureCanvas clonee, FigureCanvas clone, IFigure cloneFigure) { this.cloneFigure = cloneFigure; this.cloneCanvas = clone; this.clonedCanvas = clonee; clonedCanvas.getViewport().getContents().addFigureListener(this); clonedCanvas.getLightweightSystem().getUpdateManager().addUpdateListener(this); } /* (non-Javadoc) * @see org.eclipse.draw2d.FigureListener#figureMoved(org.eclipse.draw2d.IFigure) */ public void figureMoved(IFigure source) { if (cloneFigure.getParent() != null) { LayoutManager manager = cloneFigure.getParent().getLayoutManager(); if (manager instanceof XYLayout) { Rectangle bounds = source.getBounds(); manager.setConstraint(cloneFigure, new Rectangle(0,0, bounds.width, bounds.height)); cloneFigure.invalidate(); } } } /* (non-Javadoc) * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) */ public void widgetDisposed(DisposeEvent e) { if (!clonedCanvas.isDisposed()) { clonedCanvas.removeDisposeListener(this); } if (!cloneCanvas.isDisposed()) { cloneCanvas.removeDisposeListener(this); } clonedCanvas.getLightweightSystem().getUpdateManager().removeUpdateListener(this); clonedCanvas.getViewport().getContents().removeFigureListener(this); } /* (non-Javadoc) * @see org.eclipse.draw2d.UpdateListener#notifyPainting(org.eclipse.draw2d.geometry.Rectangle, java.util.Map) */ @SuppressWarnings("unchecked") public void notifyPainting(Rectangle damage, Map dirtyRegions) { cloneCanvas.getLightweightSystem().getUpdateManager().addDirtyRegion(cloneFigure, cloneFigure.getBounds().getCopy()); cloneCanvas.getLightweightSystem().getUpdateManager().performUpdate(cloneFigure.getBounds().getCopy()); } /* (non-Javadoc) * @see org.eclipse.draw2d.UpdateListener#notifyValidating() */ public void notifyValidating() { cloneCanvas.getLightweightSystem().getRootFigure().invalidateTree(); cloneCanvas.getLightweightSystem().getRootFigure().revalidate(); } } private class EventForwarder implements Listener { private FigureCanvas target; private FigureCanvas source; protected EventForwarder(FigureCanvas source, FigureCanvas target) { this.target = target; this.source = source; source.addListener(SWT.MouseMove, this); source.addListener(SWT.MouseUp, this); source.addListener(SWT.MouseDown, this); source.addListener(SWT.MouseHover, this); source.addListener(SWT.MouseWheel, this); source.addListener(SWT.MouseDoubleClick, this); source.addListener(SWT.KeyUp, this); source.addListener(SWT.KeyDown, this); target.addListener(SWT.Dispose, this); } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event) { Event e = new Event(); e.keyCode = event.keyCode; e.stateMask = event.stateMask; e.text = event.text; e.type = event.type; e.time = event.time; e.widget = chart; e.button = event.button; e.character = event.character; e.count = event.count; e.data = event.data; e.detail = event.detail; e.display = event.display; e.doit = event.doit; e.end = event.end; e.gc = event.gc; e.height = event.height; e.index = event.index; switch (event.type) { case SWT.Dispose: if (!source.isDisposed()) { source.removeListener(SWT.MouseMove, this); source.removeListener(SWT.MouseUp, this); source.removeListener(SWT.MouseDown, this); source.removeListener(SWT.MouseHover, this); source.removeListener(SWT.MouseWheel, this); source.removeListener(SWT.MouseDoubleClick, this); source.removeListener(SWT.KeyUp, this); source.removeListener(SWT.KeyDown, this); } break; case SWT.MouseMove: case SWT.MouseDown: case SWT.MouseUp: case SWT.MouseWheel: case SWT.MouseDoubleClick: Point p = translateToTarget(event.x, event.y); e.x = p.x; e.y = p.y; case SWT.KeyDown: case SWT.KeyUp: target.notifyListeners(e.type, e); } } /** * Translates the given point in the local canvas to the same point in the * target canvas. * @param x * @param y * @return */ private Point translateToTarget(int x, int y) { org.eclipse.draw2d.geometry.Point p = new org.eclipse.draw2d.geometry.Point(x, y); //int horiz = source.getViewport().getHorizontalRangeModel().getValue(); //int vert = source.getViewport().getHorizontalRangeModel().getValue(); source.getViewport().getContents().translateToRelative(p); target.getViewport().getContents().translateToAbsolute(p); //target.getViewport().translateFromParent(p); return new Point(p.x, p.y); } } /** * Creates a clone of the given sequence chart inside the given parent control. * @param parent the parent to create the clone within. * @param chartToClone the chart to clone */ public SequenceClone(Composite parent, UMLSequenceChart chartToClone) { this.chart = chartToClone; page = new Composite(parent, SWT.NONE); page.setBackground(chartToClone.getBackground()); GridLayout layout = new GridLayout(); layout.horizontalSpacing=0; layout.verticalSpacing=0; layout.numColumns=1; layout.marginHeight=0; layout.marginWidth=0; page.setLayout(layout); FigureCanvas chartSequenceCanvas = (FigureCanvas) chart.getSequenceControl(); FigureCanvas chartObjectCanvas = (FigureCanvas) chart.getLifelineControl(); //add a canvas to clone the object area. localObjectCanvas = new FigureCanvas(page); GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); localObjectCanvas.setLayoutData(gd); gd.minimumHeight = MessageBasedSequenceVisuals.OBJECT_HEIGHT; gd.heightHint = MessageBasedSequenceVisuals.OBJECT_HEIGHT; localObjectCanvas.setLayoutData(gd); localObjectCanvas.setViewport(new FreeformViewport()); FreeformLayer objectContents = new FreeformLayer(); objectContents.setLayoutManager(new FreeformLayout()); localObjectCanvas.getViewport().setContents(objectContents); CloneFigure objectClone = new CloneFigure(chartObjectCanvas.getViewport().getContents()); objectContents.add(objectClone); localObjectCanvas.setScrollBarVisibility(FigureCanvas.NEVER); //add hooks localObjectCanvas.getViewport().setContentsTracksHeight(true); localObjectCanvas.getViewport().setContentsTracksHeight(true); UpdateHook objectHook = new UpdateHook(chartObjectCanvas, localObjectCanvas, objectClone); objectHook.figureMoved(chartObjectCanvas.getViewport().getContents()); new EventForwarder(localObjectCanvas, chartObjectCanvas); localSequenceCanvas = new FigureCanvas(page); localSequenceCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); localSequenceCanvas.setViewport(new FreeformViewport()); FreeformLayer localContents = new FreeformLayer(); localSequenceCanvas.getViewport().setContents(localContents); localSequenceCanvas.setHorizontalScrollBarVisibility(FigureCanvas.AUTOMATIC); localSequenceCanvas.setVerticalScrollBarVisibility(FigureCanvas.NEVER); localContents.setLayoutManager(new FreeformLayout()); IFigure sequenceContents = chartSequenceCanvas.getViewport().getContents(); CloneFigure cloneFigure = new CloneFigure(sequenceContents); localContents.add(cloneFigure); //add hooks localSequenceCanvas.getViewport().setContentsTracksHeight(true); localSequenceCanvas.getViewport().setContentsTracksWidth(true); UpdateHook sequenceUpdateHook = new UpdateHook(chartSequenceCanvas, localSequenceCanvas, cloneFigure); sequenceUpdateHook.figureMoved(chartSequenceCanvas.getViewport().getContents()); new EventForwarder(localSequenceCanvas, chartSequenceCanvas); hookScrolling(); page.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { unhookScrolling(); } }); } /** * */ protected void unhookScrolling() { RangeModel mySeqHorizontalModel = localSequenceCanvas.getViewport().getHorizontalRangeModel(); RangeModel theirVerticalModel = ((FigureCanvas) chart .getSequenceControl()).getViewport().getVerticalRangeModel(); RangeModel theirHorizontalModel = ((FigureCanvas) chart .getSequenceControl()).getViewport().getHorizontalRangeModel(); mySeqHorizontalModel.removePropertyChangeListener(lifeLineScroller); mySeqHorizontalModel.removePropertyChangeListener(targetSyncScroller); theirVerticalModel.removePropertyChangeListener(verticalScroller); theirHorizontalModel.removePropertyChangeListener(sourceSyncScroller); } /** * */ private void hookScrolling() { final RangeModel mySeqHorizontalModel = localSequenceCanvas.getViewport().getHorizontalRangeModel(); final RangeModel mySeqVerticalModel = localSequenceCanvas.getViewport().getVerticalRangeModel(); final RangeModel myObjHorizontalModel = localObjectCanvas.getViewport().getHorizontalRangeModel(); //first, hook the whole cloned figure to make sure that the object canvas and //sequence canvas are in sync. lifeLineScroller = new PropertyChangeListener(){ public void propertyChange(PropertyChangeEvent evt) { if (myObjHorizontalModel.getValue() != mySeqHorizontalModel.getValue()) { myObjHorizontalModel.setValue(mySeqHorizontalModel.getValue()); } } }; mySeqHorizontalModel.addPropertyChangeListener(lifeLineScroller); final RangeModel theirVerticalModel = ((FigureCanvas) chart .getSequenceControl()).getViewport().getVerticalRangeModel(); final RangeModel theirHorizontalModel = ((FigureCanvas) chart .getSequenceControl()).getViewport().getHorizontalRangeModel(); verticalScroller = new PropertyChangeListener(){ public void propertyChange(PropertyChangeEvent evt) { int myv = mySeqVerticalModel.getValue(); int theirv = theirVerticalModel.getValue(); if (myv != theirv) { mySeqVerticalModel.setValue(theirv); } } }; theirVerticalModel.addPropertyChangeListener(verticalScroller); targetSyncScroller = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { int myv = mySeqHorizontalModel.getValue(); int myex = mySeqHorizontalModel.getExtent(); int theirv = theirHorizontalModel.getValue(); // int theirex = ((FigureCanvas) chart // .getSequenceControl()).getViewport().getSize().width; //int theirex = theirHorizontalModel.getExtent(); int newv = theirv-myex; if (theirv < myv+myex && newv < myv) { if (newv < 0) { newv= 0; } mySeqHorizontalModel.setValue(newv); } } }; sourceSyncScroller = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { int myv = mySeqHorizontalModel.getValue(); //int mymax = mySeqHorizontalModel.getMaximum(); int theirv = theirHorizontalModel.getValue(); // int theirex = ((FigureCanvas) chart // .getSequenceControl()).getViewport().getSize().width; int theirmax = theirHorizontalModel.getMaximum(); int myex = mySeqHorizontalModel.getExtent(); int newv = myv + myex; if (theirv < newv) { if (myv == 0) { newv = theirv; } else if (newv > theirmax) { newv = theirmax; } theirHorizontalModel.setValue(newv); } } }; theirHorizontalModel.addPropertyChangeListener(targetSyncScroller); mySeqHorizontalModel.addPropertyChangeListener(sourceSyncScroller); } /** * */ public Control getControl() { return page; } public void dispose() { page.dispose(); } /** * @return */ public boolean isDisposed() { return page == null || page.isDisposed(); } /** * @param color */ public void setBackground(Color color) { page.setBackground(color); localObjectCanvas.setBackground(color); localSequenceCanvas.setBackground(color); } }