/*
*
* Copyright (C) SISDEF Ltda. All rights reserved.
*
* Created on 25-feb-2005
*/
package com.bbn.openmap.event;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import javax.swing.ImageIcon;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.MoreMath;
import com.bbn.openmap.image.ImageScaler;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMGraphicConstants;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.proj.Cartesian;
import com.bbn.openmap.proj.Cylindrical;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.PropUtils;
/**
* PanMouseMode it is a class for Pan operation on the visible map. This class
* show actual map in transparent mode. 25-feb-2005. There are a couple of
* properties that can be set in this mouse mode:
*
* <pre>
* # Floating number between 0-1, with 1 being opaque, default .5
* panmm.opaqueness=.5f
* # True/false, to leave old map up behind panned version.
* panmm.leaveShadow=true
* </pre>
*
* @author cursor
* @author Stephane Wasserhardt
*/
public class PanMouseMode extends CoordMouseMode implements ProjectionListener {
public final static String OPAQUENESS_PROPERTY = "opaqueness";
public final static String LEAVE_SHADOW_PROPERTY = "leaveShadow";
public final static String USE_CURSOR_PROPERTY = "useCursor";
public final static String AZ_PANNING_SHAPEFILE_PROPERTY = "azPanningShapefile";
public final static String AZ_PANNING_PROPERTY = "azPanning";
public final static float DEFAULT_OPAQUENESS = 0.5f;
public final static transient String modeID = "Pan";
private boolean isPanning = false;
private BufferedImage bufferedMapImage = null;
private OMRaster paintedImage = null;
private int beanBufferWidth = 0;
private int beanBufferHeight = 0;
private int oX, oY;
private MouseEvent lastMouseEvent;
private float opaqueness;
private boolean leaveShadow;
private boolean useCursor;
private AzimuthPanner azPanner = null;
private String azPanningShapefile = null;
private DrawingAttributes azDrawing = null;
public PanMouseMode() {
super(modeID, true);
setUseCursor(true);
setLeaveShadow(false);
setOpaqueness(DEFAULT_OPAQUENESS);
DrawingAttributes da = DrawingAttributes.getDefaultClone();
da.setMatted(true);
da.setMattingPaint(Color.LIGHT_GRAY);
setAzDrawing(da);
}
public void setActive(boolean val) {
if (!val) {
if (bufferedMapImage != null) {
bufferedMapImage.flush();
}
beanBufferWidth = 0;
beanBufferHeight = 0;
bufferedMapImage = null;
}
}
/**
* @return Returns the useCursor.
*/
public boolean isUseCursor() {
return useCursor;
}
/**
* @param useCursor The useCursor to set.
*/
public void setUseCursor(boolean useCursor) {
this.useCursor = useCursor;
if (useCursor) {
/*
* For who like make his CustomCursor
*/
try {
Toolkit tk = Toolkit.getDefaultToolkit();
ImageIcon pointer = new ImageIcon(getClass().getResource("pan.gif"));
Dimension bestSize = tk.getBestCursorSize(pointer.getIconWidth(), pointer.getIconHeight());
Image pointerImage = ImageScaler.getOptimalScalingImage(pointer.getImage(), (int) bestSize.getWidth(), (int) bestSize.getHeight());
Cursor cursor = tk.createCustomCursor(pointerImage, new Point(0, 0), "PP");
setModeCursor(cursor);
return;
} catch (Exception e) {
// Problem finding image probably, just move on.
}
}
setModeCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
public void setProperties(String prefix, Properties props) {
super.setProperties(prefix, props);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
opaqueness = PropUtils.floatFromProperties(props, prefix + OPAQUENESS_PROPERTY, opaqueness);
leaveShadow = PropUtils.booleanFromProperties(props, prefix + LEAVE_SHADOW_PROPERTY, leaveShadow);
azPanningShapefile = props.getProperty(prefix + AZ_PANNING_SHAPEFILE_PROPERTY, azPanningShapefile);
setUseCursor(PropUtils.booleanFromProperties(props, prefix + USE_CURSOR_PROPERTY, isUseCursor()));
azDrawing.setProperties(prefix + AZ_PANNING_PROPERTY, props);
}
public Properties getProperties(Properties props) {
props = super.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(this);
props.put(prefix + OPAQUENESS_PROPERTY, Float.toString(getOpaqueness()));
props.put(prefix + LEAVE_SHADOW_PROPERTY, Boolean.toString(isLeaveShadow()));
props.put(prefix + USE_CURSOR_PROPERTY, Boolean.toString(isUseCursor()));
props.put(prefix + AZ_PANNING_SHAPEFILE_PROPERTY, PropUtils.unnull(azPanningShapefile));
azDrawing.getProperties(props);
return props;
}
public Properties getPropertyInfo(Properties props) {
props = super.getPropertyInfo(props);
PropUtils.setI18NPropertyInfo(i18n, props, PanMouseMode.class, OPAQUENESS_PROPERTY, "Transparency", "Transparency level for moving map, between 0 (clear) and 1 (opaque).", null);
PropUtils.setI18NPropertyInfo(i18n, props, PanMouseMode.class, LEAVE_SHADOW_PROPERTY, "Leave Shadow", "Display current map in background while panning.", "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
PropUtils.setI18NPropertyInfo(i18n, props, PanMouseMode.class, USE_CURSOR_PROPERTY, "Use Cursor", "Use hand cursor for mouse mode.", "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor");
PropUtils.setI18NPropertyInfo(i18n, props, OMMouseMode.class, AZ_PANNING_SHAPEFILE_PROPERTY, "Az Projection Panning Shapefile", "Use a shapefile for azimuthal projection panning.", "com.bbn.openmap.util.propertyEditor.FUPropertyEditor");
azDrawing.getPropertyInfo(props);
return props;
}
/**
* PaintListener method.
*
* @param source the source object, most likely the MapBean
* @param g java.awt.Graphics
*/
public void listenerPaint(Object source, Graphics g) {
MapBean mapBean = source instanceof MapBean ? (MapBean) source : null;
if (azPanner != null) {
azPanner.render(g);
} else if (mapBean != null) {
if (isPanning && lastMouseEvent != null && bufferedMapImage != null) {
/**
* TODO: This doesn't work for rotated images, can't quite get the
* buffered image to render in the right location and not be
* rotated as well. So for now, if rotated, handle as if for
* azimuth. - DFD
*/
Graphics2D gr2d = (Graphics2D) g.create();
Projection proj = mapBean.getRotatedProjection();
if (!leaveShadow) {
gr2d.setPaint(mapBean.getBckgrnd());
// Takes care of rotated dimensions, too.
gr2d.fillRect(0, 0, proj.getWidth(), proj.getHeight());
}
Point2D pnt0 = proj.forward(mapBean.inverse(oX, oY, null));
int startX = (int) pnt0.getX();
int startY = (int) pnt0.getY();
Point2D pnt = mapBean.getNonRotatedLocation(lastMouseEvent);
int x = (int) pnt.getX();
int y = (int) pnt.getY();
/*
* Drawing image with transparency and in the mouse position
* minus original mouse click position
*/
gr2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opaqueness));
paintedImage.setX(x - startX);
paintedImage.setY(y - startY);
paintedImage.setRotationAngle(-proj.getRotationAngle());
paintedImage.generate(proj);
paintedImage.render(gr2d);
} else {
mapBean.removePaintListener(this);
}
}
}
public void mousePressed(MouseEvent arg0) {
MapBean mapBean = arg0.getSource() instanceof MapBean ? (MapBean) arg0.getSource() : null;
if (mapBean != null) {
lastMouseEvent = arg0;
oX = (int) arg0.getX();
oY = (int) arg0.getY();
// If the map is rotated, the size of the projection will be bigger
// than
// the size of the MapBean.
Projection proj = mapBean.getRotatedProjection();
int w = proj.getWidth();
int h = proj.getHeight();
if ((proj instanceof Cylindrical || proj instanceof Cartesian)
&& proj.getRotationAngle() == 0.0) {
if (bufferedMapImage == null) {
createBuffer(w, h);
}
Graphics2D g = (Graphics2D) bufferedMapImage.getGraphics();
mapBean.paintChildren(g, null);
Point2D ul = mapBean.inverse(0.0, 0.0, null);
paintedImage = new OMRaster(MoreMath.latJLT90(ul.getY()), ul.getX(), 0, 0, bufferedMapImage);
paintedImage.putAttribute(OMGraphicConstants.NO_ROTATE, Boolean.TRUE);
} else {
URL url = null;
try {
url = PropUtils.getResourceOrFileOrURL(azPanningShapefile);
} catch (MalformedURLException murle) {
}
if (url != null) {
azPanner = new AzimuthPanner.Shapefile(oX, oY, getAzDrawing(), url);
} else {
azPanner = new AzimuthPanner.Standard(oX, oY, getAzDrawing());
}
}
isPanning = true;
mapBean.addPaintListener(this);
}
}
/**
* @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
* The first click for drag, the image is generated. This image is
* redrawing when the mouse is move, but, I need to repaint the
* original image.
*/
public void mouseDragged(MouseEvent arg0) {
MapBean mapBean = arg0.getSource() instanceof MapBean ? (MapBean) arg0.getSource() : null;
lastMouseEvent = arg0;
if (mapBean != null) {
if (azPanner != null) {
azPanner.handlePan(mapBean, arg0);
}
mapBean.repaint();
}
super.mouseDragged(arg0);
}
/**
* @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
* Make Pan event for the map.
*/
public void mouseReleased(MouseEvent arg0) {
MapBean mapBean = arg0.getSource() instanceof MapBean ? (MapBean) arg0.getSource() : null;
if (azPanner != null) {
azPanner.handleUnpan(arg0);
azPanner = null;
}
if (isPanning && mapBean != null) {
Projection proj = mapBean.getProjection();
Point2D center = proj.forward(proj.getCenter());
int x = (int) arg0.getX();
int y = (int) arg0.getY();
center.setLocation(center.getX() - x + oX, center.getY() - y + oY);
isPanning = false; // needs to be here too so paintlistener doesn't
// get triggered
mapBean.setCenter(mapBean.inverse(center.getX(), center.getY(), null));
}
oX = 0;
oY = 0;
isPanning = false;
super.mouseReleased(arg0);
}
public boolean isLeaveShadow() {
return leaveShadow;
}
public void setLeaveShadow(boolean leaveShadow) {
this.leaveShadow = leaveShadow;
}
public float getOpaqueness() {
return opaqueness;
}
public void setOpaqueness(float opaqueness) {
this.opaqueness = opaqueness;
}
public boolean isPanning() {
return isPanning;
}
public int getOX() {
return oX;
}
public int getOY() {
return oY;
}
/**
* @return the azPanningShapefile
*/
public String getAzPanningShapefile() {
return azPanningShapefile;
}
/**
* @param azPanningShapefile the azPanningShapefile to set
*/
public void setAzPanningShapefile(String azPanningShapefile) {
this.azPanningShapefile = azPanningShapefile;
}
/**
* @return the azDrawing
*/
public DrawingAttributes getAzDrawing() {
return azDrawing;
}
/**
* @param azDrawing the azDrawing to set
*/
public void setAzDrawing(DrawingAttributes azDrawing) {
this.azDrawing = azDrawing;
}
public void projectionChanged(ProjectionEvent e) {
Object obj = e.getSource();
if (obj instanceof MapBean) {
MapBean mb = (MapBean) obj;
int w = mb.getWidth();
int h = mb.getHeight();
createBuffer(w, h);
}
}
/**
* Instantiates new image buffers if needed.<br>
* This method is synchronized to avoid creating the images multiple times
* if width and height doesn't change.
*
* @param w mapBean's width.
* @param h mapBean's height.
*/
public synchronized void createBuffer(int w, int h) {
if (w > 0 && h > 0 && (w != beanBufferWidth || h != beanBufferHeight)) {
beanBufferWidth = w;
beanBufferHeight = h;
createBufferImpl(w, h);
}
}
/**
* Instantiates new image buffers.
*
* @param w Non-zero mapBean's width.
* @param h Non-zero mapBean's height.
*/
protected void createBufferImpl(int w, int h) {
// Release system resources used by previous images...
if (bufferedMapImage != null) {
bufferedMapImage.flush();
}
// New image...
bufferedMapImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
}