/*
* @(#)MapTool.java 3.0 99/09/06 Jonathan Callahan
* Moved user_X and user_Y from MapCanvas to the MapTool.
* @(#)MapTool.java 2.3 97/10/13 Jonathan Callahan
* @(#)MapTool.java 1.0 96/09/26 Jonathan Callahan
*
*
* This software was developed by the Thermal Modeling and Analysis
* Project(TMAP) of the National Oceanographic and Atmospheric
* Administration's (NOAA) Pacific Marine Environmental Lab(PMEL),
* hereafter referred to as NOAA/PMEL/TMAP.
*
* Access and use of this software shall impose the following
* obligations and understandings on the user. The user is granted the
* right, without any fee or cost, to use, copy, modify, alter, enhance
* and distribute this software, and any derivative works thereof, and
* its supporting documentation for any purpose whatsoever, provided
* that this entire notice appears in all copies of the software,
* derivative works and supporting documentation. Further, the user
* agrees to credit NOAA/PMEL/TMAP in any publications that result from
* the use of this software or in any product that includes this
* software. The names TMAP, NOAA and/or PMEL, however, may not be used
* in any advertising or publicity to endorse or promote any products
* or commercial entity unless specific written permission is obtained
* from NOAA/PMEL/TMAP. The user also understands that NOAA/PMEL/TMAP
* is not obligated to provide the user with any support, consulting,
* training or assistance of any kind with regard to the use, operation
* and performance of this software nor to provide the user with any
* updates, revisions, new versions or "bug fixes".
*
* THIS SOFTWARE IS PROVIDED BY NOAA/PMEL/TMAP "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL NOAA/PMEL/TMAP BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
package tmap_30.map;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Point;
import MapConstants;
import MapGrid;
import ToolHandle;
import tmap_30.convert.Convert;
import tmap_30.convert.ConvertLength;
import tmap_30.convert.ConvertLongitude;
/**
* A rectangular map tool defined by x, y, width and height.
* This abstract superclass defines a rectangular tool with
* nine ToolHandles (small rectangles) which allow
* reshaping and repositioning of the tool on a MapCanvas.
*
* The abstract
method which makes this class
* abstract is the draw(Graphics g)
method.
*
* @version 3.0 Sept 09 1999
* @author Jonathan Callahan
*/
public abstract class MapTool extends Rectangle
implements MapConstants
{
/*
* These constants are for internal use only and are
* not part of the API.
*/
static final int NW = 0;
static final int N = 1;
static final int NE = 2;
static final int E = 3;
static final int SE = 4;
static final int S = 5;
static final int SW = 6;
static final int W = 7;
static final int C = 8;
/*
* These flags are set when the tool bumps up against the edges and are watched by the
* MapScroller which then takes the appropriate action. (Internal, not in API.)
*/
boolean pan_down = false;
boolean pan_down_fast = false;
boolean pan_left = false;
boolean pan_left_fast = false;
boolean pan_right = false;
boolean pan_right_fast = false;
boolean pan_up = false;
boolean pan_up_fast = false;
/*
* Additional flags set in applyClipRect which tell bump_against_sides()
* whether scrolling should be invoked. (Internal, not in API.)
*/
protected boolean left_edge_scroll = true;
protected boolean right_edge_scroll = true;
protected boolean top_edge_scroll = true;
protected boolean bottom_edge_scroll = true;
/**
* Current "user" values for the left, middle and right edges of this tool.
*
* Access these values with LO
, MID
* or HI
as in: user_X[LO]
.
* DO NOT SET THESE VALUES. They should only be read.
*/
public double [] user_X = new double[3];
/**
* Current "user" values for the bottom, middle and top edges of this tool.
*
* Access these values with LO
, MID
* or HI
as in: user_Y[LO]
.
* DO NOT SET THESE VALUES. They should only be read.
*/
public double [] user_Y = new double[3];
/**
* Range for this tool in "user" values. The tool will be
* restricted to movement within this range.
*
* Access these values with LO
* or HI
as in: range_X[LO]
.
*/
public double [] range_X = new double[2];
/**
* Range for this tool in "user" values. The tool will be
* restricted to movement within this range.
*
* Access these values with LO
* or HI
as in: range_Y[LO]
.
*/
public double [] range_Y = new double[2];
/**
* Delta for this tool in "user" values. This determins
* the grid spacing for this tool along the X axis and is
* used for grid snapping.
*
* A value of 0.0 tells the MapCanvas to use the default
* value calculated by MapGrid.setDomain_X.
*/
public double delta_X = 1.0;
/**
* Delta for this tool in "user" values. This determins
* the grid spacing for this tool along the Y axis and is
* used for grid snapping.
*
* A value of 0.0 tells the MapCanvas to use the default
* value calculated by MapGrid.setDomain_Y.
*/
public double delta_Y = 1.0;
/**
* Whether the tool handles should be drawn or not.
*/
protected boolean drawHandles = false;
/**
* Whether the tool snaps to the underlying grid or not.
*/
protected boolean snap_X = false;
/**
* Whether the tool snaps to the underlying grid or not.
*/
protected boolean snap_Y = false;
/**
* Whether or not the tool should always have a range of
* values along the X axis.
*/
public boolean needsRange_X = true;
/**
* Whether or not the tool should always have a range of
* values along the Y axis.
*/
public boolean needsRange_Y = true;
/**
* The grid on which this tool acts. This is just a reference to
* the MapGrid associated with the MapCanvas.
*/
public MapGrid grid;
/**
* The bounding rectangle in which this tool moves freely.
*
* The boundingRect is the intersection of the
* canvas_clipRect and the tool X and Y ranges. The
* boundingRect defines the area of free movement
* before the tool either 1) bumps against a side or
* 2) causes the image to scroll.
*/
protected Rectangle boundingRect;
/**
* The area of the map canvas occupied by the map. The
* bounding rectangle will be some rectangular subset of this area.
*/
protected Rectangle canvas_clipRect;
/**
* The color of the tool.
*/
protected Color color;
/**
* Returns true
if the tool is "active".
* An "active" tool is one that is currently being dragged
* or resized by the mouse.
*/
protected boolean active = false;
/**
* The array of handles for this tool.
*/
protected ToolHandle [] handle;
/**
* The number of handles in this tool.
*/
protected int numHandles = 9;
/**
* The type of the active handle (eg. NW
).
*/
private int selectedHandle;
/**
* The handle type to return when mouseDown doesn't activate
* one of the handles.(eg. NW
).
*/
protected int mouseDownHandle = NW;
/**
* If a tool has an extent along an axis, then it should snap to grid
* midpoints on that axis IF the CENTER handle is selected AND the
* extent is an odd number of grid cells. Tools which have no extent
* along an axis should always snap to grid points.
*/
boolean snapMid_X = true;
boolean snapMid_Y = true;
/**
* The width for the handles of this tool.
*/
int hw=5;
/**
* The height for the handles of this tool.
*/
int hh=5;
/**
* Constructs a new MapTool.
*/
public MapTool() {
}
/**
* Constructs and initializes a MapTool with the specified parameters.
* @param x the x coordinate
* @param y the y coordinate
* @param width the width of the MapTool
* @param height the height of the MapTool
* @param color the color of the MapTool
*/
public MapTool(int x, int y, int width, int height, Color color) {
setBounds(x, y, width, height);
setColor(color);
}
/**
* Constructs a MapTool and initializes it with the specified parameters.
* @param rect the rectangle of the MapTool
* @param color the color of the MapTool
*/
public MapTool(Rectangle rect, Color color) {
this(rect.x, rect.y, rect.width, rect.height, color);
}
/**
* Constructs a MapTool and initializes it with the specified parameters.
* @param width the width of the MapTool
* @param height the height of the MapTool
* @param color the color of the MapTool
*/
public MapTool(int width, int height, Color color) {
this(0, 0, width, height, color);
}
/**
* Constructs a MapTool and initializes it with a specified parameters.
* @param p the point
* @param d dimension
* @param color the color of the MapTool
*/
public MapTool(Point p, Dimension d, Color color) {
this(p.x, p.y, d.width, d.height, color);
}
/**
* Constructs a MapTool and initializes it with the specified parameters.
* @param p the value of the x and y coordinate
* @param color the color of the MapTool
*/
public MapTool(Point p, Color color) {
this(p.x, p.y, 0, 0, color);
}
/**
* Constructs a MapTool and initializes it with the specified parameters.
* @param d the value of the width and height
* @param color the color of the MapTool
*/
public MapTool(Dimension d, Color color) {
this(0, 0, d.width, d.height, color);
}
/**
* Returns the String representation of the tool's values.
*/
public String toString() {
StringBuffer sbuf = new StringBuffer(super.toString());
sbuf.append(numHandles + " handles");
if (active)
sbuf.append(", tool is active");
else
sbuf.append(", tool is inactive");
sbuf.append(", selected handle = " + selectedHandle);
return sbuf.toString();
}
/**
* Returns the state of the tool.
* @return the state of the tool.
*/
public boolean is_active() {
return active;
}
/**
* Sets the "snap to grid" state.
* When snap_X or snap_Y is true
, MapTools will snap to gridpoints along these axes where gridpoints are defined by the MapGrid associated with the MapCanvas.
* @param snap_X whether snapping is in effect for the X axis.
* @param snap_Y whether snapping is in effect for the Y axis.
*/
public void setSnapping(boolean snap_X, boolean snap_Y) {
this.snap_X = snap_X;
this.snap_Y = snap_Y;
}
/**
* Gets snap_X for this tool.
*/
public boolean getSnap_X() {
return snap_X;
}
/**
* Gets snap_Y for this tool.
*/
public boolean getSnap_Y() {
return snap_Y;
}
/**
* Returns the grid on which this tool acts.
* @return the MapGrid on which this tool acts.
*/
public MapGrid getGrid() {
return grid;
}
/**
* Sets the grid on which this tool acts.
* @param new_grid the new grid
*/
public void setGrid(MapGrid grid) {
this.grid = grid;
}
/**
* Sets the X range of this tool in "user" coordinates.
*
* Note: If you wish to use setRange_X() to restrict the movement of the tool
* then you must set modulo_X = false
in the associated
* MapGrid. Otherwise the mapGrid boundingRect
* restricting the tool's movement will default to the entire globe.
* @param lo the "user" value of the low end of the data range along X.
* @param hi the "user" value of the high end of the data range along X.
*/
public void setRange_X(double lo, double hi) {
range_X[LO] = lo;
range_X[HI] = hi;
}
/**
* Sets the Y range of this tool in "user" coordinates.
* @param lo the "user" value of the low end of the data range along Y.
* @param hi the "user" value of the highe end of the data range along Y.
*/
public void setRange_Y(double lo, double hi) {
range_Y[LO] = lo;
range_Y[HI] = hi;
}
/**
* Sets delta_X for this tool in "user" coordinates. This delta_X
* value will override the default value calculated in
* MapGrid.setDomain_X.
* @param delta the "user" value of of the grid spacing along X.
*/
public void setDelta_X(double delta) {
delta_X = delta;
}
/**
* Gets delta_X for this tool.
*/
public double getDelta_X() {
return delta_X;
}
/**
* Sets delta_Y for this tool in "user" coordinates. This delta_X
* value will override the default value calculated in
* MapGrid.setDomain_Y.
* @param delta the "user" value of of the grid spacing along Y.
*/
public void setDelta_Y(double delta) {
delta_Y = delta;
}
/**
* Gets delta_Y for this tool.
*/
public double getDelta_Y() {
return delta_Y;
}
/**
* Resizes the tool to the intersection of the current tool
* rectangle and the specified rectangle.
*
* @param x the x location in pixels
* @param y the y location in pixels
* @param width the width in pixels
* @param height the height in pixels
* @see Rectangle
*/
public void intersect(int x, int y, int width , int height) {
Rectangle rect = new Rectangle(x, y, width, height);
Rectangle newRect = super.intersection(rect);
this.setBounds(newRect);
}
/**
* Moves the tool to a new x, y location interpreting
* x and y as pixels.
* @param x the x location in pixels
* @param y the y location in pixels
* @see Rectangle
*/
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
this.saveHandles();
}
/**
* Moves the tool to a new x, y location interpreting
* x and y as user values on this tools grid.
* @param x the x location in user values
* @param y the y location in user values
* @see Rectangle
// JC_TODO: This method should do the same checks found in
// JC_TODO: setUserBounds().
*/
public void setUserLocation(double x, double y) {
setLocation(grid.userToPixel_X(x), grid.userToPixel_Y(y));
}
/**
* Reshapes the tool using a rectangle specified in pixel values
* @param rect a rectangle specified in pixels
* @see Rectangle
*/
public void setBounds(Rectangle rect) {
setBounds(rect.x, rect.y, rect.width, rect.height);
}
/**
* Reshapes the tool using pixel values
* @param x the x location in pixels
* @param y the y location in pixels
* @param width the width in pixels
* @param height the height in pixels
* @see Rectangle
*/
public void setBounds(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.saveHandles();
}
/**
* Reshapes the tool using user values
* @param left the left in user values
* @param right the right in user values
* @param bottom the bottom location in user values
* @param top the top location in user values
* @see Rectangle
*
* This method of reshaping the tool does checks against the
* tool's associated grid to make sure the tool stays within
* the range and domain.
*/
public void setUserBounds(double left, double right, double bottom, double top) {
int width=0, height=0;
// First check:
// keep the top and bottom, left and right values within the domain of X and Y.
//
if ( top < grid.domain_Y[LO] ) { top = grid.domain_Y[LO]; }
if ( top > grid.domain_Y[HI] ) { top = grid.domain_Y[HI]; }
if ( bottom < grid.domain_Y[LO] ) { bottom = grid.domain_Y[LO]; }
if ( bottom > grid.domain_Y[HI] ) { bottom = grid.domain_Y[HI]; }
if ( grid.modulo_X ) {
while (left < grid.domain_X[LO]) { left += grid.x_factor; }
while (left > grid.domain_X[HI]) { left -= grid.x_factor; }
while (right < grid.domain_X[LO]) { right += grid.x_factor; }
while (right > grid.domain_X[HI]) { right -= grid.x_factor; }
} else {
if (left < grid.domain_X[LO]) { left = grid.domain_X[LO]; }
if (left > grid.domain_X[HI]) { left = grid.domain_X[HI]; }
if (right < grid.domain_X[LO]) { right = grid.x_factor; }
if (right > grid.domain_X[HI]) { right = grid.domain_X[HI]; }
}
// Second check:
// IF the X axis type is LONGITUDE_AXIS
// AND the tool needs a range of X
// AND left == right
// THEN make the width 360 degrees
//
if ( (grid.x_type == LONGITUDE_AXIS) && needsRange_X && (left == right) ) {
right = left + 360.0;
}
// Third check:
// (right < left) is only valid for LONGITUDE_AXIS
//
if ( right < left ) {
if ( grid.x_type != LONGITUDE_AXIS )
System.out.println("ERROR in MapTool.java:serUserBounds(): " + right + " < " + left);
if ( grid.modulo_X )
width = (int) ( (grid.x_factor - (left-right)) * ((grid.imageRect.width-1)/grid.x_factor) );
else
width = (int) ( (left-right) * ((grid.imageRect.width-1)/grid.x_factor) );
} else
width = (int) ( (right-left) * ((grid.imageRect.width-1)/grid.x_factor) );
height = grid.userToPixel_Y(bottom) - grid.userToPixel_Y(top);
setBounds(grid.userToPixel_X(left), grid.userToPixel_Y(top), width, height);
setUser_XY(left, right, bottom, top);
}
/**
* Gets the color for this tool.
* @return the Color of this tool.
*/
public Color getColor() {
return color;
}
/**
* Sets the color for this tool.
* @param color the Color of this tool.
*/
public void setColor(Color color) {
int i=0;
this.color = color;
for (i=0; i grid.userToPixel_X(range_X[HI]) )
new_x = grid.snap_X(x, SNAP_ON, -1);
if ( new_x < grid.userToPixel_X(range_X[LO]) )
new_x = x;
if ( new_x < x ) {
width = x - new_x;
x = new_x;
} else if ( new_x > x ) {
width = new_x - x;
}
} else {
if ( x == boundingRect.x + boundingRect.width ) {
x = x-1; width=1;
} else {
width=1;
}
}
}
// For the Y axis remember that pixel values increase
// from top to bottom while user values increase from
// bottom to top.
// If one grid cell above and below are both outside
// the range, return the original Y value.
//
if ( height == 0 && needsRange_Y ) {
return_val += 2;
if ( snap_Y ) {
int new_y = grid.snap_Y(y, SNAP_ON, 1);
if ( new_y < grid.userToPixel_Y(range_Y[HI]) )
new_y = grid.snap_Y(y, SNAP_ON, -1);
if ( new_y > grid.userToPixel_Y(range_Y[LO]) )
new_y = y;
if ( new_y < y ) {
height = y - new_y;
y = new_y;
} else if ( new_y > y ) {
height = new_y - y;
}
} else {
if ( y == boundingRect.y + boundingRect.height ) {
y = y-1; height=1;
} else {
height=1;
}
}
}
return return_val;
}
/**
* Applies the canvas_clipRect after potential scrolling
* so that a new boundingRect can be calculated for this tool.
*
* @param rect the MapCanvas clipRect rectangle.
*/
public void applyClipRect(Rectangle rect) {
applyClipRect(rect.x, rect.y, rect.width, rect.height);
}
/**
* Sets the bounding rectangle for this tool. MapTools are always constrained to remain
* within the bounding rectangle. This behavior allows for a tool to be used to select
* data within a specified range which may be smaller than the domain represented by
* the base image used in the MapCanvas.
* @param c_r_x the x coordinate of the boundingRect
* @param c_r_y the y coordinate of the boundingRect
* @param c_r_width the width of the boundingRect.
* @param c_r_height the height of the boundingRect.
*/
public void applyClipRect(int c_r_x, int c_r_y, int c_r_width, int c_r_height) {
canvas_clipRect = new Rectangle(c_r_x, c_r_y, c_r_width, c_r_height);
// range_ values
int r_xlo = grid.userToPixel_X(range_X[LO]);
int r_xhi = grid.userToPixel_X(range_X[HI]);
int r_ylo = grid.userToPixel_Y(range_Y[HI]);
int r_yhi = grid.userToPixel_Y(range_Y[LO]);
// clipRect values
int c_r_xlo = c_r_x;
int c_r_xhi = c_r_x + c_r_width;
int c_r_ylo = c_r_y;
int c_r_yhi = c_r_y + c_r_height;
// new boundingRect values
//
// If the base image doesn't represent a domain which is modulo_X
// * r_lo is guaranteed to be on the left
// * set the bounding box to be the intersection of r_ and c_r_ values.
// If the domain is modulo_X but we have a restricted range
// * set the bounding box to be the intersection of r_ and c_r_ values.
// If the range straddles the domain boundaries (e.g. dom=-180:180, rng=160E:90W)
// * test to make sure r_xlo/hi isn't off the right/left edge
// If the domain is modulo_X and the range equals the domain
// use the c_r_ values.
int b_r_xlo = c_r_xlo;
int b_r_xhi = c_r_xhi;
if ( !grid.modulo_X ) {
if (c_r_xlo > r_xlo) {
b_r_xlo = c_r_xlo;
left_edge_scroll = true;
} else {
b_r_xlo = r_xlo;
left_edge_scroll = false;
}
if (c_r_xhi < r_xhi) {
b_r_xhi = c_r_xhi;
right_edge_scroll = true;
} else {
b_r_xhi = r_xhi;
right_edge_scroll = false;
}
} else { // The domain is modulo_X
if ( Math.abs(range_X[HI]-range_X[LO]) < Math.abs(grid.domain_X[HI]-grid.domain_X[LO]) ) {
// If the range is a subset of the domain, the
// test is as expected.
if (c_r_xlo > r_xlo) {
b_r_xlo = c_r_xlo;
left_edge_scroll = true;
} else {
b_r_xlo = r_xlo;
left_edge_scroll = false;
}
if (c_r_xhi < r_xhi) {
b_r_xhi = c_r_xhi;
right_edge_scroll = true;
} else {
b_r_xhi = r_xhi;
right_edge_scroll = false;
}
// If the range straddles the domain boundaries, an
// additional check is needed.
if ( (range_X[HI]-range_X[LO]) < 0 ) {
if (b_r_xlo > c_r_xhi) {
b_r_xlo = c_r_xlo;
left_edge_scroll = true;
}
if (b_r_xhi < c_r_xlo) {
b_r_xhi = c_r_xhi;
right_edge_scroll = true;
}
}
} else { // The range equals the domain and is modulo_X
b_r_xlo = c_r_xlo;
left_edge_scroll = true;
b_r_xhi = c_r_xhi;
right_edge_scroll = true;
}
}
int b_r_ylo = c_r_ylo;
int b_r_yhi = c_r_yhi;
if (c_r_ylo > r_ylo) {
b_r_ylo = c_r_ylo;
top_edge_scroll = true;
} else {
b_r_ylo = r_ylo;
top_edge_scroll = false;
}
if (c_r_yhi < r_yhi) {
b_r_yhi = c_r_yhi;
bottom_edge_scroll = true;
} else {
b_r_yhi = r_yhi;
bottom_edge_scroll = false;
}
int b_r_width = b_r_xhi - b_r_xlo;
int b_r_height = b_r_yhi - b_r_ylo;
boundingRect = new Rectangle(b_r_xlo, b_r_ylo, b_r_width, b_r_height);
}
/**
* Reshapes the tool.
* @param mouse_x new mouse X position for one of the handles
* @param mouse_y new mouse Y position for one of the handles
*/
public void handle_reshape(int mouse_x, int mouse_y) {
boolean handle_overtook_opposite_side = false;
// First, deal with the new X position
// Snap to grid if necessary
if ( snap_X ) {
mouse_x = grid.snap_X(mouse_x, SNAP_ON);
if ( mouse_x > boundingRect.x+boundingRect.width )
mouse_x = grid.snap_X(mouse_x, SNAP_ON, -1);
else if ( mouse_x < boundingRect.x )
mouse_x = grid.snap_X(mouse_x, SNAP_ON, 1);
}
if ( mouse_x > x + width ) {
switch (selectedHandle) {
case NE:
case E:
case SE:
// x = x;
width = mouse_x - x;
break;
case NW:
case W:
case SW:
handle_overtook_opposite_side = true;
x = x + width; // left edge = old right edge
width = mouse_x - x; // width = new width - new left edge
break;
}
} else if ( mouse_x < x ) {
switch (selectedHandle) {
case NE:
case E:
case SE:
handle_overtook_opposite_side = true;
width = x - mouse_x; // width = old left edge - new width
x = mouse_x; // left edge = new width
break;
case NW:
case W:
case SW:
// width = old width + ( old left edge - new width )
width = width + (x - mouse_x);
x = mouse_x; // left edge = new width
break;
}
} else {
switch (selectedHandle) {
case NE:
case E:
case SE:
// x = x;
width = mouse_x - x;
break;
case NW:
case W:
case SW:
// width = old width + ( old left edge - new width )
width = width + (x - mouse_x);
x = mouse_x; // left edge = new width
break;
}
}
if ( handle_overtook_opposite_side ) {
if ( selectedHandle == NE ) selectedHandle = NW;
else if ( selectedHandle == E ) selectedHandle = W;
else if ( selectedHandle == SE ) selectedHandle = SW;
else if ( selectedHandle == NW ) selectedHandle = NE;
else if ( selectedHandle == W ) selectedHandle = E;
else if ( selectedHandle == SW ) selectedHandle = SE;
handle_overtook_opposite_side = false;
}
// Now, deal with the new Y position
// Snap to grid if necessary
if ( snap_Y ) {
mouse_y = grid.snap_Y(mouse_y, SNAP_ON);
if ( mouse_y > boundingRect.y+boundingRect.height )
mouse_y = grid.snap_Y(mouse_y, SNAP_ON, 1);
else if ( mouse_y < boundingRect.y )
mouse_y = grid.snap_Y(mouse_y, SNAP_ON, -1);
}
if ( mouse_y > y + height ) {
switch (selectedHandle) {
case NW:
case N:
case NE:
handle_overtook_opposite_side = true;
y = y + height; // top edge = old bottom edge
height = mouse_y - y; // height = new height - new top edge
break;
case SW:
case S:
case SE:
// y = y;
height = mouse_y - y;
break;
}
} else if ( mouse_y < y ) {
switch (selectedHandle) {
case NW:
case N:
case NE:
// height = old height + ( old top edge - new height )
height = height + (y - mouse_y);
y = mouse_y; // top edge = new height
break;
case SW:
case S:
case SE:
handle_overtook_opposite_side = true;
height = y - mouse_y; // height = old top edge - new height
y = mouse_y; // top edge - new height
break;
}
} else {
switch (selectedHandle) {
case NW:
case N:
case NE:
// height = old height + ( old top edge - new height )
height = height + (y - mouse_y);
y = mouse_y; // top edge = new height
break;
case SW:
case S:
case SE:
// y = y;
height = mouse_y - y;
break;
}
}
if ( handle_overtook_opposite_side ) {
if ( selectedHandle == NW ) selectedHandle = SW;
else if ( selectedHandle == N ) selectedHandle = S;
else if ( selectedHandle == NE ) selectedHandle = SE;
else if ( selectedHandle == SW ) selectedHandle = NW;
else if ( selectedHandle == S ) selectedHandle = N;
else if ( selectedHandle == SE ) selectedHandle = NE;
handle_overtook_opposite_side = false;
}
}
/**
* Sets the user_X/Y values for this tool.
*/
public void setUser_XY() {
setUser_X();
setUser_Y();
}
/**
* Sets the user_X/Y values for this tool.
*/
public void setUser_XY(double Xlo, double Xhi, double Ylo, double Yhi) {
setUser_X(Xlo,Xhi);
setUser_Y(Ylo,Yhi);
}
/**
* Sets the user_X values for this tool.
*/
public void setUser_X() {
double x_lo=0.0, x_hi=0.0, x_range=0.0;
Convert XConvert = new ConvertLongitude();
if ( grid.x_type != LONGITUDE_AXIS )
XConvert = new ConvertLength();
user_X[LO] = grid.pixelToUser_X(x);
user_X[HI] = grid.pixelToUser_X(x+width);
user_X[PT] = grid.pixelToUser_X(x+width/2+1);
if ( snap_X ) {
user_X[LO] = grid.snapUser_X(user_X[LO], SNAP_ON, 0);
user_X[HI] = grid.snapUser_X(user_X[HI], SNAP_ON, 0);
user_X[PT] = grid.snapUser_X(user_X[PT], SNAP_ON, 0);
}
// First check
// It's quite possible for the pixelToUser_ code or the
// snapUser_ code to return a value which lies outside
// the range. This can happen when there are fewer pixels
// than grid points. If we've gotten outside the range,
// get back in.
//
// Don't forget that we might be a Longitude axes where
// (hi < lo) is acceptable for the range or the user values.
// Use the logic in ConvertLongitude to check for this.
XConvert.setRange(range_X[LO], range_X[HI]);
user_X[LO] = XConvert.getNearestValue(user_X[LO], LO);
user_X[HI] = XConvert.getNearestValue(user_X[HI], HI);
// The following logic is needed in order to prevent whole-world
// selections from appearing as single-point selections.
if ( grid.modulo_X && (grid.x_type == LONGITUDE_AXIS) ) {
if ( (user_X[HI] == user_X[LO]) && (width > 0) ) {
user_X[HI] = user_X[LO] + (grid.domain_X[HI]-grid.domain_X[LO]);
}
}
// Second check
// It seems that low pixel resolution can result in the following
// annoying bug: A whole world selection may be slightly larger
// than that, appearing as "140 E" to "142 E" for exampe, which
// specifies a longitude range of 2 degrees rather than 360 which
// the user expects.
x_lo = user_X[LO];
x_hi = user_X[HI];
if ( grid.modulo_X && (grid.x_type == LONGITUDE_AXIS) ) {
if ( x_lo < 0 ) x_lo += 360;
if ( x_hi < 0 ) x_hi += 360;
if ( x_hi < x_lo ) x_hi += 360;
}
x_range = (x_hi - x_lo);
if ( grid.rangeToPixels_X(x_range) < (width/2) ) {
user_X[HI] = user_X[LO] + (grid.domain_X[HI]-grid.domain_X[LO]);
}
}
/**
* Sets the user_Y values for this tool.
*/
public void setUser_Y() {
// We need to "invert" the user_Y values because the tools consider
// the top of the screen to be "y=0" and we want to make the bottom
// of the map to be "y=0"
user_Y[HI] = grid.pixelToUser_Y(y);
user_Y[LO] = grid.pixelToUser_Y(y+height);
user_Y[PT] = grid.pixelToUser_Y(y+height/2+1);
if ( snap_Y ) {
user_Y[LO] = grid.snapUser_Y(user_Y[LO], SNAP_ON);
user_Y[HI] = grid.snapUser_Y(user_Y[HI], SNAP_ON);
user_Y[PT] = grid.snapUser_Y(user_Y[PT], SNAP_ON);
}
// First check
// It's quite possible for the pixelToUser_ code or the
// snapUser_ code to return a value which lies outside
// the range. This can happen when there are fewer pixels
// than grid points. If we've gotten outside the range,
// get back in.
if ( user_Y[LO] < range_Y[LO] ) user_Y[LO] = range_Y[LO];
if ( user_Y[HI] > range_Y[HI] ) user_Y[HI] = range_Y[HI];
}
/**
* Sets the user_X values for this tool.
* @param lo the lower value in "user" units.
* @param hi the higher value in "user" units.
*
* This method sets the user values directly rather than going through
* the interpolation in the tool. Whenever map.reshape_tool() or
* map.center_tool() is called, this method should be used because
* we don't want to allow the possibility of the grid altering
* the user values simply because the image has fewer pixels
* than grid points.
*/
public void setUser_X(double lo, double hi) {
// The logic below for lo > hi is the following:
//
// [ (lo as a percentage of the way around) - (hi ditto) ] / 2.0 + 50%
// gives the percentage of the way around from the start.
//
user_X[LO] = lo;
user_X[HI] = hi;
if ( hi >= lo ) {
user_X[PT] = lo + (hi - lo)/2.0;
} else {
if ( grid.x_type != LONGITUDE_AXIS ) {
System.out.println("ERROR in set_user_values: non-modulo_X and hi(" + hi + ") < lo (" + lo + ")");
} else {
user_X[PT] = grid.x_factor / 2.0 + grid.domain_X[LO] +
(( (lo-grid.domain_X[LO])/grid.x_factor - (hi-grid.domain_X[LO])/grid.x_factor )/2.0) * grid.x_factor;
}
}
}
/**
* Sets the user_Y values for this tool.
* @param lo the lower value in "user" units.
* @param hi the higher value in "user" units.
*
* This method sets the user values directly rather than going through
* the interpolation in the tool. Whenever map.reshape_tool() or
* map.center_tool() is called, this method should be used because
* we don't want to allow the possibility of the grid altering
* the user values simply because the image has fewer pixels
* than grid points.
*/
public void setUser_Y(double lo, double hi) {
user_Y[LO] = lo;
user_Y[HI] = hi;
user_Y[PT] = lo + (hi - lo)/2.0;
}
/**
* Adjust the width or height for those tools which don't
* specify an extent of width or height.
*
* XTool, YTool, PTTool and decendents retain a width and
* height but these may need to be adjusted as the tool is
* moved near the sides.
*/
public void adjustWidthHeight() {
// No adjustment for tools which specify an XY region.
}
//----------------------------------------------------------//
//-------------------- Action methods --------------------//
//----------------------------------------------------------//
/**
* Draws a MapTool.
* This is the abstract
method which makes this class abstract.
* @param g the graphics context for the drawing operation.
*/
public abstract void draw(Graphics g);
/**
* Notifies tool of a mouseMove event. Returns Frame.MOVE_CURSOR
if the
* mouse moves of the center tool handle.
* @param mouse_x current mouse X
* @param mouse_y current mouse Y
* @return the type of cursor to display.
*/
public int mouseMove(int mouse_x, int mouse_y) {
int i=0;
for (i=0; i boundingRect.x+boundingRect.width) ? (boundingRect.x+boundingRect.width) : mouse_x;
mouse_y = (mouse_y < boundingRect.y) ? boundingRect.y : mouse_y;
mouse_y = (mouse_y > boundingRect.y+boundingRect.height) ? (boundingRect.y+boundingRect.height) : mouse_y;
x = mouse_x;
y = mouse_y;
width = 0;
height = 0;
active = true;
selectedHandle = mouseDownHandle;
saveHandles();
}
}
/**
* Notifies tool of a mouseDrag event.
* @param mouse_x current mouse X
* @param mouse_y current mouse Y
*/
public void mouseDrag(int mouse_x, int mouse_y) {
if ( active ) {
// Recalculate the bounding rectangle because of potential scrolling
this.applyClipRect(this.canvas_clipRect);
// Keep the mouse within the bounding rectangle
mouse_x = (mouse_x < boundingRect.x) ? boundingRect.x : mouse_x;
mouse_x = (mouse_x > boundingRect.x+boundingRect.width) ?
(boundingRect.x+boundingRect.width) : mouse_x;
mouse_y = (mouse_y < boundingRect.y) ? boundingRect.y : mouse_y;
mouse_y = (mouse_y > boundingRect.y+boundingRect.height) ?
(boundingRect.y+boundingRect.height) : mouse_y;
if ( selectedHandle == C )
this.bump_against_sides(mouse_x,mouse_y);
else
this.handle_reshape(mouse_x,mouse_y);
// We use setUser_XY() here because the user is modifying
// the shape of the tool and it's appropriate to calculate
// new user_X/Y values based on the image.
this.saveHandles();
this.setUser_XY();
}
}
/**
* Notifies tool of a mouseUp event.
* @param x current mouseX
* @param y current mouseY
*/
public void mouseUp(int mouse_x, int mouse_y) {
int i=0, new_mouse=0;
double user_range=0.0;
if ( active ) {
if ( selectedHandle == C ) {
// Recalculate the bounding rectangle because of potential scrolling
this.applyClipRect(this.canvas_clipRect);
// This is for the special case where a non-XY tool has been
// moved close to the edge. These tools are drawn in the
// center of the width or height range and this range must
// shrink near the boundaries.
adjustWidthHeight();
// This is for the special case where you have scrolled to the top of the map
// and the range is less than the full map. If you didn't move the mouse
// after you went outside the canvas rectangle, you will have scrolled to
// top of the map. Now we need to reposition the tool so that it is within
// the appropriate boundingRect as defined by the Grid.range.
if ( this.x < boundingRect.x ) {
this.x = boundingRect.x;
} else if ( this.x > boundingRect.x + boundingRect.width ) {
this.x = boundingRect.x+boundingRect.width-this.width;
}
if ( this.y < boundingRect.y ) {
this.y = boundingRect.y;
} else if ( this.y > boundingRect.y + boundingRect.height ) {
this.y = boundingRect.y+boundingRect.height-this.height;
}
// This is for the special case where you are snpping to grid and
// you have moved the tool by the central handle. If you dragged
// the handle all the way to the edge, you will have "snapped" to
// the edge of the boundingRect even though that may not represent
// a gridpoint. (This behavior is needed to allow scrolling to
// happen.) Now we need to snap to the next gridpoint inwards.
if ( snap_X ) {
// 0) check for odd (SNAP_MID) or even (SNAP_ON) number of grid cells
// 1) snap
// 3) make sure the left and right edges are within boundingRect
user_range = grid.pixelToUser_X(this.x+this.width) -
grid.pixelToUser_X(this.x);
// Center handle and ODD number of grid cells
if ( ((int)(user_range/grid.delta_X))%2 == 1 && snapMid_X ) {
mouse_x = grid.snap_X(mouse_x, SNAP_MID);
new_mouse = mouse_x;
while ( mouse_x+this.width/2 > boundingRect.x+boundingRect.width ) {
mouse_x = grid.snap_X(--new_mouse, SNAP_MID);
}
while ( mouse_x-this.width/2 < boundingRect.x ) {
mouse_x = grid.snap_X(++new_mouse, SNAP_MID);
}
this.x = mouse_x - this.width/2;
// Center handle and EVEN number of grid cells
} else {
mouse_x = grid.snap_X(mouse_x, SNAP_ON);
new_mouse = mouse_x;
while ( mouse_x+this.width/2 > boundingRect.x+boundingRect.width ) {
mouse_x = grid.snap_X(--new_mouse, SNAP_ON);
}
while ( mouse_x-this.width/2 < boundingRect.x ) {
mouse_x = grid.snap_X(++new_mouse, SNAP_ON);
}
this.x = mouse_x - this.width/2;
}
}
if ( snap_Y ) {
user_range = grid.pixelToUser_Y(this.y+this.height) -
grid.pixelToUser_Y(this.y);
user_range = Math.abs(user_range);
// Center handle and ODD number of grid cells
if ( ((int)(user_range/grid.delta_Y))%2 == 1 && snapMid_Y ) {
mouse_y = grid.snap_Y(mouse_y, SNAP_MID);
new_mouse = mouse_y;
while ( mouse_y+this.height/2 > boundingRect.y+boundingRect.height ) {
mouse_y = grid.snap_Y(--new_mouse, SNAP_MID);
}
while ( mouse_y-this.height/2 < boundingRect.y ) {
mouse_y = grid.snap_Y(++new_mouse, SNAP_MID);
}
this.y = mouse_y - this.height/2;
// Center handle and EVEN number of grid cells
} else {
mouse_y = grid.snap_Y(mouse_y, SNAP_ON);
new_mouse = mouse_y;
while ( mouse_y+this.height/2 > boundingRect.y+boundingRect.height ) {
mouse_y = grid.snap_Y(--new_mouse, SNAP_ON);
}
while ( mouse_y-this.height/2 < boundingRect.y ) {
mouse_y = grid.snap_Y(++new_mouse, SNAP_ON);
}
this.y = mouse_y - this.height/2;
}
}
}
// We need to check for zero width/height selections.
// If we have zero width or height in conflict with
// the needs of the tool, we must reset the tool width/height
// or x/y to maintain the minimum delta_X/Y.
//
check_for_zero_range();
active = false;
pan_left = false;
pan_right = false;
pan_left_fast = false;
pan_right_fast = false;
pan_up = false;
pan_down = false;
pan_up_fast = false;
pan_down_fast = false;
// We use setUser_XY() here because the user is modifying
// the shape of the tool and it's appropriate to calculate
// new user_X/Y values based on the image.
this.saveHandles();
this.setUser_XY();
}
}
/**
* Allows movement of the tool within the bounding rectangle specified in applyClipRect().
* @param x current mouseX
* @param y current mouseY
*/
public void bump_against_sides(int mouse_x, int mouse_y) {
double user_range = 0.0;
/*
* If you are snapping to the grid and the tool encompasses
* an odd number of grid cells along an axis, then you should
* snap to the grid cell MIDpoints along that axis. Otherwise,
* snap ONto the gridpoints themselves.
*/
if ( snap_X ) {
user_range = grid.pixelToUser_X(this.x+this.width) -
grid.pixelToUser_X(this.x);
if ( ((int)(user_range/grid.delta_X))%2 == 1 && snapMid_X )
mouse_x = grid.snap_X(mouse_x, SNAP_MID);
else
mouse_x = grid.snap_X(mouse_x, SNAP_ON);
}
if ( snap_Y ) {
user_range = grid.pixelToUser_Y(this.y+this.height) -
grid.pixelToUser_Y(this.y);
user_range = Math.abs(user_range);
if ( ((int)(user_range/grid.delta_Y))%2 == 1 && snapMid_Y )
mouse_y = grid.snap_Y(mouse_y, SNAP_MID);
else
mouse_y = grid.snap_Y(mouse_y, SNAP_ON);
}
/*
* Now we're done with snapping and we can decide whether
* scrolling should be performed.
*/
// left edge against left edge of the boundingRect
if ( (mouse_x-width/2) < (boundingRect.x) ) {
this.pan_right = false;
this.pan_right_fast = false;
if (left_edge_scroll) {
this.pan_left = true;
if ( mouse_x < boundingRect.x+2 )
this.pan_left_fast = true;
else
this.pan_left_fast = false;
} else {
this.pan_left = false;
this.pan_left_fast = false;
}
x = boundingRect.x;
// right edge against right edge of the boundingRect
} else if ( (mouse_x-width/2+width) > (boundingRect.x+boundingRect.width) ) {
this.pan_left = false;
this.pan_left_fast = false;
if (right_edge_scroll) {
this.pan_right = true;
if ( mouse_x > boundingRect.x+boundingRect.width-2 )
this.pan_right_fast = true;
else
this.pan_right_fast = false;
} else {
this.pan_right = false;
this.pan_right_fast = false;
}
x = (boundingRect.x+boundingRect.width) - width;
} else {
this.pan_left = false;
this.pan_right = false;
this.pan_left_fast = false;
this.pan_right_fast = false;
x = mouse_x-width/2;
}
// top edge against top edge of the boundingRect
if ( (mouse_y-height/2) < (boundingRect.y) ) {
this.pan_down = false;
this.pan_down_fast = false;
if (top_edge_scroll) {
this.pan_up = true;
if ( mouse_y < boundingRect.y+2 )
this.pan_up_fast = true;
else
this.pan_up_fast = false;
} else {
this.pan_up = false;
this.pan_up_fast = false;
}
y = boundingRect.y;
// bottom edge against bottom edge of the boundingRect
} else if ( (mouse_y-height/2+height) > (boundingRect.y+boundingRect.height) ) {
this.pan_up = false;
this.pan_up_fast = false;
if (bottom_edge_scroll) {
this.pan_down = true;
if ( mouse_y > boundingRect.y+boundingRect.height-2 )
this.pan_down_fast = true;
else
this.pan_down_fast = false;
} else {
this.pan_down = false;
this.pan_down = false;
}
y = (boundingRect.y+boundingRect.height) - height;
} else {
this.pan_down = false;
this.pan_up = false;
this.pan_down_fast = false;
this.pan_up_fast = false;
y = mouse_y-height/2;
}
}
}