/*
* Copyright (C) 2014 Alec Dhuse
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.map;
import co.foldingmap.map.vector.CoordinateList;
import co.foldingmap.map.vector.CoordinateMath;
import co.foldingmap.map.vector.VectorObject;
import co.foldingmap.map.vector.VectorObjectList;
import co.foldingmap.map.vector.Coordinate;
import co.foldingmap.map.vector.MapPoint;
import co.foldingmap.GUISupport.Updateable;
import co.foldingmap.GUISupport.htmlRendering.HtmlRenderer;
import co.foldingmap.Logger;
import co.foldingmap.ResourceHelper;
import co.foldingmap.actions.Actions;
import co.foldingmap.map.themes.MapTheme;
import co.foldingmap.map.visualization.TimeSpanControl;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Popup;
/**
* This class extends JPanel and handles the displaying of map,
* represented by the DigitalMap Class.
*
* @author Alec
*/
public class MapPanel extends JPanel implements KeyListener,
MapAccepter,
MouseListener,
MouseMotionListener,
MouseWheelListener,
Updateable {
//Map Modes for how the map acts and is displayed.
public static final int DRAG_PAN = 0;
public static final int DRAG_SELECT = 1;
public static final int MODIFY = 2;
public static final int TRACE = 3;
public static final int SINGLE_CLICK_WIDTH = 8;
private Actions actions;
private ArrayList<Shape> points;
private ArrayList<Updateable> updateables;
private BasicStroke dashedStroke1, dashedStroke2;
private boolean allowShiftZoom, controlPressed, shiftPressed;
private boolean showObjectPopup, showScale, showSelectRectangle, showZoomControls;
private boolean coordinateDrag, dragging, moveSelectedPoint, panelLocked;
private boolean zoomControlClicked;
private Coordinate coordinateToModify, coordinateToModifyOriginal;
private CoordinateList<Coordinate> traceCoordinates;
private DigitalMap mapData;
private Ellipse2D ellipseZoomIn, ellipseZoomOut;
private float currentX, currentY;
private float xDragStart, yDragStart;
private float scaleX, scaleY;
private float xOrigin, yOrigin;
private ImageIcon iconPlus, iconMinus;
private int dragMode, previousDragMode;
private MapObjectList<MapObject> traceMergeObjects;
private MapView currentMapView;
private Path2D tracePath;
private Popup objectPopup;
private Rectangle2D rectangleSelection;
private RenderingHints renderAntialiasing;
private ResourceHelper helper;
private TimeSpanControl timeSpanControl;
/**
* Constructor for objects of class MapPanel
*/
public MapPanel() {
init();
}
/**
* Add an Updateable to this panel. These are called when certain events
* happen in the panel.
*
* @param u
*/
public void addUpdateable(Updateable u) {
this.updateables.add(u);
}
private void clearTracePath() {
traceCoordinates.clear();
traceMergeObjects.clear();
}
/**
* Creates the visual trace path used when using the trace merge function.
*/
private void createTracePath() {
float x, y;
int index;
Shape point;
try {
points = new ArrayList<Shape>();
tracePath = new Path2D.Double();
index = -1;
for (Coordinate currentCoordinate: traceCoordinates) {
index++;
point = VectorObject.getPointShape(currentCoordinate, MapView.NO_WRAP);
x = currentMapView.getX(currentCoordinate, MapView.NO_WRAP);
y = currentMapView.getY(currentCoordinate);
if (index == 0) {
tracePath.moveTo(x, y);
} else {
tracePath.lineTo(x, y);
}
points.add(point);
}
} catch (Exception e) {
System.err.println("Error in MapPanel.createTracePath() - " + e);
}
}
/**
* Performs the trace-merge operation
*
* @param e
* @param xDragDifference
* @param yDragDifference
*/
private void doTrace() {
try {
mapData.deselectObjects();
mapData.getActions().traceMerge(mapData, new VectorObjectList(traceMergeObjects), traceCoordinates, updateables);
tracePath = null;
this.clearTracePath();
} catch (Exception e) {
System.err.println("Error in MapPanel.doTrace(MouseEvent, float, float) - " + e);
}
}
/**
* Moves the View Port and updates dragging information.
*
* @param e
* @param xDragDifference
* @param yDragDifference
*/
private void dragPanMap(MouseEvent e, double xDragDifference, double yDragDifference) {
xDragStart = e.getX();
yDragStart = e.getY();
scroll(xDragDifference, yDragDifference);
}
/**
* Draws the object popup for a single selected object.
*
* @param g2
*/
private void drawObjectPopup(Graphics2D g2) {
BasicStroke lineStroke;
boolean hasDescription;
float popupHeight, popupWidth, popupX, popupY;
FontMetrics fm;
HtmlRenderer htmlRenderer;
MapObject selectedObject;
MapObjectList<MapObject> selectedObjects;
Rectangle2D descBounds, titleBounds;
RoundRectangle2D popup;
String title;
try {
htmlRenderer = null;
lineStroke = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
selectedObjects = this.mapData.getSelectedObjects();
g2.setFont(g2.getFont().deriveFont(Font.BOLD));
fm = g2.getFontMetrics();
if (selectedObjects.size() == 1 && dragging == false) {
selectedObject = selectedObjects.get(dragMode);
title = (selectedObject.getName().length() > 0 ? selectedObject.getName() : "(Unnamed)");
//If the selected object is a MapPoint use the point to anchor and not the click pos
if (selectedObject instanceof MapPoint) {
Coordinate c = selectedObject.getCoordinateList().get(0);
popupX = (float) c.getCenterPoint().getX();
popupY = (float) c.getCenterPoint().getY() - 10;
} else {
popupX = currentX;
popupY = currentY;
}
if (selectedObject.getDescription() != null &&
selectedObject.getDescription().length() > 0) {
titleBounds = fm.getStringBounds(title, g2);
hasDescription = true;
popupHeight = 100;
popupWidth = 200;
popupX = popupX - (popupWidth / 2);
popupY = popupY - popupHeight - 10;
htmlRenderer = new HtmlRenderer(selectedObject.getDescription(), (popupX + 5), (popupY + 30), (popupHeight - 35), (popupWidth - 10));
descBounds = htmlRenderer.getBounds(g2);
popupHeight = (float) (descBounds.getHeight() + 30);
popupWidth = (float) (descBounds.getWidth() + 10);
popupWidth = (float) Math.max(popupWidth, titleBounds.getWidth() + 10);
} else {
hasDescription = false;
popupHeight = 22;
Rectangle2D r = fm.getStringBounds(title, g2);
popupWidth = (float) (r.getWidth() + 10);
popupX = popupX - (popupWidth / 2);
popupY = popupY - popupHeight;
}
popup = new RoundRectangle2D.Float(popupX, popupY, popupWidth, popupHeight, 6, 6);
g2.setColor(new Color(68,68,68,180));
g2.fill(popup);
g2.setColor(new Color(255,255,255,180));
g2.setStroke(lineStroke);
g2.draw(popup);
g2.setFont(g2.getFont().deriveFont(Font.BOLD));
g2.drawString(title, popupX + 5, popupY + 15);
if (hasDescription == true && htmlRenderer != null) {
htmlRenderer.draw(g2);
} //End Description Check
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error ing MapPanel.drawObjectPopup(Graphics2D) - " + e);
}
}
/**
* Draws the scale of the current view of the map .
*
* @param g2 - The graphics of the MapPanel
*/
private void drawScale(Graphics2D g2) {
BasicStroke lineStroke;
Coordinate c1, c2;
float lat, lon, scaleDistanceDouble;
float scaleDisplayX1, scaleDisplayX2, scaleDisplayY1, scaleDisplayY2;
int scaleDistance;
Rectangle2D scaleRectangle;
String scaleLabel;
//Set the left side of the scale
scaleDisplayX1 = 15;
scaleDisplayY1 = (this.getHeight() - 35);
lat = currentMapView.getLatitude(scaleDisplayX1, scaleDisplayY1);
lon = currentMapView.getLongitude(scaleDisplayX1, scaleDisplayY1);
c1 = new Coordinate(0, lat, lon, false);
scaleDistanceDouble = getPanelWidthInKm() / 10;
lineStroke = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
g2.setStroke(lineStroke);
if (scaleDistanceDouble < 1.0) {
scaleDistance = (int) ((scaleDistanceDouble) * 10);
scaleDistance = scaleDistance * 100;
if (scaleDistance <= 0) {
scaleDistance = (int) ((scaleDistanceDouble) * 100);
scaleDistance = scaleDistance * 10;
if (scaleDistance <= 0)
scaleDistance = (int) ((scaleDistanceDouble) * 1000);
}
scaleLabel = Integer.toString(scaleDistance) + " m";
scaleDistanceDouble = (scaleDistance / 1000.0f);
} else {
scaleDistance = (int) scaleDistanceDouble;
scaleLabel = Integer.toString(scaleDistance) + " km";
scaleDistanceDouble = scaleDistance;
}
c2 = c1.reckonCoordinate(scaleDistanceDouble, 90);
scaleDisplayX2 = (float) currentMapView.getMapProjection().getX(c2);
scaleDisplayY2 = currentMapView.getY(c2);
scaleRectangle = new Rectangle2D.Double(scaleDisplayX1, scaleDisplayY1, (scaleDisplayX2 - scaleDisplayX1), 5);
g2.setColor(Color.BLACK);
g2.draw(scaleRectangle);
g2.drawString(scaleLabel, (float) scaleDisplayX1 - 1, (float) scaleDisplayY1 + 18);
g2.drawString(scaleLabel, (float) scaleDisplayX1 + 1, (float) scaleDisplayY1 + 18);
g2.drawString(scaleLabel, (float) scaleDisplayX1 + 0, (float) scaleDisplayY1 + 19);
g2.drawString(scaleLabel, (float) scaleDisplayX1 + 0, (float) scaleDisplayY1 + 17);
g2.setColor(Color.WHITE);
g2.drawString(scaleLabel, (float) scaleDisplayX1, (float) scaleDisplayY1 + 18);
g2.setColor(new Color(68,68,68,180));
g2.fill(scaleRectangle);
}
/**
* Draws the zoom controls for the MapPanel
*
* @param g2 - The graphics used in this MapPanel
*/
private void drawZoomControls(Graphics2D g2) {
int zoomInX, zoomInY, zoomOutX, zoomOutY;
zoomInX = this.getWidth() - 30;
zoomInY = this.getHeight() - 35;
zoomOutX = zoomInX - 25;
zoomOutY = zoomInY;
ellipseZoomIn = new Ellipse2D.Float(zoomInX, zoomInY, 20, 20);
ellipseZoomOut = new Ellipse2D.Float(zoomOutX, zoomOutY, 20, 20);
g2.drawImage(iconPlus.getImage(), zoomInX, zoomInY, iconPlus.getImageObserver());
g2.drawImage(iconMinus.getImage(), zoomOutX, zoomOutY, iconPlus.getImageObserver());
}
/**
* Runs update on all of this object's updatables.
*/
private void fireUpdateables() {
try {
for (Updateable u: updateables) {
if (u != null)
u.update();
}
} catch (Exception e) {
System.err.println("Error in MapPanel.fireUpdateables() - " + e);
}
}
/**
* Returns the Coordinate that represents the current mouse location.
*
* @return The Coordinate of the current mouse location.
*/
public Coordinate getCoordinateAtMouseLocation() {
float lat, lon;
lat = currentMapView.getLatitude(currentX, currentY);
lon = currentMapView.getLongitude(currentX, currentY);
return new Coordinate(0, lat, lon);
}
/**
* Returns the drag mode being used by this panel.
*
* @return
*/
public int getDragMode() {
return this.dragMode;
}
/**
* Returns the DigitalMap being displayed in this panel.
*
* @return
*/
public DigitalMap getMap() {
return mapData;
}
/**
* Returns the MapView being used by this panel.
*
* @return
*/
public MapView getMapView() {
return this.currentMapView;
}
/**
* Returns the width of this MapPanel in Kilometers.
*
* @return
*/
public float getPanelWidthInKm() {
Coordinate c1, c2;
float widthInKm;
float panelLat1, panelLat2, panelLon1, panelLon2;
panelLat1 = currentMapView.getLatitude(0,0);
panelLat2 = currentMapView.getLatitude(this.getHeight(), this.getWidth());
panelLon1 = currentMapView.getLongitude(0,0);
panelLon2 = currentMapView.getLongitude(this.getHeight(), this.getWidth());
c1 = new Coordinate(0, panelLat1, panelLon1, false);
c2 = new Coordinate(0, panelLat2, panelLon2, false);
widthInKm = CoordinateMath.getDistance(c1, c2) / 1000.0f;
return widthInKm;
}
/**
* Hides the Object Popup.
*
*/
public void hideObjectPopup() {
if ((objectPopup != null) && showObjectPopup) {
showObjectPopup = false;
objectPopup.hide();
}
}
/**
* Initiate objects used by the panel.
*
*/
private void init() {
this.helper = ResourceHelper.getInstance();
this.allowShiftZoom = true;
this.mapData = new DigitalMap();
this.showScale = true;
this.showZoomControls = true;
this.traceMergeObjects = new MapObjectList<MapObject>();
this.traceCoordinates = new CoordinateList<Coordinate>();
this.updateables = new ArrayList<Updateable>();
this.xOrigin = 0;
this.yOrigin = 0;
this.dragMode = DRAG_PAN;
this.controlPressed = false;
this.panelLocked = false;
this.showSelectRectangle = false;
this.coordinateToModify = null;
this.mapData = new DigitalMap();
this.currentMapView = new MapView(new MercatorProjection(0, 0, 1));
this.renderAntialiasing = new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
this.renderAntialiasing.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
this.iconPlus = helper.getImage("add_button.png");
this.iconMinus = helper.getImage("minus_button.png");
float DASH1[] = { 5.0f };
dashedStroke1 = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 10.0f, DASH1, 0.0f);
dashedStroke2 = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 10.0f, DASH1, 5.0f);
addKeyListener(this);
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
}
@Override
public void keyPressed(KeyEvent ke) {
if ((ke.getKeyCode() == KeyEvent.VK_KP_DOWN) ||
(ke.getKeyCode() == KeyEvent.VK_DOWN)) {
this.scroll(0, -10);
} else if ((ke.getKeyCode() == KeyEvent.VK_KP_LEFT) ||
(ke.getKeyCode() == KeyEvent.VK_LEFT)) {
this.scroll(10, 0);
} else if ((ke.getKeyCode() == KeyEvent.VK_KP_RIGHT) ||
(ke.getKeyCode() == KeyEvent.VK_RIGHT)) {
this.scroll(-10, 0);
} else if ((ke.getKeyCode() == KeyEvent.VK_KP_UP) ||
(ke.getKeyCode() == KeyEvent.VK_UP)) {
this.scroll(0, 10);
} else if ((ke.getKeyCode() == KeyEvent.VK_ENTER)) {
if (dragMode == TRACE) {
this.doTrace();
}
} else if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) {
clearTracePath();
}
if (ke.getKeyCode() == KeyEvent.VK_CONTROL) {
this.controlPressed = true;
} else if (ke.getKeyCode() == 157) {
//apple command key
this.controlPressed = true;
// } else if (ke.getKeyCode() == KeyEvent.VK_M) {
// mapData.deselectObjects();
// this.previousDragMode = this.dragMode;
// this.dragMode = DRAG_SELECT;
// showSelectRectangle = true;
} else if (ke.getKeyCode() == KeyEvent.VK_SHIFT) {
//for shift zoom
this.shiftPressed = true;
}
}
@Override
public void keyReleased(KeyEvent ke) {
this.controlPressed = false;
this.shiftPressed = false;
}
@Override
public void keyTyped(KeyEvent ke) {}
@Override
public void mouseClicked(MouseEvent me) {
Rectangle2D selection;
currentX = me.getX();
currentY = me.getY();
currentMapView.setLastMouseClickPosition(currentX, currentY);
if (this.timeSpanControl != null && this.timeSpanControl.containsPoint(currentX, currentY)) {
this.timeSpanControl.mouseClicked(currentX, currentY);
timeSpanControl.addUpdateable(this);
} else {
if (!panelLocked) {
selection = new Rectangle2D.Float((me.getX() - (SINGLE_CLICK_WIDTH / 2.0f)),
(me.getY() - (SINGLE_CLICK_WIDTH / 2.0f)),
SINGLE_CLICK_WIDTH, SINGLE_CLICK_WIDTH);
if (me.getButton() == MouseEvent.BUTTON1) {
if (dragMode == DRAG_PAN || dragMode == MODIFY) {
if (!controlPressed)
mapData.deselectObjects();
if (zoomControlClicked == false) {
mapData.highlightObject(selection, controlPressed);
}
if ((dragMode == MODIFY) && (mapData.getSelectedObjects().size() > 0)) {
MapObjectList<MapObject> selectedObjects;
selectedObjects = (mapData.getSelectedObjects());
/** select the last element because that will be the newly selected VectorObject.
* This will allow multiple component coordinates to be selected. */
MapObject objectToModify = selectedObjects.lastElement();
Coordinate closeCoordinate = objectToModify.getCoordinateList().getCoordinateClosestTo(currentMapView.getLastMouseClickCoordinate());
objectToModify.setSelectedCoordinate(closeCoordinate);
}
}
//show popup
if (dragMode == DRAG_PAN) {
if (mapData.getSelectedObjects().size() == 1) {
showObjectPopup(me.getXOnScreen(), me.getYOnScreen());
}
}
if (dragMode == TRACE) {
boolean coordinateAdded = false;
for (Coordinate c: currentMapView.getViewPortCoordinates()) {
//Check to see if alreay existing Coordinates have been clicked.
if (Math.abs(c.getCenterPoint().x - currentX) < 3 &&
Math.abs(c.getCenterPoint().y - currentY) < 3) {
/* Check to see if the newly clicked point is the
* second to last added, if so remove it. */
if (traceCoordinates.size() > 2 &&
c.equals(traceCoordinates.get(traceCoordinates.size() - 2))) {
traceCoordinates.remove(traceCoordinates.size() - 1);
coordinateAdded = true;
} else {
traceCoordinates.forceAdd(c);
coordinateAdded = true;
//If the coordinate used has only one parent, add it to the trace merge objects
if (c.getParentVectorObjects().size() == 1)
traceMergeObjects.add(c.getParentVectorObjects().get(0));
//If the trace is closed, create a polygon.
if (traceCoordinates.size() > 1 &&
traceCoordinates.lastCoordinate().equals(traceCoordinates.get(0))) {
this.doTrace();
}
} //end check for undo
}
}
if (coordinateAdded == false) {
//Add new Coordiante
Coordinate c = getCoordinateAtMouseLocation();
currentMapView.getViewPortCoordinates().add(c);
mapData.getCoordinateSet().put(c);
traceCoordinates.forceAdd(c);
}
//mapData.highlightObject(selection, controlPressed);
}
} else if (me.getButton() == MouseEvent.BUTTON2) {
} else if (me.getButton() == MouseEvent.BUTTON3) {
if (dragMode == TRACE) {
//Finish the trace route
doTrace();
}
}
this.zoomControlClicked = false;
} //end panel locked check
}
this.repaint();
}
/**
* Handles the dragging events for the MapPanel.
*
* @param me
*/
@Override
public void mouseDragged(MouseEvent me) {
float xDragDifference, yDragDifference;
MapObject objectToModify;
MapObjectList<MapObject> selectedObjects;
try {
currentX = me.getX();
currentY = me.getY();
selectedObjects = (mapData.getSelectedObjects());
if (!panelLocked) {
if (scaleX == 0 && scaleY == 0) {
xDragDifference = (me.getX() - xDragStart);
yDragDifference = (me.getY() - yDragStart);
} else {
/* if using scaling we must adjust the drag by the
* inverse of the scale
*/
xDragDifference = (me.getX() - xDragStart) * (1.0f / scaleX);
yDragDifference = (me.getY() - yDragStart) * (1.0f / scaleY);
}
if (this.timeSpanControl != null && this.timeSpanControl.containsPoint(xDragStart, yDragStart)) {
this.timeSpanControl.mouseClicked(currentX, currentY);
} else {
if (dragMode == DRAG_PAN) {
if (this.shiftPressed == true) {
//shift zoom!
showSelectRectangle = true;
} else if ((this.controlPressed == true) &&
((xDragDifference > SINGLE_CLICK_WIDTH) ||
(yDragDifference > SINGLE_CLICK_WIDTH))) {
//change mode to Drag_Select
mapData.deselectObjects();
this.previousDragMode = DRAG_PAN;
this.dragMode = DRAG_SELECT;
showSelectRectangle = true;
} else {
//drag pan the map
xDragStart = me.getX();
yDragStart = me.getY();
scroll(xDragDifference, yDragDifference);
}
} else if (dragMode == DRAG_SELECT) {
showSelectRectangle = true;
} else if (dragMode == MODIFY) {
if (this.shiftPressed == true) {
//shift zoom!
showSelectRectangle = true;
} else if ((this.controlPressed == true) && (coordinateToModify == null)) {
// //change mode to Drag_Select
// mapData.deselectObjects();
// this.previousDragMode = MODIFY;
// this.dragMode = DRAG_SELECT;
// showSelectRectangle = true;
} else {
if (selectedObjects.size() > 0) {
objectToModify = mapData.getSelectedObjects().get(0);
coordinateToModify = objectToModify.getSelectedCoordinate();
} else {
coordinateToModify = null;
objectToModify = null;
}
if (coordinateToModify == null) {
//pan map
dragPanMap(me, xDragDifference, yDragDifference);
} else {
if (moveSelectedPoint) {
//move selected point
coordinateToModify.setLongitude(currentMapView.getLongitude(currentX, currentY));
coordinateToModify.setLatitude (currentMapView.getLatitude (currentX, currentY));
this.repaint();
} else {
//pan map
dragPanMap(me, xDragDifference, yDragDifference);
}
}
}
} else if (dragMode == TRACE) {
dragPanMap(me, xDragDifference, yDragDifference);
}
if (showSelectRectangle == true) {
//draw the selection rectange if the situation warents it.
if ( (currentY > yDragStart) && (currentX > xDragStart) ) {
rectangleSelection = new Rectangle2D.Float((xDragStart), (yDragStart), xDragDifference , yDragDifference);
} else if ( (currentY > yDragStart) && (currentX < xDragStart) ) {
rectangleSelection = new Rectangle2D.Float((currentX), (yDragStart), (xDragStart - currentX), yDragDifference);
} else if ( (currentY < yDragStart) && (currentX > xDragStart) ) {
rectangleSelection = new Rectangle2D.Float((xDragStart), (currentY) , xDragDifference , (yDragStart - currentY));
} else if ( (currentY < yDragStart) && (currentX < xDragStart) ) {
rectangleSelection = new Rectangle2D.Float(currentX, currentY, (xDragStart - currentX), (yDragStart - currentY));
}
}
}
}//end of locked check
this.repaint();
coordinateDrag = true;
} catch (Exception e) {
System.err.println("Error in MapPanel.mouseDragged(MouseEvent) - " + e);
}
}
@Override
public void mouseEntered(MouseEvent me) {}
@Override
public void mouseExited(MouseEvent me) {}
@Override
public void mouseMoved(MouseEvent me) {
currentX = me.getX();
currentY = me.getY();
}
@Override
public void mousePressed(MouseEvent me) {
float coordinateToModifyX, coordinateToModifyY;
try {
zoomControlClicked = false;
hideObjectPopup();
if (!panelLocked) {
xDragStart = me.getX();
yDragStart = me.getY();
dragging = true;
//check to see if user has clicked the zoom controls
if (showZoomControls) {
if (ellipseZoomIn.contains(me.getX(),me.getY())) {
this.zoomIn(this.getWidth() / 2.0, this.getHeight() / 2.0);
zoomControlClicked = true;
} else if (ellipseZoomOut.contains(me.getX(),me.getY())) {
this.zoomOut(this.getWidth() / 2.0, this.getHeight() / 2.0);
zoomControlClicked = true;
}
}
if (zoomControlClicked == false) {
//move component points
if (dragMode == MODIFY) {
MapObject objectToModify;
MapObjectList<MapObject> selectedObjects;
selectedObjects = (mapData.getSelectedObjects());
if (selectedObjects.size() > 0) {
objectToModify = selectedObjects.get(0); //get the first selected object
coordinateToModifyOriginal = objectToModify.getSelectedCoordinate().copy(); //create a copy of the object so that the action can be undone
coordinateToModifyX = currentMapView.getX(coordinateToModifyOriginal, MapView.NO_WRAP);
coordinateToModifyY = currentMapView.getY(coordinateToModifyOriginal);
if ((Math.abs(me.getX() - coordinateToModifyX) < SINGLE_CLICK_WIDTH) &&
(Math.abs(me.getY() - coordinateToModifyY) < SINGLE_CLICK_WIDTH) ) {
//Coordinate is within single cick range, move point
moveSelectedPoint = true;
} else {
//pan map
moveSelectedPoint = false;
}
}
} else if (dragMode == TRACE) {
}
} //end zoom clicked check
} //end of locked check
} catch (Exception e) {
System.err.println("Error in MapPanel.mousePressed(MouseEvent) - " + e);
}
coordinateDrag = false;
}
@Override
public void mouseReleased(MouseEvent me) {
boolean mergeSelectedPoint = false;
if (!panelLocked) {
this.dragging = false;
if (this.shiftPressed == true) {
//shift zoom
//TODO: fix shift zooming
//zoomTo(rectangleSelection);
}
if ((dragMode == DRAG_SELECT) && (me.getButton() == MouseEvent.BUTTON1)) {
if (!controlPressed) {
mapData.deselectObjects();
}
mapData.highlightObject(rectangleSelection, controlPressed);
} else if (dragMode == MODIFY) {
if ((actions != null) &&
(coordinateToModifyOriginal != null) &&
(coordinateToModify != null) &&
(coordinateDrag == true)) {
//This code is buggy
// if (!coordinateToModifyOriginal.equals(coordinateToModify)) {
// //Check to see if the point is overlapping an existing point
// for (Coordinate c: currentMapView.getViewPortCoordinates()) {
// if (Math.abs(c.getCenterPoint().x - coordinateToModify.getCenterPoint().x) < 3 &&
// Math.abs(c.getCenterPoint().y - coordinateToModify.getCenterPoint().y) < 3 &&
// !c.equals(coordinateToModify)) {
//
// boolean sameObject = false;
//
// for (MapObject mapObject1: c.getParentVectorObjects()) {
// for (MapObject mapObject2: coordinateToModify.getParentVectorObjects()) {
// if (mapObject1 == mapObject2) {
// sameObject = true;
// break;
// }
// }
//
// if (sameObject)
// break;
// }
//
// if (!sameObject)
// actions.mergeCoordinates(mapData, coordinateToModify, c);
//
// break;
// }
// }
// }
//
// if (mergeSelectedPoint == false) {
// actions.moveCoordinate(coordinateToModifyOriginal, coordinateToModify);
// coordinateToModifyOriginal = null;
// }
//
// for (VectorObject vo: coordinateToModify.getParentVectorObjects())
// vo.generateBoundingBox();
}
coordinateToModify = null;
} else if (dragMode == TRACE) {
}
//reset selection rectangle
showSelectRectangle = false;
coordinateDrag = false;
rectangleSelection = new Rectangle2D.Double(0,0,0,0);
this.repaint();
}
}
/**
* Process mouse wheel moved events.
*
* @param mwe
*/
@Override
public void mouseWheelMoved(MouseWheelEvent mwe) {
hideObjectPopup();
if (!panelLocked) {
if (mwe.getWheelRotation() > 0) {
zoomOut(currentX, currentY);
} else if (mwe.getWheelRotation() < 0) {
zoomIn(currentX, currentY);
}
if (dragMode == TRACE) {
this.repaint();
}
} //end panel lock check
}
/**
* Paints the DigitalMap on the Panel.
*
* @param g
*/
@Override
public void paint(Graphics g) {
Graphics2D g2;
MapTheme mapTheme;
super.paintComponent(g); // clears the panel
g2 = (Graphics2D) g;
g2.setRenderingHints(renderAntialiasing);
this.timeSpanControl = mapData.getTimeSpanControl();
if ((scaleX != 0) && (scaleY != 0))
g2.scale(scaleX, scaleY);
//background color
mapTheme = mapData.getTheme();
this.setBackground(mapTheme.getBackgroundColor());
currentMapView.getMapProjection().setDisplaySize(this.getHeight(), this.getWidth());
currentMapView.setDragging(dragging);
//draw map
mapData.drawMap(g2, currentMapView);
//selection rectangle
if (showSelectRectangle && rectangleSelection != null) {
g2.setStroke(dashedStroke1); //Change draw mode to he first dashed stroke
g2.setColor(Color.BLACK);
g2.draw(rectangleSelection);
g2.setStroke(dashedStroke2); //Change draw mode to he second dashed stroke
g2.setColor(Color.WHITE);
g2.draw(rectangleSelection);
}
if ((dragMode == TRACE)) {
g2.setColor(new Color(68, 68, 68, 200));
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
for (Coordinate c: currentMapView.getViewPortCoordinates()) {
if (c.getParentVectorObjects().size() > 0)
g2.draw(VectorObject.getPointShape(c, MapView.NO_WRAP));
}
if (traceCoordinates.size() > 0) {
//Generate race path
createTracePath();
// Trace outline
g2.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.setColor(Color.WHITE);
g2.draw(tracePath);
// Trace inner
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.setColor(new Color(68, 68, 68));
g2.draw(tracePath);
for (Shape s: points) {
g2.setColor(Color.BLACK);
g2.draw(s);
}
}
}
if (dragMode == DRAG_PAN)
drawObjectPopup(g2);
if (showScale)
drawScale(g2);
if (showZoomControls)
drawZoomControls(g2);
if (timeSpanControl != null)
timeSpanControl.draw(g2, this.getHeight(), this.getWidth());
}
/**
* Scrolls the View Port.
*
* @param horizontal Horizontal scroll difference.
* @param vertical Vertical scroll difference.
*/
public void scroll(double horizontal, double vertical) {
xOrigin += (horizontal);
yOrigin += (vertical);
currentMapView.shiftMapReference(xOrigin, yOrigin);
xOrigin = 0;
yOrigin = 0;
this.repaint();
}
/**
* Sets the Drag Mode for this map. It the behavior of how dragging and
* other mouse operations are handled by the panel.
*
* @param dragMode
*/
public void setDragMode(int dragMode) {
if (dragMode == TRACE && this.dragMode == TRACE) {
doTrace();
} else {
clearTracePath();
}
this.dragMode = dragMode;
if (dragMode == MODIFY || dragMode == TRACE) {
currentMapView.setShowPoints(true);
} else {
currentMapView.setShowPoints(false);
}
}
/**
* Sets the map to be displayed in this MapPanel and updates the Map Reference.
*
* @param mapData
*/
@Override
public void setMap(DigitalMap mapData) {
currentMapView = mapData.getLastMapView();
currentMapView.setNodeMap(mapData.getCoordinateSet());
updateMap(mapData);
}
/**
* Sets the display scale for view in the map.
*
* @param scaleX
* @param scaleY
*/
public void setScale(float scaleX, float scaleY) {
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/**
* Sets the TimeSpanControl to be used in the panel.
*
* @param timeSpanControl
*/
public void setTimeSpanControl(TimeSpanControl timeSpanControl) {
this.timeSpanControl = timeSpanControl;
}
/**
* Displays the Object Popup. This shows info about the selected object.
*
* @param x
* @param y
*/
public void showObjectPopup(int x, int y) {
//TODO: finish codding ObjectPopup.
}
/**
* Sets if the panel will display the map scale.
*
* @param showScale
*/
public void showScale(boolean showScale) {
this.showScale = showScale;
}
public void showZoomControls(boolean showZoomControls) {
this.showZoomControls = showZoomControls;
}
/**
* Does maintenence for the MapPanel
*/
@Override
public void update() {
this.controlPressed = false;
this.shiftPressed = false;
this.repaint();
}
/**
* Updates the Map used by the MapPanel but not the map reference
*
* @param mapData The DigitalMap to display in the panel.
*/
public void updateMap(DigitalMap mapData) {
// this.zoomLevel = mapData.getLookAtCoordinate().getAltitude();
this.mapData = mapData;
this.actions = mapData.getActions();
if (this.actions == null) {
//The map does not have a commands object, create one.
this.actions = new Actions(mapData, this);
mapData.setActions(actions);
}
this.fireUpdateables();
this.repaint();
}
/**
* Zooms the view port in.
*
* @param x
* @param y
*/
public void zoomIn(double x, double y) {
if (getPanelWidthInKm() > 0.01104) {
currentMapView.getMapProjection().zoomIn(x, y);
currentMapView.update();
this.repaint();
}
}
/**
* Zooms the view port out.
*
* @param x
* @param y
*/
public void zoomOut(double x, double y) {
currentMapView.getMapProjection().zoomOut(x, y);
currentMapView.update();
this.repaint();
}
}