/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm3d.core;
import java.awt.Color;
import java.awt.Cursor;
import org.freehep.graphics2d.VectorGraphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
/**
* A BasicMouseTask2D is a MouseTask that allows the user to drag and zoom
* the 2D window that is used to draw the Exhibit in a display. Dragging
* is done with the left mouse button. Zooming is done by dragging up/down
* with the middle mouse button (or holding down a shift or alt or option key).
* When draggin with the left mouse button, the user can switch between
* dragging the exhibit and zooming it by pressing and releasing the shift key.
*
* The user can also use the right mouse key (or hold down the control or command key) to draw
* a rectangle; the rectangle can be moved while dragging by holding down the shift
* key. The View is then zoomed into that rectangle. The shape of the rectangle
* is contrained to have the same aspect ration as the display.
*
*
All the methods in this class are called by a Display in response to user actions.
* None of them are meant to be called directly.
*/
public class BasicMouseTask2D extends MouseTask {
private int operation;
private static final int NONE = 0, DRAG = 1, ZOOM = 2, ZOOM_RECT = 3;
private int startx, starty;
private int prevx, prevy;
private double[] startWindow;
private double requestedAspectRatio; // aspect ratio of requested x- and y-limits on starting window
private Rectangle scaleRect; // for ZOOM_RECT operation
private long mouseDownTime;
private boolean startedDragging;
private boolean saveFastDrawing;
/**
* Decides what type of operation to perform in response to a user's mouse click, and
* initiates that operation. This method is called by a Display when the user clicks on
* the Display; it is not meant to be called directly.
* @return returns true to tell the Display that the drag operation should continue,
* unless there is no Exhibit currently installed in the display.
*/
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
operation = NONE;
if (view.getExhibit() == null)
return false;
startx = prevx = evt.getX();
starty = prevy = evt.getY();
startWindow = view.getWindow();
Transform tr = view.getTransform();
requestedAspectRatio = Math.abs((tr.getYmaxRequested() - tr.getYminRequested()) / (tr.getXmaxRequested() - tr.getXminRequested()));
if (evt.isAltDown() || evt.isShiftDown())
operation = ZOOM;
else if (evt.isControlDown() || evt.isMetaDown()) {
operation = ZOOM_RECT;
scaleRect = null;
}
else
operation = DRAG;
saveFastDrawing = view.getFastDrawing();
mouseDownTime = evt.getWhen();
startedDragging = false; // won't actualy start until 1/3 second has passed or user moves mouse
return true;
}
/**
* Continue processing a drag operation.
*/
public void doMouseDrag(MouseEvent evt, Display display, View view, int width, int height) {
if (!startedDragging && evt.getWhen() - mouseDownTime < 300
&& Math.abs(evt.getX() - startx) < 3 && Math.abs(evt.getY() - starty) < 3)
return;
if (!startedDragging) {
startedDragging = true;
if (operation != ZOOM_RECT)
view.setFastDrawing(true);
}
if (operation == DRAG && !(evt.isControlDown() || evt.isMetaDown())) {
if (evt.isAltDown() || evt.isShiftDown()) {
operation = ZOOM;
startx = evt.getX();
starty = evt.getY();
startWindow = view.getWindow();
return;
}
}
else if (operation == ZOOM && !(evt.isAltDown() || evt.isShiftDown())) {
operation = DRAG;
startx = evt.getX();
starty = evt.getY();
startWindow = view.getWindow();
return;
}
switch (operation) {
case DRAG:
double pixelWidth = Math.abs(startWindow[1] - startWindow[0])/width;
double pixelHeight = Math.abs(startWindow[3] - startWindow[2])/height;
double offsetX = (startx - evt.getX()) * pixelWidth;
double offsetY = -(starty - evt.getY()) * pixelHeight;
newWindow(view,startWindow[0]+offsetX,startWindow[1]+offsetX,
startWindow[2]+offsetY,startWindow[3]+offsetY);
display.repaint();
break;
case ZOOM:
double diff = -(starty - evt.getY()) / 200.0;
double mag = Math.exp(diff); // factor of e every 200 pixels
double startx_real = startWindow[0] + startx * ((startWindow[1] - startWindow[0]) / width);
double starty_real = startWindow[2] + (height-starty) * ((startWindow[3] - startWindow[2]) / height);
double newwidth = (startWindow[1] - startWindow[0]) * mag;
double newheight = (startWindow[3] - startWindow[2]) * mag;
double newpixelwidth = newwidth / width;
double newpixelheight = newheight / height;
double newxmin = startx_real - newpixelwidth*startx;
double newxmax = newxmin + newwidth;
double newymin = starty_real - newpixelheight*(height-starty);
double newymax = newymin + newheight;
newWindow(view,newxmin,newxmax,newymin,newymax);
display.repaint();
break;
case ZOOM_RECT:
if (evt.isShiftDown()) { // move the rectangle, if any
if (scaleRect != null) {
int movex = evt.getX() - prevx;
int movey = evt.getY() - prevy;
startx += movex;
starty += movey;
prevx = evt.getX();
prevy = evt.getY();
scaleRect.translate(movex,movey);
}
}
else { // change the rectangle's shape
prevx = evt.getX();
prevy = evt.getY();
if (prevx < 0)
prevx = 0;
else if (prevx >= width)
prevx = width-1;
if (prevy < 0)
prevy = 0;
else if (prevy >= height)
prevy = height-1;
if (Math.abs(prevx - startx) < 3 || Math.abs(prevy - starty) < 3)
scaleRect = null;
else {
if (scaleRect == null)
scaleRect = new Rectangle();
scaleRect.setBounds(startx,starty,0,0);
if (view.getPreserveAspect()) {
int w = Math.abs(prevx - startx);
int h = Math.abs(prevy - starty);
double aspect = (double)width / height;
double rectAspect = (double)w / h;
if (aspect > rectAspect)
w = (int)(w*aspect/rectAspect+0.499);
else if (aspect < rectAspect)
h = (int)(h*rectAspect/aspect+0.499);
scaleRect.add(prevx>startx? startx+w : startx-w, prevy>starty? starty+h : starty-h);
}
else
scaleRect.add(prevx,prevy);
}
}
display.repaint();
break;
}
}
/**
* Used to set a new window for the View that has the same requested aspect ratio
* as the current one, and has the specified x and y ranges as the actual ranges
* on the window.
*/
private void newWindow(View view,double xmin, double xmax, double ymin, double ymax) {
double aspect = Math.abs( (ymax - ymin) / (xmax - xmin) );
if (aspect > requestedAspectRatio) { // shrink y range
double shrinkFactor = requestedAspectRatio / aspect;
double newHeight = (ymax - ymin) * shrinkFactor;
double middle = (ymax + ymin) / 2;
ymin = middle - newHeight / 2;
ymax = middle + newHeight / 2;
}
else { // shrink x range
double shrinkFactor = aspect / requestedAspectRatio;
double newWidth = (xmax - xmin) * shrinkFactor;
double middle = (xmax + xmin) / 2;
xmin = middle - newWidth / 2;
xmax = middle + newWidth / 2;
}
view.setWindow(xmin,xmax,ymin,ymax);
}
/**
* Finish processing a drag operation.
*/
public void doMouseUp(MouseEvent evt, Display display, View view, int width, int height) {
if (!startedDragging)
return;
if (operation == ZOOM_RECT && scaleRect != null) {
double pixelWidth = (startWindow[1] - startWindow[0])/width;
double pixelHeight = (startWindow[3] - startWindow[2])/height;
double newxmin = startWindow[0] + scaleRect.x*pixelWidth;
double newxmax = newxmin + scaleRect.width*pixelWidth;
double newymax = startWindow[3] - (scaleRect.y)*pixelHeight;
double newymin = newymax - scaleRect.height*pixelHeight;
newWindow(view,newxmin,newxmax,newymin,newymax);
display.repaint();
}
view.setFastDrawing(saveFastDrawing);
}
/**
* Returns the cursor that the Display should use during a drag operation.
* The cursor that is returned depends on which operation is being performed
* with the mouse.
*/
public Cursor getCursorForDragging(MouseEvent mouseDownEvent, Display display, View view) {
if (operation == ZOOM_RECT)
return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
else if (operation == DRAG)
return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
else if (operation == ZOOM)
return Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
else
return null;
}
/**
* When the Display is repainted during a drag operation, this method will be
* called after the Exhibit is drawn, to give the MouseTask a chance to draw
* some extra stuff on top of the Exhibit. In this class, it draws nothing
* unless the operation that is being performed is to draw a rectangle for
* zooming; if that is the operation, then the rectangle is drawn in red.
*/
public void drawWhileDragging(VectorGraphics g, Display display, View view, int width, int height) {
if (operation != ZOOM_RECT || scaleRect == null)
return;
if (Math.abs(startx - prevx) < 3 || Math.abs(starty - prevy) < 3)
return;
g.setColor(Color.WHITE);
g.drawRect(scaleRect.x-1,scaleRect.y-1,scaleRect.width+2,scaleRect.height+2);
g.drawRect(scaleRect.x+1,scaleRect.y+1,scaleRect.width-2,scaleRect.height-2);
g.setColor(Color.BLACK);
g.drawRect(scaleRect.x,scaleRect.y,scaleRect.width,scaleRect.height);
}
}
Ads help maintain this website.