// Charles A. Loomis, Jr., and University of California, Santa Cruz,
// Copyright (c) 2000
package org.freehep.swing.graphics;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.SwingUtilities;
import org.freehep.swing.images.FreeHepImage;
/**
* This panel allows the user to select a point on the screen. The
* crosshair cursor fill follow the system pointer when the left mouse
* button is pressed. The system pointer will become invisible while
* the user is actively repositioning the crosshair.
*
* This panel will also respond to keystroke input.
* <ul>
* <li>arrow keys: move the active control point in the specified
* direction.
* <li>backspace key: reset selection region (make invisible).
* <li>delete key: reset selection region (make invisible).
* <li>escape key: leave selection mode (make component invisible).
* <li>enter key: accept selection region (send off region
* selected event)
* </ul>
* Normally, the arrow keys move the cursor by 2 pixels per key
* release; however if the shift key is pressed simultaneously,
* then the arrow keys move the cursor by 1 pixel.
*
* @author Charles Loomis
* @version $Id: PointSelectionPanel.java 8584 2006-08-10 23:06:37Z duns $ */
public class PointSelectionPanel
extends GraphicalSelectionPanel {
final private static int cursorSize=10;
Rectangle oldBounds = new Rectangle();
Rectangle lastDrawnRect = new Rectangle();
Rectangle updateRect = new Rectangle();
// Private class variables.
private Point currentPoint = new Point();
// The maximum and minimum values of the coordinates.
private int xmin, xmax;
private int ymin, ymax;
// Flag to indicate whether or not the crosshair cursor is
// visible.
private boolean cursorVisible;
// This is a custom system cursor which is invisible. Used when a
// drag is active to avoid the system cursor from obscuring the
// crosshairs.
private static Cursor invisibleCursor;
// Temporary variable to save old cursor so that it can be
// restored after the cursor drag has been completed.
private Cursor savedCursor;
/**
* Construct a PointSelectionPanel. Initially the cursor is not
* visible. */
public PointSelectionPanel() {
cursorVisible = false;
setSelectionActionsEnabled(false);
BufferedImage cursorImage =
new BufferedImage(16,16,BufferedImage.TYPE_INT_ARGB);
Graphics g = cursorImage.getGraphics();
g.setColor(new Color(0,0,0,0));
g.fillRect(0,0,16,16);
Toolkit toolKit = Toolkit.getDefaultToolkit();
invisibleCursor = toolKit.createCustomCursor(cursorImage,
new Point(0,0),
"InvisibleCursor");
setCursor(FreeHepImage.getCursor("PointCursor"));
}
/**
* Process key-released events. This allow the selection panel to
* move the cursor with the keyboard arrows.
*
* <ul>
* <li>arrow keys: move the active control point in the specified
* direction.
* <li>backspace key: reset selection region (make invisible).
* <li>delete key: reset selection region (make invisible).
* <li>escape key: leave selection mode (make component invisible).
* <li>tab key: next selection mode (next component made visible).
* <li>enter key: accept selection region (send off region
* selected event)
* <li>spacebar: accept selection region (send off region
* selected event)
* </ul>
* Normally, the arrow keys move the cursor by 2 pixels per key
* release; however if the shift key is pressed simultaneously,
* then the arrow keys move the cursor by 1 pixel.
*
* @param e KeyEvent describing the key which has been released */
public void keyReleased(KeyEvent e) {
// Change the size of the increment in the given direction.
int increment = (e.isShiftDown()) ? 1 : 2;
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
updatePosition(currentPoint.x,currentPoint.y-increment);
break;
case KeyEvent.VK_DOWN:
updatePosition(currentPoint.x,currentPoint.y+increment);
break;
case KeyEvent.VK_RIGHT:
updatePosition(currentPoint.x+increment,currentPoint.y);
break;
case KeyEvent.VK_LEFT:
updatePosition(currentPoint.x-increment,currentPoint.y);
break;
default:
super.keyReleased(e);
break;
}
}
/**
* The crosshair cursor will begin to follow the system pointer;
* the system pointer is made invisible.
*
* @param e the mouse pressed event to which to respond */
public void mousePressed(MouseEvent e) {
if (!isProcessingPopup(e)) {
cursorVisible = true;
setSelectionActionsEnabled(true);
savedCursor = getCursor();
setCursor(invisibleCursor);
updatePosition(e.getX(),e.getY());
}
}
/**
* The crosshair cursor will stop following the system pointer
* when the mouse is released. The system pointer will again be
* visible.
*
* @param e the mouse release event to which to respond */
public void mouseReleased(MouseEvent e) {
if (!isProcessingPopup(e)) {
setCursor(savedCursor);
updatePosition(e.getX(),e.getY());
}
}
/**
* The crosshair cursor follows the system pointer during a mouse
* drag event.
*
* @param e the mouse drag event to which to respond */
public void mouseDragged(MouseEvent e) {
if (!isProcessingPopup(e)) {
updatePosition(e.getX(),e.getY());
}
}
/**
* This method updates the position of the crosshair cursor.
* Normally this is called internally; however, it can be called
* programatically by other object, for example, to move the
* cursor on top of a selected object.
*
* @param x the x coordinate for the cursor (in pixels in the
* PointSelectionPanel's coordinate system)
* @param y the y coordinate for the cursor (in pixels in the
* PointSelectionPanel's coordinate system) */
public void updatePosition(int x, int y) {
if (cursorVisible) {
// Get the current boundries.
xmin = 1;
xmax = getWidth()-1;
ymin = 1;
ymax = getHeight()-1;
// Bring the location within bounds.
x = Math.max(Math.min(x,xmax),xmin);
y = Math.max(Math.min(y,ymax),ymin);
currentPoint.setLocation(x,y);
// Update the current bounds to be the new position.
int xLow = currentPoint.x-cursorSize;
int yLow = currentPoint.y-cursorSize;
// Repaint the new bounds.
updateRect.setBounds(lastDrawnRect);
updateRect = SwingUtilities.computeUnion(xLow,
yLow,
2*cursorSize,
2*cursorSize,
updateRect);
repaint(updateRect);
}
}
/**
* Set the visiblility of the crosshair cursor.
*
* @param visible boolean indicating whether or not the crosshair
* cursor should be visible */
public void setCursorVisible(boolean visible) {
if (cursorVisible!=visible) {
cursorVisible = visible;
setSelectionActionsEnabled(cursorVisible);
repaint();
}
}
/**
* Get the visibility of the crosshair cursor.
*
* @return boolean indicating whether or not the crosshair cursor
* is visible */
public boolean getCursorVisible() {
return cursorVisible;
}
/**
* Reset the selection; this remove the crosshair cursor from the
* screen. */
public void resetSelection() {
cursorVisible = false;
updateRect.setBounds(lastDrawnRect);
setSelectionActionsEnabled(false);
repaint(updateRect);
}
public void paintComponent(Graphics g) {
// Allow parent to draw any custom painting.
super.paintComponent(g);
if (cursorVisible) {
// Make a 2D graphics context.
Graphics2D g2d = (Graphics2D) g;
// Get the limits.
int xLow = currentPoint.x-cursorSize;
int xHigh = currentPoint.x+cursorSize;
int yLow = currentPoint.y-cursorSize;
int yHigh = currentPoint.y+cursorSize;
// Paint the crosshairs.
g2d.setStroke(thickStroke);
g.setColor(Color.black);
g.drawLine(currentPoint.x, yLow, currentPoint.x, yHigh);
g.drawLine(xLow, currentPoint.y, xHigh, currentPoint.y);
g2d.setStroke(thinStroke);
g.setColor(Color.white);
g.drawLine(currentPoint.x, yLow, currentPoint.x, yHigh);
g.drawLine(xLow, currentPoint.y, xHigh, currentPoint.y);
// Update the last drawn rectangle.
lastDrawnRect.setRect(xLow-1,yLow-1,2*cursorSize+3,2*cursorSize+3);
}
}
/**
* Make the affine transform which will center the display on the
* current point if applied.
*
* @return AffineTransform will center the current point */
public AffineTransform makeAffineTransform() {
return new AffineTransform(1.,0.,0.,1.,
getWidth()/2.-currentPoint.x,
getHeight()/2.-currentPoint.y);
}
/**
* A utility function which creates an appropriate selection event
* when the user accepts the current selection. */
protected void makeSelectionEvent(int actionCode) {
switch (actionCode) {
case GraphicalSelectionEvent.DEFAULT_MODE:
resetSelection();
setVisible(false);
fireGraphicalSelectionMade(new
GraphicalSelectionEvent(this,
GraphicalSelectionEvent.DEFAULT_MODE,
null,null));
break;
case GraphicalSelectionEvent.NEXT_MODE:
resetSelection();
setVisible(false);
fireGraphicalSelectionMade(new
GraphicalSelectionEvent(this,
GraphicalSelectionEvent.NEXT_MODE,
null,null));
break;
case GraphicalSelectionEvent.PREVIOUS_MODE:
resetSelection();
setVisible(false);
fireGraphicalSelectionMade(new
GraphicalSelectionEvent(this,
GraphicalSelectionEvent.PREVIOUS_MODE,
null,null));
break;
case GraphicalSelectionEvent.ZOOM:
case GraphicalSelectionEvent.ZOOM_NEW_VIEW:
// For all of the zooming just center the selected point in the
// view.
if (cursorVisible) {
fireGraphicalSelectionMade(new
PointSelectionEvent(this,actionCode,
currentPoint,
makeAffineTransform()));
}
resetSelection();
break;
case GraphicalSelectionEvent.PICK:
case GraphicalSelectionEvent.PICK_ADD:
case GraphicalSelectionEvent.UNPICK:
// For all of the picking modify the transform to center the
// selected point on the view, but also to zoom into the n x n
// region around this point.
if (cursorVisible) {
// Get the centering transform.
AffineTransform trans = makeAffineTransform();
// Now get the transform which maintains the center of the
// view but scales so that +-size pixels fill the view.
double size = 20.;
double halfWidth = getWidth()/2.;
double sx = halfWidth/size;
double halfHeight = getHeight()/2.;
double sy = halfHeight/size;
AffineTransform scaling =
new AffineTransform(sx,0.,0.,sy,
halfWidth*(1.-sx),
halfHeight*(1.-sy));
trans.preConcatenate(scaling);
fireGraphicalSelectionMade(new
PointSelectionEvent(this,actionCode,currentPoint,trans));
}
resetSelection();
break;
}
}
}