// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/event/NavMouseMode.java,v $ // $RCSfile: NavMouseMode.java,v $ // $Revision: 1.12 $ // $Date: 2007/02/12 17:36:26 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.event; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Properties; import com.bbn.openmap.MapBean; import com.bbn.openmap.omGraphics.DrawingAttributes; import com.bbn.openmap.proj.Proj; import com.bbn.openmap.proj.ProjMath; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.Debug; /** * The Navigation Mouse Mode interprets mouse clicks and mouse drags to recenter * and rescale the map. The map is centered on the location where a click * occurs. If a box is drawn by clicking down and dragging the mouse, the map is * centered on the dot in the center of the box, and the scale is adjusted so * the screen fills the area designated by the box. * <p> * You MUST add this MouseMode as a ProjectionListener to the MapBean to get it * to work. If you use a MouseDelegator with the bean, it will take care of that * for you. */ public class NavMouseMode extends CoordMouseMode { /** * Mouse Mode identifier, which is "Navigation". */ public final static transient String modeID = "Navigation"; protected Point point1, point2; protected boolean autoZoom = false; MapBean theMap = null; /** * DrawingAttributes to use for drawn rectangle. Fill paint will be used for * XOR color, line paint will be used for paint color. */ protected DrawingAttributes rectAttributes = DrawingAttributes.getDefaultClone(); /** * Construct a NavMouseMode. Sets the ID of the mode to the modeID, the * consume mode to true, and the cursor to the crosshair. */ public NavMouseMode() { this(true); rectAttributes.setLinePaint(Color.GRAY); rectAttributes.setMattingPaint(Color.LIGHT_GRAY); rectAttributes.setMatted(true); } /** * Construct a NavMouseMode. Lets you set the consume mode. If the events * are consumed, then a MouseEvent is sent only to the first * MapMouseListener that successfully processes the event. If they are not * consumed, then all of the listeners get a chance to act on the event. * * @param shouldConsumeEvents the mode setting. */ public NavMouseMode(boolean shouldConsumeEvents) { super(modeID, shouldConsumeEvents); // override the default cursor setModeCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); rectAttributes.setLinePaint(Color.GRAY); rectAttributes.setMattingPaint(Color.LIGHT_GRAY); rectAttributes.setMatted(true); } /** * Handle a mousePressed MouseListener event. Erases the old navigation * rectangle if there is one, and then keeps the press point for reference * later. * * @param e MouseEvent to be handled */ public void mousePressed(MouseEvent e) { if (Debug.debugging("mousemode")) { Debug.output(getID() + "|NavMouseMode.mousePressed()"); } e.getComponent().requestFocus(); if (!mouseSupport.fireMapMousePressed(e) && e.getSource() instanceof MapBean) { // set the new first point point1 = e.getPoint(); // ensure the second point isn't set. point2 = null; autoZoom = true; } } public void mouseClicked(MouseEvent e) { Object obj = e.getSource(); super.mouseClicked(e); if (!(obj instanceof MapBean) || point1 == null) { return; } MapBean map = (MapBean) obj; Projection projection = map.getProjection(); Proj p = (Proj) projection; Point2D llp = map.getCoordinates(e); boolean shift = e.isShiftDown(); boolean control = e.isControlDown(); if (control) { if (shift) { p.setScale(p.getScale() * 2.0f); } else { p.setScale(p.getScale() / 2.0f); } } cleanUp(); p.setCenter(llp); map.setProjection(p); } public void mouseMoved(MouseEvent e) { super.mouseMoved(e); if (theMap != null) { cleanUp(); } } /** * Handle a mouseReleased MouseListener event. If there was no drag events, * or if there was only a small amount of dragging between the occurrence of * the mousePressed and this event, then recenter the map. Otherwise we get * the second corner of the navigation rectangle and try to figure out the * best scale and location to zoom in to based on that rectangle. * * @param e MouseEvent to be handled */ public void mouseReleased(MouseEvent e) { if (Debug.debugging("mousemode")) { Debug.output(getID() + "|NavMouseMode.mouseReleased()"); } if (!mouseSupport.fireMapMouseReleased(e)) { handleMouseReleased(e); } } /** * Override this method to change what happens when the mouse is released. * * @param e MouseEvent */ protected void handleMouseReleased(MouseEvent e) { Object obj = e.getSource(); MapBean map = (MapBean) theMap; Point firstPoint = this.point1; Point secondPoint = this.point2; // point2 is always going to be null for a click. if (!(obj == map) || !autoZoom || firstPoint == null || secondPoint == null) { return; } Projection projection = map.getProjection(); Proj p = (Proj) projection; synchronized (this) { point2 = getRatioPoint((MapBean) e.getSource(), firstPoint, e.getPoint()); secondPoint = point2; int dx = Math.abs(secondPoint.x - firstPoint.x); int dy = Math.abs(secondPoint.y - firstPoint.y); // Don't bother redrawing if the rectangle is too small if ((dx < 5) || (dy < 5)) { // If rectangle is too small in both x and y then // recenter the map if ((dx < 5) && (dy < 5)) { Point2D llp = map.getCoordinates(e); boolean shift = e.isShiftDown(); boolean control = e.isControlDown(); if (control) { if (shift) { p.setScale(p.getScale() * 2.0f); } else { p.setScale(p.getScale() / 2.0f); } } cleanUp(); p.setCenter(llp); map.setProjection(p); } else { cleanUp(); map.repaint(); } return; } // Figure out the new scale float newScale = com.bbn.openmap.proj.ProjMath.getScale(firstPoint, secondPoint, projection); // Figure out the center of the rectangle int centerx = Math.min(firstPoint.x, secondPoint.x) + dx / 2; int centery = Math.min(firstPoint.y, secondPoint.y) + dy / 2; Point2D center = map.inverse(centerx, centery, null); // Fire events on main map to change view to match rect1 // Debug.output("point1: " +point1); // Debug.output("point2: " +point2); // Debug.output("Centerx: " +centerx + // " Centery: " + centery); // Debug.output("New Scale: " + newScale); // Debug.output("New Center: " +center); // Set the parameters of the projection and then set // the projection of the map. This way we save having // the MapBean fire two ProjectionEvents. p.setScale(newScale); p.setCenter(center); cleanUp(); map.setProjection(p); } } /** * Handle a mouseEntered MouseListener event. The boolean autoZoom is set to * true, which will make the delegate ask the map to zoom in to a box that * is drawn. * * @param e MouseEvent to be handled */ public void mouseEntered(MouseEvent e) { if (Debug.debugging("mousemodedetail")) { Debug.output(getID() + "|NavMouseMode.mouseEntered()"); } super.mouseEntered(e); autoZoom = true; } /** * Handle a mouseExited MouseListener event. The boolean autoZoom is set to * false, which will cause the delegate to NOT ask the map to zoom in on a * box. If a box is being drawn, it will be erased. The point1 is kept in * case the mouse comes back on the screen with the button still down. Then, * a new box will be drawn with the original mouse press position. * * @param e MouseEvent to be handled */ public void mouseExited(MouseEvent e) { if (Debug.debugging("mousemodedetail")) { Debug.output(getID() + "|NavMouseMode.mouseExited()"); } super.mouseExited(e); if (theMap == e.getSource()) { // don't zoom in, because the mouse is off the window. autoZoom = false; // set the second point to null so that a new box will be // drawn if the mouse comes back, and the box will use the // old starting point, if the mouse button is still down. point2 = null; theMap.repaint(); } } // Mouse Motion Listener events // ///////////////////////////// /** * Handle a mouseDragged MouseMotionListener event. A rectangle is drawn * from the mousePressed point, since I'm assuming that I'm drawing a box to * zoom the map to. If a previous box was drawn, it is erased. * * @param e MouseEvent to be handled */ public void mouseDragged(MouseEvent e) { if (Debug.debugging("mousemodedetail")) { Debug.output(getID() + "|NavMouseMode.mouseDragged()"); } super.mouseDragged(e); Object obj = e.getSource(); if (obj instanceof MapBean && theMap == null) { theMap = (MapBean) obj; theMap.addPaintListener(this); } MapBean map = this.theMap; Point firstPoint = this.point1; if (map != null) { if (!autoZoom) { return; } point2 = getRatioPoint(map, firstPoint, e.getPoint()); map.repaint(); } } protected void cleanUp() { if (theMap != null) { theMap.removePaintListener(this); theMap = null; } point1 = null; point2 = null; } /** * Given a MapBean, which provides the projection, and the starting point of * a box (pt1), look at pt2 to see if it represents the ratio of the * projection map size. If it doesn't, provide a point that does. This * method signature is provided for backwards compatibility. */ protected Point getRatioPoint(MapBean map, Point pt1, Point pt2) { return ProjMath.getRatioPoint(map.getProjection(), pt1, pt2); } /** * Draws or erases boxes between two screen pixel points. The graphics from * the map is set to XOR mode, and this method uses two colors to make the * box disappear if on has been drawn at these coordinates, and the box to * appear if it hasn't. * * @param pt1 one corner of the box to drawn, in window pixel coordinates. * @param pt2 the opposite corner of the box. */ protected void paintRectangle(Graphics g, Point pt1, Point pt2) { if (pt1 != null && pt2 != null) { int width = Math.abs(pt2.x - pt1.x); int height = Math.abs(pt2.y - pt1.y); if (width == 0) { width++; } if (height == 0) { height++; } Rectangle2D rect1 = new Rectangle2D.Double(pt1.x < pt2.x ? pt1.x : pt2.x, pt1.y < pt2.y ? pt1.y : pt2.y, width, height); Rectangle2D rect2 = new Rectangle2D.Double(pt1.x < pt2.x ? pt1.x + (pt2.x - pt1.x) / 2 - 1 : pt2.x + (pt1.x - pt2.x) / 2 - 1, pt1.y < pt2.y ? pt1.y + (pt2.y - pt1.y) / 2 - 1 : pt2.y + (pt1.y - pt2.y) / 2 - 1, 2, 2); if (theMap != null) { rectAttributes.render((Graphics2D) g, theMap.getNonRotatedShape(rect1)); rectAttributes.render((Graphics2D) g, theMap.getNonRotatedShape(rect2)); } } } /** * Called by the MapBean when it repaints, to let the MouseMode know when to * update itself on the map. PaintListener interface. */ public void listenerPaint(Object obj, java.awt.Graphics g) { if (theMap == null && obj instanceof MapBean) { ((MapBean) obj).removePaintListener(this); return; } Graphics2D graphics = (Graphics2D) g.create(); if (point1 != null && point2 != null) { paintRectangle(graphics, point1, point2); } graphics.dispose(); } public DrawingAttributes getRectAttributes() { return rectAttributes; } public void setRectAttributes(DrawingAttributes rectAttributes) { this.rectAttributes = rectAttributes; } /** * PropertyConsumer interface method. */ public void setProperties(String prefix, Properties setList) { super.setProperties(prefix, setList); rectAttributes.setProperties(prefix, setList); } /** * PropertyConsumer interface method. */ public Properties getProperties(Properties getList) { return rectAttributes.getProperties(getList); } /** * PropertyConsumer interface method. */ public Properties getPropertyInfo(Properties list) { list = super.getPropertyInfo(list); rectAttributes.getPropertyInfo(list); list.put(initPropertiesProperty, DrawingAttributes.linePaintProperty + " " + DrawingAttributes.mattingPaintProperty + " " + DrawingAttributes.mattedProperty); return list; } }