/*******************************************************************************
* Copyright (c) 2010 Oak Ridge National Laboratory.
* 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
******************************************************************************/
package org.eclipse.nebula.visualization.xygraph.figures;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.internal.xygraph.undo.SaveStateCommand;
import org.eclipse.nebula.visualization.internal.xygraph.undo.ZoomCommand;
import org.eclipse.nebula.visualization.xygraph.linearscale.Range;
import org.eclipse.nebula.visualization.xygraph.util.SWTConstants;
import org.eclipse.nebula.visualization.xygraph.util.XYGraphMediaFactory;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
/**
* The plot area figure.
*
* @author Xihui Chen
* @author Kay Kasemir - Axis zoom/pan tweaks
* @author Laurent PHILIPPE - Add property change event for annotation
*/
public class PlotArea extends Figure {
// Added by Laurent PHILIPPE
private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
// System.out.println("**** PlotArea.addPropertyChangeListener() ****");
changeSupport.addPropertyChangeListener(listener);
}
@Override
public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
// System.out.println("**** PlotArea.addPropertyChangeListener() ****");
changeSupport.addPropertyChangeListener(property, listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(String property, PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(property, listener);
}
public static final String BACKGROUND_COLOR = "background_color"; //$NON-NLS-1$
/**
* Value of left click mouse button event which is equal to 1
*/
public static final int BUTTON1 = 1;
/**
* Value of middle click / mousewheel button event which is equal to 2
*/
public static final int BUTTON2 = 2;
final private IXYGraph xyGraph;
final private List<Trace> traceList = new ArrayList<Trace>();
final private List<Grid> gridList = new ArrayList<Grid>();
final private List<Annotation> annotationList = new ArrayList<Annotation>();
final private Cursor grabbing;
private boolean showBorder;
private ZoomType zoomType;
private Point start;
private Point dynamicStart;
private Point end;
private boolean armed;
private Color revertBackColor;
public PlotArea(final IXYGraph xyGraph) {
this((XYGraph) xyGraph);
}
/**
* Use {@link #PlotArea(IXYGraph)} instead
*
* @param xyGraph
*/
@Deprecated
public PlotArea(final XYGraph xyGraph) {
this.xyGraph = xyGraph;
setBackgroundColor(XYGraphMediaFactory.getInstance().getColor(255, 255, 255));
setForegroundColor(XYGraphMediaFactory.getInstance().getColor(0, 0, 0));
setOpaque(true);
RGB backRGB = getBackgroundColor().getRGB();
revertBackColor = XYGraphMediaFactory.getInstance().getColor(255 - backRGB.red, 255 - backRGB.green,
255 - backRGB.blue);
PlotMouseListener zoomer = new PlotMouseListener();
addMouseListener(zoomer);
addMouseMotionListener(zoomer);
grabbing = XYGraphMediaFactory.getInstance().getCursor(XYGraphMediaFactory.CURSOR_GRABBING_PATH);
zoomType = ZoomType.NONE;
}
@Override
public void setBackgroundColor(final Color bg) {
// System.out.println("**** PlotArea.setBackgroundColor() ****");
RGB backRGB = bg.getRGB();
revertBackColor = XYGraphMediaFactory.getInstance().getColor(255 - backRGB.red, 255 - backRGB.green,
255 - backRGB.blue);
Color oldColor = getBackgroundColor();
super.setBackgroundColor(bg);
changeSupport.firePropertyChange(BACKGROUND_COLOR, oldColor, bg);
}
/**
* Add a trace to the plot area.
*
* @param trace
* the trace to be added.
*/
public void addTrace(final Trace trace) {
traceList.add(trace);
add(trace);
revalidate();
}
/**
* Remove a trace from the plot area.
*
* @param trace
* @return true if this plot area contained the specified trace
*/
public boolean removeTrace(final Trace trace) {
boolean result = traceList.remove(trace);
if (result) {
remove(trace);
revalidate();
}
return result;
}
/**
* Add a grid to the plot area.
*
* @param grid
* the grid to be added.
*/
public void addGrid(final Grid grid) {
gridList.add(grid);
add(grid);
revalidate();
}
/**
* Remove a grid from the plot area.
*
* @param grid
* the grid to be removed.
* @return true if this plot area contained the specified grid
*/
public boolean removeGrid(final Grid grid) {
final boolean result = gridList.remove(grid);
if (result) {
remove(grid);
revalidate();
}
return result;
}
/**
* Add an annotation to the plot area.
*
* @param annotation
* the annotation to be added.
*/
public void addAnnotation(final Annotation annotation) {
annotationList.add(annotation);
annotation.setxyGraph(xyGraph);
add(annotation);
revalidate();
// Laurent PHILIPPE send event
changeSupport.firePropertyChange("annotationList", null, annotation);
}
/**
* Remove a annotation from the plot area.
*
* @param annotation
* the annotation to be removed.
* @return true if this plot area contained the specified annotation
*/
public boolean removeAnnotation(final Annotation annotation) {
final boolean result = annotationList.remove(annotation);
if (!annotation.isFree())
annotation.getTrace().getDataProvider().removeDataProviderListener(annotation);
if (result) {
remove(annotation);
revalidate();
// Laurent PHILIPPE send event
changeSupport.firePropertyChange("annotationList", annotation, null);
}
return result;
}
@Override
protected void layout() {
final Rectangle clientArea = getClientArea();
for (Trace trace : traceList) {
if (trace != null && trace.isVisible())
// Shrink will make the trace has no intersection with axes,
// which will make it only repaints the trace area.
trace.setBounds(clientArea);// .getCopy().shrink(1, 1));
}
for (Grid grid : gridList) {
if (grid != null && grid.isVisible())
grid.setBounds(clientArea);
}
for (Annotation annotation : annotationList) {
if (annotation != null && annotation.isVisible())
annotation.setBounds(clientArea);// .getCopy().shrink(1, 1));
}
super.layout();
}
@Override
protected void paintClientArea(final Graphics graphics) {
super.paintClientArea(graphics);
if (showBorder) {
graphics.setLineWidth(2);
graphics.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y);
graphics.drawLine(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height);
}
// Show the start/end cursor or the 'rubberband' of a zoom operation?
if (armed && end != null && start != null) {
switch (zoomType) {
case RUBBERBAND_ZOOM:
case DYNAMIC_ZOOM:
case HORIZONTAL_ZOOM:
case VERTICAL_ZOOM:
graphics.setLineStyle(SWTConstants.LINE_DOT);
graphics.setLineWidth(1);
graphics.setForegroundColor(revertBackColor);
graphics.drawRectangle(start.x, start.y, end.x - start.x, end.y - start.y);
break;
default:
break;
}
}
}
/**
* @param showBorder
* the showBorder to set
*/
public void setShowBorder(final boolean showBorder) {
this.showBorder = showBorder;
repaint();
}
/**
* @return the showBorder
*/
public boolean isShowBorder() {
return showBorder;
}
/**
* @param zoomType
* the zoomType to set
*/
public void setZoomType(final ZoomType zoomType) {
this.zoomType = zoomType;
setCursor(zoomType.getCursor());
}
/**
* Zoom 'in' or 'out' by a fixed factor
*
* @param horizontally
* along x axes?
* @param vertically
* along y axes?
* @param mouseX
* absolute X location of the mouse cursor
* @param mouseY
* absolute Y location of the mouse cursor
* @param factor
* Zoom factor. Positive to zoom 'in', negative 'out'.
*/
public void zoomInOut(final boolean horizontally, final boolean vertically, final int mouseX, final int mouseY,
final double factor) {
if (horizontally)
for (Axis axis : xyGraph.getXAxisList()) {
final double center = axis.getPositionValue(mouseX, false);
axis.zoomInOut(center, factor);
}
if (vertically)
for (Axis axis : xyGraph.getYAxisList()) {
final double center = axis.getPositionValue(mouseY, false);
axis.zoomInOut(center, factor);
}
}
/**
* @return the traceList
*/
public List<Trace> getTraceList() {
return traceList;
}
/**
* @return the annotationList
*/
public List<Annotation> getAnnotationList() {
return annotationList;
}
/**
* Field used to remember the previous zoom type used
*/
private ZoomType previousZoomType = ZoomType.NONE;
/**
* Listener to mouse events, performs panning and some zooms Is very similar
* to the Axis.AxisMouseListener, but unclear how easy/useful it would be to
* base them on the same code.
*/
class PlotMouseListener extends MouseMotionListener.Stub implements MouseListener {
final private List<Range> xAxisStartRangeList = new ArrayList<Range>();
final private List<Range> yAxisStartRangeList = new ArrayList<Range>();
private SaveStateCommand command;
private boolean dynamicZoomMode = false;
@Override
public void mousePressed(final MouseEvent me) {
// Only react to 'main' mouse button, only react to 'real' zoom
if ((me.button != BUTTON1 || zoomType == ZoomType.NONE) && me.button != BUTTON2)
return;
// Remember last used zoomtype
previousZoomType = zoomType;
// if the mousewheel is pressed
if (me.button == BUTTON2) {
zoomType = ZoomType.PANNING;
}
armed = true;
dynamicZoomMode = false;
// get start position
switch (zoomType) {
case RUBBERBAND_ZOOM:
start = me.getLocation();
end = null;
break;
case DYNAMIC_ZOOM:
start = me.getLocation();
dynamicStart = me.getLocation(); // dynamicStart will save
// starting point, start
// variable
// will be changed according
// to zoomType
end = null;
break;
case HORIZONTAL_ZOOM:
start = new Point(me.getLocation().x, bounds.y);
end = null;
break;
case VERTICAL_ZOOM:
start = new Point(bounds.x, me.getLocation().y);
end = null;
break;
case PANNING:
setCursor(grabbing);
start = me.getLocation();
end = null;
xAxisStartRangeList.clear();
yAxisStartRangeList.clear();
for (Axis axis : xyGraph.getXAxisList())
xAxisStartRangeList.add(axis.getRange());
for (Axis axis : xyGraph.getYAxisList())
yAxisStartRangeList.add(axis.getRange());
break;
case ZOOM_IN:
case ZOOM_IN_HORIZONTALLY:
case ZOOM_IN_VERTICALLY:
case ZOOM_OUT:
case ZOOM_OUT_HORIZONTALLY:
case ZOOM_OUT_VERTICALLY:
start = me.getLocation();
end = new Point();
// Start timer that will zoom while mouse button is pressed
Display.getCurrent().timerExec(Axis.ZOOM_SPEED, new Runnable() {
@Override
public void run() {
if (!armed)
return;
performInOutZoom();
Display.getCurrent().timerExec(Axis.ZOOM_SPEED, this);
}
});
break;
default:
break;
}
// add command for undo operation
command = new ZoomCommand(zoomType.getDescription(), xyGraph.getXAxisList(), xyGraph.getYAxisList());
me.consume();
}
@Override
public void mouseDoubleClicked(final MouseEvent me) { /* Ignored */
}
@Override
public void mouseDragged(final MouseEvent me) {
if (!armed)
return;
if (dynamicZoomMode)
zoomType = ZoomType.DYNAMIC_ZOOM;
switch (zoomType) {
case DYNAMIC_ZOOM:
dynamicZoomMode = true;
if (Math.abs(dynamicStart.x - me.x) < 30) {
start = new Point(bounds.x, dynamicStart.y);
end = new Point(bounds.x + bounds.width, me.getLocation().y);
setZoomType(ZoomType.VERTICAL_ZOOM);
} else if (Math.abs(dynamicStart.y - me.y) < 30) {
start = new Point(dynamicStart.x, bounds.y);
end = new Point(me.getLocation().x, bounds.y + bounds.height);
setZoomType(ZoomType.HORIZONTAL_ZOOM);
} else {
start = dynamicStart;
end = me.getLocation();
setZoomType(ZoomType.RUBBERBAND_ZOOM);
}
break;
case RUBBERBAND_ZOOM:
end = me.getLocation();
break;
case HORIZONTAL_ZOOM:
end = new Point(me.getLocation().x, bounds.y + bounds.height);
break;
case VERTICAL_ZOOM:
end = new Point(bounds.x + bounds.width, me.getLocation().y);
break;
case PANNING:
end = me.getLocation();
pan();
break;
default:
break;
}
PlotArea.this.repaint();
}
@Override
public void mouseExited(final MouseEvent me) {
// Treat like releasing the button to stop zoomIn/Out timer
switch (zoomType) {
case ZOOM_IN:
case ZOOM_IN_HORIZONTALLY:
case ZOOM_IN_VERTICALLY:
case ZOOM_OUT:
case ZOOM_OUT_HORIZONTALLY:
case ZOOM_OUT_VERTICALLY:
mouseReleased(me);
default:
}
}
@Override
public void mouseReleased(final MouseEvent me) {
if (!armed)
return;
armed = false;
if (zoomType == ZoomType.PANNING)
setCursor(zoomType.getCursor());
if (end == null || start == null)
return;
// If we are in dynamicZoom mode we will zoom like this, for other
// zooms is everything like before
if (dynamicZoomMode) {
if (zoomType != ZoomType.VERTICAL_ZOOM)
for (Axis axis : xyGraph.getXAxisList()) {
final double t1 = axis.getPositionValue(start.x, false);
final double t2 = axis.getPositionValue(end.x, false);
axis.setRange(t1, t2, true);
}
if (zoomType != ZoomType.HORIZONTAL_ZOOM)
for (Axis axis : xyGraph.getYAxisList()) {
final double t1 = axis.getPositionValue(start.y, false);
final double t2 = axis.getPositionValue(end.y, false);
axis.setRange(t1, t2, true);
}
setZoomType(ZoomType.DYNAMIC_ZOOM);
} else
switch (zoomType) {
case RUBBERBAND_ZOOM:
for (Axis axis : xyGraph.getXAxisList()) {
final double t1 = axis.getPositionValue(start.x, false);
final double t2 = axis.getPositionValue(end.x, false);
axis.setRange(t1, t2, true);
}
for (Axis axis : xyGraph.getYAxisList()) {
final double t1 = axis.getPositionValue(start.y, false);
final double t2 = axis.getPositionValue(end.y, false);
axis.setRange(t1, t2, true);
}
break;
case HORIZONTAL_ZOOM:
for (Axis axis : xyGraph.getXAxisList()) {
final double t1 = axis.getPositionValue(start.x, false);
final double t2 = axis.getPositionValue(end.x, false);
axis.setRange(t1, t2, true);
}
break;
case VERTICAL_ZOOM:
for (Axis axis : xyGraph.getYAxisList()) {
final double t1 = axis.getPositionValue(start.y, false);
final double t2 = axis.getPositionValue(end.y, false);
axis.setRange(t1, t2, true);
}
break;
case PANNING:
pan();
break;
case ZOOM_IN:
case ZOOM_IN_HORIZONTALLY:
case ZOOM_IN_VERTICALLY:
case ZOOM_OUT:
case ZOOM_OUT_HORIZONTALLY:
case ZOOM_OUT_VERTICALLY:
performInOutZoom();
break;
default:
break;
}
// mousewheel is pressed and last zoom type was not panning, we set
// the zoomtype to the previous state.
if (me.button == BUTTON2 && previousZoomType != ZoomType.PANNING) {
zoomType = previousZoomType;
setCursor(previousZoomType.getCursor());
}
if (zoomType != ZoomType.NONE && command != null) {
command.saveState();
xyGraph.getOperationsManager().addCommand(command);
command = null;
}
start = null;
end = null;
PlotArea.this.repaint();
}
/** Pan axis according to start/end from mouse listener */
private void pan() {
List<Axis> axes = xyGraph.getXAxisList();
for (int i = 0; i < axes.size(); ++i) {
final Axis axis = axes.get(i);
axis.pan(xAxisStartRangeList.get(i), axis.getPositionValue(start.x, false),
axis.getPositionValue(end.x, false));
}
axes = xyGraph.getYAxisList();
for (int i = 0; i < axes.size(); ++i) {
final Axis axis = axes.get(i);
axis.pan(yAxisStartRangeList.get(i), axis.getPositionValue(start.y, false),
axis.getPositionValue(end.y, false));
}
}
/** Perform the in or out zoom according to zoomType */
private void performInOutZoom() {
switch (zoomType) {
case ZOOM_IN:
zoomInOut(true, true, start.x, start.y, Axis.ZOOM_RATIO);
break;
case ZOOM_IN_HORIZONTALLY:
zoomInOut(true, false, start.x, start.y, Axis.ZOOM_RATIO);
break;
case ZOOM_IN_VERTICALLY:
zoomInOut(false, true, start.x, start.y, Axis.ZOOM_RATIO);
break;
case ZOOM_OUT:
zoomInOut(true, true, start.x, start.y, -Axis.ZOOM_RATIO);
break;
case ZOOM_OUT_HORIZONTALLY:
zoomInOut(true, false, start.x, start.y, -Axis.ZOOM_RATIO);
break;
case ZOOM_OUT_VERTICALLY:
zoomInOut(false, true, start.x, start.y, -Axis.ZOOM_RATIO);
break;
default: // NOP
}
}
}
}