/******************************************************************************* * 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.csstudio.swt.xygraph.figures; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.csstudio.swt.xygraph.linearscale.Range; import org.csstudio.swt.xygraph.undo.SaveStateCommand; import org.csstudio.swt.xygraph.undo.ZoomCommand; import org.csstudio.swt.xygraph.undo.ZoomType; import org.csstudio.swt.xygraph.util.SWTConstants; import org.csstudio.swt.xygraph.util.XYGraphMediaFactory; import org.csstudio.swt.xygraph.util.XYGraphMediaFactory.CURSOR_TYPE; import org.eclipse.draw2d.ColorConstants; 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.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 */ public class PlotArea extends Figure { public static final String BACKGROUND_COLOR = "background_color"; //$NON-NLS-1$ final protected XYGraph 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; protected ZoomType zoomType; private Point start; private Point end; private boolean armed; private Color revertBackColor; 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.getCursor(CURSOR_TYPE.GRABBING); zoomType = ZoomType.NONE; } @Override public void setBackgroundColor(final Color bg) { RGB backRGB = bg.getRGB(); revertBackColor = XYGraphMediaFactory.getInstance().getColor(255- backRGB.red, 255 - backRGB.green, 255 - backRGB.blue); Color oldColor = getBackgroundColor(); super.setBackgroundColor(bg); 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(); } /**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()) if (annotation.getTrace()!=null && annotation.getTrace().getDataProvider()!=null) { annotation.getTrace().getDataProvider().removeDataProviderListener(annotation); } if(result){ remove(annotation); revalidate(); } 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 HORIZONTAL_ZOOM: case VERTICAL_ZOOM: // Instead of XOR which does not always work, // we draw two slighly staggered boxes, one in white, // one in black. graphics.setLineStyle(SWTConstants.LINE_DOT); graphics.setLineWidth(1); graphics.setLineDash(new int[]{1,1}); graphics.setForegroundColor(ColorConstants.black); graphics.drawRectangle(start.x, start.y, end.x - start.x, end.y - start.y); graphics.setForegroundColor(ColorConstants.white); graphics.drawRectangle(start.x+1, start.y+1, end.x - start.x, end.y - start.y); 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 factor Zoom factor. Positive to zoom 'in', negative 'out'. */ private void zoomInOut(final boolean horizontally, final boolean vertically, final double factor) { if (horizontally) for(Axis axis : xyGraph.getXAxisList()) { final double center = axis.getPositionValue(start.x, false); axis.zoomInOut(center, factor); } if (vertically) for(Axis axis : xyGraph.getYAxisList()) { final double center = axis.getPositionValue(start.y, false); axis.zoomInOut(center, factor); } } /** * @return the traceList */ public List<Trace> getTraceList() { return traceList; } /** * @return the annotationList */ public List<Annotation> getAnnotationList() { return annotationList; } /** * Alternative listener which will be notified * in addition to processing the internal tools. */ private Collection<MouseListener> auxilliaryClickListeners; /** * Alternative listener which will be notified * in addition to processing the internal tools. */ private Collection<MouseMotionListener> auxilliaryMotionListeners; /** 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 implements MouseListener, MouseMotionListener { final private List<Range> xAxisStartRangeList = new ArrayList<Range>(); final private List<Range> yAxisStartRangeList = new ArrayList<Range>(); private SaveStateCommand command; @Override public void mousePressed(final MouseEvent me) { fireMousePressed(me); // Only react to 'main' mouse button, only react to 'real' zoom if (me.button != 1 || zoomType == ZoomType.NONE) return; armed = true; // get start position switch (zoomType) { case RUBBERBAND_ZOOM: start = me.getLocation(); 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) { fireMouseDoubleClicked(me); } @Override public void mouseDragged(final MouseEvent me) { fireMouseDragged(me); if(!armed) return; switch (zoomType) { 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) { fireMouseExited(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) { fireMouseReleased(me); if (! armed) return; armed = false; if(zoomType == ZoomType.PANNING) setCursor(zoomType.getCursor()); if(end == null || start == null) return; 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); Range range = getNewRange(axis, t1, t2); axis.setRange(range); } for(Axis axis : xyGraph.getYAxisList()) { final double t1 = axis.getPositionValue(start.y, false); final double t2 = axis.getPositionValue(end.y, false); axis.setRange(getNewRange(axis, t1, t2)); } 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(getNewRange(axis, t1, t2)); } 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(getNewRange(axis, t1, t2)); } 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; } if (zoomType != ZoomType.NONE && command != null) { command.saveState(); xyGraph.getOperationsManager().addCommand(command); command = null; } start = null; end = null; PlotArea.this.repaint(); } /**Get the new Range which will honor its original range direction. * @param axis the axis whose range should be honored * @param t1 start * @param t2 end * @return the new range */ private Range getNewRange(Axis axis, final double t1, final double t2) { Range range; if(axis.getRange().isMinBigger()){ range = new Range(t1>t2? t1:t2, t1>t2?t2:t1); }else range = new Range(t1>t2? t2:t1, t1>t2?t1:t2); return range; } /** 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, Axis.ZOOM_RATIO); break; case ZOOM_IN_HORIZONTALLY: zoomInOut(true, false, Axis.ZOOM_RATIO); break; case ZOOM_IN_VERTICALLY: zoomInOut(false, true, Axis.ZOOM_RATIO); break; case ZOOM_OUT: zoomInOut(true, true, -Axis.ZOOM_RATIO); break; case ZOOM_OUT_HORIZONTALLY:zoomInOut(true, false,-Axis.ZOOM_RATIO); break; case ZOOM_OUT_VERTICALLY: zoomInOut(false, true,-Axis.ZOOM_RATIO); break; default: // NOP } } @Override public void mouseEntered(MouseEvent me) { fireMouseEntered(me); } @Override public void mouseHover(MouseEvent me) { fireMouseHover(me); } @Override public void mouseMoved(MouseEvent me) { fireMouseMoved(me); } } public void addAuxilliaryMotionListener(MouseMotionListener auxilliaryMotionListener) { if (this.auxilliaryMotionListeners==null) auxilliaryMotionListeners = new HashSet<MouseMotionListener>(); auxilliaryMotionListeners.add(auxilliaryMotionListener); } public void removeAuxilliaryClickListener(MouseListener auxilliaryClickListener) { if (this.auxilliaryClickListeners==null) return; auxilliaryClickListeners.remove(auxilliaryClickListener); } public void removeAuxilliaryMotionListener(MouseMotionListener auxilliaryMotionListener) { if (this.auxilliaryMotionListeners==null) return; auxilliaryMotionListeners.remove(auxilliaryMotionListener); } public void addAuxilliaryClickListener(MouseListener auxilliaryClickListener) { if (this.auxilliaryClickListeners==null) auxilliaryClickListeners = new HashSet<MouseListener>(); auxilliaryClickListeners.add(auxilliaryClickListener); } public void fireMouseReleased(MouseEvent me) { if (this.auxilliaryClickListeners==null) return; for (MouseListener l : auxilliaryClickListeners) l.mouseReleased(me); } public void fireMouseDoubleClicked(MouseEvent me) { if (this.auxilliaryClickListeners==null) return; for (MouseListener l : auxilliaryClickListeners) l.mouseDoubleClicked(me); } public void fireMousePressed(MouseEvent me) { if (this.auxilliaryClickListeners==null) return; for (MouseListener l : auxilliaryClickListeners) l.mousePressed(me); } public void fireMouseMoved(MouseEvent me) { if (this.auxilliaryMotionListeners==null) return; for (MouseMotionListener l : auxilliaryMotionListeners) l.mouseMoved(me); } public void fireMouseHover(MouseEvent me) { if (this.auxilliaryMotionListeners==null) return; for (MouseMotionListener l : auxilliaryMotionListeners) l.mouseHover(me); } public void fireMouseEntered(MouseEvent me) { if (this.auxilliaryMotionListeners==null) return; for (MouseMotionListener l : auxilliaryMotionListeners) l.mouseEntered(me); } public void fireMouseExited(MouseEvent me) { if (this.auxilliaryMotionListeners==null) return; for (MouseMotionListener l : auxilliaryMotionListeners) l.mouseExited(me); } public void fireMouseDragged(MouseEvent me) { if (this.auxilliaryMotionListeners==null) return; for (MouseMotionListener l : auxilliaryMotionListeners) l.mouseDragged(me); } }