/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2006, by :
* Corporate:
* EADS Astrium SAS
* Individual:
* Claude Cazenave
*
*
* $$Id$$
*
*/
package jsynoptic.builtin;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.swing.event.UndoableEditEvent;
import javax.swing.undo.CompoundEdit;
import jsynoptic.builtin.Plot.PlotCompoundEdit;
import simtools.diagram.DiagramComponent;
import simtools.diagram.DiagramParameters;
import simtools.diagram.DiagramSelection;
import simtools.diagram.DiagramSelectionListener;
/**
* This class performs the additional drawing required to perform some zooms
* on one or several selected plots
*
* Zoom box
* - Its location and size are defined thanks to mouse events received through the ContextualDrawing
* interface.When zoom defintion is over, on button 2 release, the rectangle
* coordinates are used to compute the zoom aplied to the selected plots.
* If there is one plot selected then the zoom concerns all the axes
* (even the secondary axes if thy exist). If there is more than a plot
* selected, the zoom is restricted to the axis with intersection with the
* zoom rectangle. In that case only one axis is changed but it is done
* on all the selected plots.
* - A red dashed rectangle is drawn.
*
* Zoom wheel
* - Its location and direction of rotation are defined thanks to mouse events received through the ContextualDrawing
* interface. Zoom wheel is performed on the selected plot that contains mouse position. Zoom whell
* concerns all the axes (even the secondary axes if thy exist).
*
* Translation
* * - The translation vector is defined thanks to mouse events received through the ContextualDrawing
* interface. The translation concerns all the axes and all the selected plots.
*
*/
public class PlotZoom implements DiagramComponent.ContextualDrawing, DiagramSelectionListener{
/** Kind of zoom */
protected int kindOfZoom;
protected final static int ZOOM_BOX = 0;
protected final static int ZOOM_WHEEL = 1;
protected final static int TRANSLATION = 2;
/** Zoom coordinates */
protected int x1,y1,x2,y2;
/** the plot on which the zoom definition starts */
protected Plot plot1;
/** on TRANSLATION: Zoom wheel factor */
protected final static double zoomWheelFactor = 1.25;
/** on ZOOM_BOX, current selection on which the zoom will aply */
final DiagramSelection selection;
/** on ZOOM_BOX, the zoom area line color */
protected static Color zoomColor=Color.red;
/** on ZOOM_BOX, a stroke used to display the zoom rectangle */
protected static BasicStroke dashStroke;
static{
float[] df={2.f,2.f};
dashStroke=new BasicStroke(0.1f,
BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER,
1.f, df,0.f);
}
/** on ZOOM_BOX, the zoom area shape */
protected Rectangle zoomArea;
/** on ZOOM_BOX, is true when the 2 pairs of coordinates are valid */
boolean twoPoints;
/** on TRANSLATION: During draging operation, , several translation are performed, but a unique event is stored into undo operation stack.*/
protected boolean firstTranslation;
/**
* Creates a new PlotZoom for the current selection
* @param sel the selection to zoom
*/
public PlotZoom(DiagramSelection sel){
selection=sel;
zoomArea=new Rectangle();
twoPoints=false;
plot1=null;
firstTranslation=false;
}
/* (non-Javadoc)
* @see simtools.diagram.DiagramComponent.ContextualDrawing#consumeKeyEvent(java.awt.event.KeyEvent)
*/
public boolean consumeKeyEvent(KeyEvent e){
if ((e.getModifiers() & KeyEvent.CTRL_MASK)== KeyEvent.CTRL_MASK){
if (e.getKeyCode() == KeyEvent.VK_GREATER) {
wheel(0, 0, true);
return true;
} else if (e.getKeyCode() == KeyEvent.VK_LESS) {
wheel(0, 0, false);
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see simtools.diagram.DiagramComponent.ContextualDrawing#consumeMouseEvent(java.awt.event.MouseEvent)
*/
public boolean consumeMouseEvent(MouseEvent e) {
switch (e.getID()) {
case MouseEvent.MOUSE_PRESSED:
if(DiagramComponent.isMouseButton2(e)){
if(selection.getElementContainer().getComponent().getContextualDrawing()==this){
if ((e.getModifiers()&MouseEvent.CTRL_MASK)==MouseEvent.CTRL_MASK){
kindOfZoom = PlotZoom.TRANSLATION;
}else{
kindOfZoom = PlotZoom.ZOOM_BOX;
}
press(e.getX(),e.getY());
}
else{
abort();
}
return true;
}
return false;
case MouseEvent.MOUSE_RELEASED:
if(DiagramComponent.isMouseButton2(e) && selection.getElementContainer().getComponent().getContextualDrawing()==this){
release(e.getX(),e.getY());
return true;
}
return false;
case MouseEvent.MOUSE_DRAGGED:
if(DiagramComponent.isMouseButton2(e) && selection.getElementContainer().getComponent().getContextualDrawing()==this){
drag(e.getX(),e.getY());
return true;
}
return false;
case MouseEvent.MOUSE_WHEEL:
if(selection.getElementContainer().getComponent().getContextualDrawing()==this){
if (e instanceof MouseWheelEvent) {
kindOfZoom = PlotZoom.ZOOM_WHEEL;
wheel(e.getX(),e.getY(), (((MouseWheelEvent)e).getWheelRotation()<0));
return true;
}
return false;
}
default:
return false;
}
}
/**
* make a zoom wheel
* @param x
* @param y
* @param zoomIn
*/
protected void wheel(int x,int y, boolean zoomIn){
DiagramParameters param = selection.getElementContainer().getComponent().getParameters();
x1 = (int) ((double) x / param.scale) - param.xmargin;
y1 = (int) ((double) y / param.scale) - param.ymargin;
plot1=getSelectedPlot(x1,y1);
if(plot1==null){
abort();
return;
}
CompoundEdit ce=null;
// simple zoom, all the axes are taken into account
HashMap plot1OldValues=plot1.getAxesProperties(null, -1);
plot1.zoomWheel(x1, y1, PlotZoom.zoomWheelFactor, zoomIn);
ce=plot1.new PlotCompoundEdit(plot1OldValues);
plot1.repaintDiagram(plot1.getBounds());
// store undoable event
selection.getElementContainer().getComponent().fireUndoableEditUpdate(new UndoableEditEvent(selection.getElementContainer().getComponent(), ce));
selection.getElementContainer().getComponent().repaint();
// end of drawing
plot1=null;
twoPoints = false;
selection.getElementContainer().getComponent().setContextualDrawing(null); // end
}
/**
* Abort the zoom
*/
public void abort(){
plot1=null;
twoPoints = false;
// end of drawing
selection.getElementContainer().getComponent().setContextualDrawing(null); // end
}
/**
* Start the zoom at the mouse pressed location
* @param x mouse location X coordinate
* @param y mouse location Y coordinate
*/
public void press(int x, int y){
DiagramParameters param = selection.getElementContainer().getComponent()
.getParameters();
x1 = (int) ((double) x / param.scale) - param.xmargin;
y1 = (int) ((double) y / param.scale) - param.ymargin;
plot1=getSelectedPlot(x1,y1);
if (kindOfZoom== PlotZoom.ZOOM_BOX)
twoPoints = false;
if (kindOfZoom== PlotZoom.TRANSLATION)
firstTranslation=true;
}
/**
* Extends the zoom area according to dragged mouse location
* @param x mouse location X coordinate
* @param y mouse location Y coordinate
*/
public void drag(int x,int y){
displayCrossLocation(x,y);
DiagramParameters param = selection.getElementContainer().getComponent()
.getParameters();
x2 = (int) ((double) x / param.scale) - param.xmargin;
y2 = (int) ((double) y / param.scale) - param.ymargin;
if (kindOfZoom==PlotZoom.ZOOM_BOX){
selection.getElementContainer().getComponent().repaint();
twoPoints = true;
}
else if (kindOfZoom==PlotZoom.TRANSLATION){
translate();
}
}
/**
* When the mouse is released, do the zoom according to zoom
* area definition
* @param x mouse location X coordinate
* @param y mouse location Y coordinate
*/
public void release(int x, int y){
if (kindOfZoom==PlotZoom.ZOOM_BOX){
DiagramParameters param = selection.getElementContainer().getComponent()
.getParameters();
x2 = (int) ((double) x / param.scale) - param.xmargin;
y2 = (int) ((double) y / param.scale) - param.ymargin;
// 1) look for the first shape with
// an overlap with zoom rectangle
Plot plot2 = getSelectedPlot(x2,y2);
if(plot1==null && plot2!=null){
plot1=plot2;
}
if(plot1==null){
abort();
return;
}
// 1 check if a collective zoom on primary or secondary
// axes has to be performed
// The criterion is same variable name (but not same data source)
HashSet collectivePlots=new HashSet();
for (int i = 0; i < selection.getShapeCount(); i++) {
Shape s = selection.getShape(i);
if (s instanceof Plot && s != plot1) {
Plot plot=(Plot)s;
collectivePlots.add(plot);
}
}
// do the zoom
CompoundEdit ce=null;
if(collectivePlots.isEmpty()){
// a simple zoom, all the exes are taken into account
HashMap plot1OldValues=plot1.getAxesProperties(null, -1);
plot1.zoom(x1, x2, y1, y2, false);
ce=plot1.new PlotCompoundEdit(plot1OldValues);
plot1.repaintDiagram(plot1.getBounds());
}
else{
// a collective zoom
ce = new CompoundEdit();
HashMap plot1OldValues=plot1.getAxesProperties(null, Plot.PX);
if(plot1.secondaryX!=null){
plot1.getAxesProperties(plot1OldValues, Plot.SX);
}
// X axes zoom only
plot1.zoom(x1, x2, 0., 0., true);
PlotCompoundEdit ce1=plot1.new PlotCompoundEdit(plot1OldValues);
plot1.repaintDiagram(plot1.getBounds());
ce.addEdit(ce1);
// apply plot1 X axes properties to the other compliants plots
Iterator it=collectivePlots.iterator();
while(it.hasNext()){
Plot plot=(Plot)it.next();
HashMap oldValues=plot.getAxesProperties(null, Plot.PX);
if(plot.secondaryX!=null){
plot.getAxesProperties(oldValues, Plot.SX);
}
Iterator vit=oldValues.keySet().iterator();
while(vit.hasNext()){
String k=(String)vit.next();
plot.setPropertyValue(k,plot1.getPropertyValue(k));
}
PlotCompoundEdit ce2=plot.new PlotCompoundEdit(oldValues);
plot.repaintDiagram(plot.getBounds());
ce.addEdit(ce2);
}
ce.end();
}
// store undoable event
selection.getElementContainer().getComponent().fireUndoableEditUpdate(new UndoableEditEvent(selection.getElementContainer().getComponent(), ce));
selection.getElementContainer().getComponent().repaint();
}
// end of drawing
plot1=null;
twoPoints = false;
selection.getElementContainer().getComponent().setContextualDrawing(null); // end
}
/**
* When the mouse is released, do the translation according to the vector (x1,y1) (x2,y2)
* @param x mouse location X coordinate
* @param y mouse location Y coordinate
*/
public void translate(){
// look for the first shape with an overlap with zoom rectangle
Plot plot2 = getSelectedPlot(x2,y2);
if(plot1==null && plot2!=null){
plot1=plot2;
}
if(plot1==null){
abort();
return;
}
// collective translation
HashSet collectivePlots=new HashSet();
for (int i = 0; i < selection.getShapeCount(); i++) {
Shape s = selection.getShape(i);
if (s instanceof Plot && s != plot1) {
collectivePlots.add(s);
}
}
// Make the translation
CompoundEdit ce=null;
if(collectivePlots.isEmpty()){
// a simple translation, all the exes are taken into account
if (firstTranslation){
HashMap plot1OldValues=plot1.getAxesProperties(null, -1);
ce=plot1.new PlotCompoundEdit(plot1OldValues);
}
plot1.translate(x1, x2, y1, y2);
plot1.repaintDiagram(plot1.getBounds());
}
else{
// collective translation
ce = new CompoundEdit();
if (firstTranslation){
HashMap plot1OldValues=plot1.getAxesProperties(null, -1);
PlotCompoundEdit ce1=plot1.new PlotCompoundEdit(plot1OldValues);
ce.addEdit(ce1);
}
plot1.translate(x1, x2, y1, y2);
plot1.repaintDiagram(plot1.getBounds());
// apply plot1 X axes properties to the other compliants plots
Iterator it=collectivePlots.iterator();
while(it.hasNext()){
Plot plot=(Plot)it.next();
if (firstTranslation){
HashMap oldValues=plot.getAxesProperties(null,-1);
PlotCompoundEdit ce2=plot.new PlotCompoundEdit(oldValues);
ce.addEdit(ce2);
}
plot.translate(x1, x2, y1, y2);
plot.repaintDiagram(plot.getBounds());
}
ce.end();
}
// store undoable event
if (firstTranslation){
selection.getElementContainer().getComponent().fireUndoableEditUpdate(new UndoableEditEvent(selection.getElementContainer().getComponent(), ce));
firstTranslation = false;
}
selection.getElementContainer().getComponent().repaint();
// end of drawing
plot1=null;
// For next translation
x1=x2;
y1=y2;
}
/**
* Get the first plot part of selection which contains this point
* @param x X coordinate location wrt diagram frame
* @param y Y coordinate location wrt diagram frame
* @return a plot
*/
protected Plot getSelectedPlot(double x, double y){
for (int i = 0; i < selection.getShapeCount(); i++) {
Shape s = selection.getShape(i);
if (s instanceof Plot
&& s.contains(x,y)) {
return (Plot)s;
}
}
return null;
}
/**
* Display the plot coordinates for this point
* @param x X coordinate location wrt diagram frame
* @param y Y coordinate location wrt diagram frame
*/
protected void displayCrossLocation(int x, int y){
DiagramParameters param = selection.getElementContainer().getComponent().getParameters();
double selX=(x/param.scale)-param.xmargin;
double selY=(y/param.scale)-param.ymargin;
Plot p=plot1;
if(p==null){
p=getSelectedPlot(selX,selY);
}
if(p!=null){
p.displayCursorLocation(selX,selY);
}
selection.getElementContainer().getComponent().setCursor(
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
/* (non-Javadoc)
* @see simtools.diagram.DiagramSelection.ContextualDrawing#draw(java.awt.Graphics2D, java.awt.Point)
*/
public void draw(Graphics2D g2, Point pMax) {
if (twoPoints && (kindOfZoom==PlotZoom.ZOOM_BOX)) {
Color oldColor = g2.getColor();
Stroke oldStroke = g2.getStroke();
g2.setColor(zoomColor);
g2.setStroke(dashStroke);
zoomArea.x = Math.min(x1, x2);
zoomArea.y = Math.min(y1, y2);
zoomArea.width = Math.abs(x2 - x1);
zoomArea.height = Math.abs(y2 - y1);
g2.draw(zoomArea);
// update drawing size
if (Math.max(x1, x2) > pMax.x) {
pMax.x = Math.max(x1, x2);
}
if (Math.max(y1, y2) > pMax.y) {
pMax.y = Math.max(y1, y2);
}
g2.setColor(oldColor);
g2.setStroke(oldStroke);
}
}
/* (non-Javadoc)
* @see simtools.diagram.DiagramSelectionListener#selectionChanged(simtools.diagram.DiagramSelection)
*/
public void selectionChanged(DiagramSelection sel) {
for (int i = 0; i < selection.getShapeCount(); i++) {
Shape s = selection.getShape(i);
if (s instanceof Plot){
return; // there is at least on plot selected
}
}
// no more plot in the selection => end of mode
selection.getElementContainer().getComponent().setContextualDrawing(null); // end
}
}