1 package tim.prune.gui.map;
3 import java.awt.BasicStroke;
4 import java.awt.BorderLayout;
6 import java.awt.Cursor;
7 import java.awt.Dimension;
8 import java.awt.FlowLayout;
9 import java.awt.FontMetrics;
10 import java.awt.Graphics;
11 import java.awt.Graphics2D;
12 import java.awt.Image;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.awt.event.ItemEvent;
16 import java.awt.event.ItemListener;
17 import java.awt.event.KeyEvent;
18 import java.awt.event.KeyListener;
19 import java.awt.event.MouseEvent;
20 import java.awt.event.MouseListener;
21 import java.awt.event.MouseMotionListener;
22 import java.awt.event.MouseWheelEvent;
23 import java.awt.event.MouseWheelListener;
24 import java.awt.image.BufferedImage;
26 import javax.swing.BorderFactory;
27 import javax.swing.BoxLayout;
28 import javax.swing.JButton;
29 import javax.swing.JCheckBox;
30 import javax.swing.JMenuItem;
31 import javax.swing.JPanel;
32 import javax.swing.JPopupMenu;
33 import javax.swing.JSlider;
34 import javax.swing.event.ChangeEvent;
35 import javax.swing.event.ChangeListener;
38 import tim.prune.DataSubscriber;
39 import tim.prune.FunctionLibrary;
40 import tim.prune.I18nManager;
41 import tim.prune.UpdateMessageBroker;
42 import tim.prune.config.ColourScheme;
43 import tim.prune.config.Config;
44 import tim.prune.data.Checker;
45 import tim.prune.data.Coordinate;
46 import tim.prune.data.DataPoint;
47 import tim.prune.data.DoubleRange;
48 import tim.prune.data.Field;
49 import tim.prune.data.FieldList;
50 import tim.prune.data.Latitude;
51 import tim.prune.data.Longitude;
52 import tim.prune.data.MidpointData;
53 import tim.prune.data.Selection;
54 import tim.prune.data.Track;
55 import tim.prune.data.TrackInfo;
56 import tim.prune.function.compress.MarkPointsInRectangleFunction;
57 import tim.prune.function.edit.FieldEdit;
58 import tim.prune.function.edit.FieldEditList;
59 import tim.prune.gui.IconManager;
62 * Class for the map canvas, to display a background map and draw on it
64 public class MapCanvas extends JPanel implements MouseListener, MouseMotionListener, DataSubscriber,
65 KeyListener, MouseWheelListener
67 /** App object for callbacks */
68 private App _app = null;
70 private Track _track = null;
71 /** TrackInfo object */
72 private TrackInfo _trackInfo = null;
73 /** Selection object */
74 private Selection _selection = null;
75 /** Object to keep track of midpoints */
76 private MidpointData _midpoints = null;
77 /** Index of point clicked at mouseDown */
78 private int _clickedPoint = -1;
79 /** Previously selected point */
80 private int _prevSelectedPoint = -1;
82 private MapTileManager _tileManager = new MapTileManager(this);
83 /** Image to display */
84 private BufferedImage _mapImage = null;
85 /** Slider for transparency */
86 private JSlider _transparencySlider = null;
87 /** Checkbox for scale bar */
88 private JCheckBox _scaleCheckBox = null;
89 /** Checkbox for maps */
90 private JCheckBox _mapCheckBox = null;
91 /** Checkbox for autopan */
92 private JCheckBox _autopanCheckBox = null;
93 /** Checkbox for connecting track points */
94 private JCheckBox _connectCheckBox = null;
95 /** Checkbox for enable edit mode */
96 private JCheckBox _editmodeCheckBox = null;
97 /** Right-click popup menu */
98 private JPopupMenu _popup = null;
99 /** Top component panel */
100 private JPanel _topPanel = null;
101 /** Side component panel */
102 private JPanel _sidePanel = null;
104 private ScaleBar _scaleBar = null;
106 private DoubleRange _latRange = null, _lonRange = null;
107 private DoubleRange _xRange = null, _yRange = null;
108 private boolean _recalculate = false;
109 /** Flag to check bounds on next paint */
110 private boolean _checkBounds = false;
112 private MapPosition _mapPosition = null;
113 /** coordinates of drag from point */
114 private int _dragFromX = -1, _dragFromY = -1;
115 /** coordinates of drag to point */
116 private int _dragToX = -1, _dragToY = -1;
117 /** coordinates of popup menu */
118 private int _popupMenuX = -1, _popupMenuY = -1;
119 /** Flag to prevent showing too often the error message about loading maps */
120 private boolean _shownOsmErrorAlready = false;
121 /** Current drawing mode */
122 private int _drawMode = MODE_DEFAULT;
124 /** Constant for click sensitivity when selecting nearest point */
125 private static final int CLICK_SENSITIVITY = 10;
126 /** Constant for pan distance from key presses */
127 private static final int PAN_DISTANCE = 20;
128 /** Constant for pan distance from autopan */
129 private static final int AUTOPAN_DISTANCE = 75;
132 private static final Color COLOR_MESSAGES = Color.GRAY;
135 private static final int MODE_DEFAULT = 0;
136 private static final int MODE_ZOOM_RECT = 1;
137 private static final int MODE_DRAW_POINTS_START = 2;
138 private static final int MODE_DRAW_POINTS_CONT = 3;
139 private static final int MODE_DRAG_POINT = 4;
140 private static final int MODE_CREATE_MIDPOINT = 5;
141 private static final int MODE_MARK_RECTANGLE = 6;
143 private static final int INDEX_UNKNOWN = -2;
148 * @param inApp App object for callbacks
149 * @param inTrackInfo track info object
151 public MapCanvas(App inApp, TrackInfo inTrackInfo)
154 _trackInfo = inTrackInfo;
155 _track = inTrackInfo.getTrack();
156 _selection = inTrackInfo.getSelection();
157 _midpoints = new MidpointData();
158 _mapPosition = new MapPosition();
159 addMouseListener(this);
160 addMouseMotionListener(this);
161 addMouseWheelListener(this);
162 addKeyListener(this);
164 // Make listener for changes to controls
165 ItemListener itemListener = new ItemListener() {
166 public void itemStateChanged(ItemEvent e)
172 // Make special listener for changes to map checkbox
173 ItemListener mapCheckListener = new ItemListener() {
174 public void itemStateChanged(ItemEvent e)
176 _tileManager.clearMemoryCaches();
178 Config.setConfigBoolean(Config.KEY_SHOW_MAP, e.getStateChange() == ItemEvent.SELECTED);
179 UpdateMessageBroker.informSubscribers(); // to let menu know
180 // If the track is only partially visible and you turn the map off, make the track fully visible again
181 if (e.getStateChange() == ItemEvent.DESELECTED && _transparencySlider.getValue() < 0) {
182 _transparencySlider.setValue(0);
186 _topPanel = new OverlayPanel();
187 _topPanel.setLayout(new FlowLayout());
188 // Make slider for transparency
189 _transparencySlider = new JSlider(-6, 6, 0);
190 _transparencySlider.setPreferredSize(new Dimension(100, 20));
191 _transparencySlider.setMajorTickSpacing(1);
192 _transparencySlider.setSnapToTicks(true);
193 _transparencySlider.setOpaque(false);
194 _transparencySlider.setValue(0);
195 _transparencySlider.addChangeListener(new ChangeListener() {
196 public void stateChanged(ChangeEvent e)
198 int val = _transparencySlider.getValue();
199 if (val == 1 || val == -1)
200 _transparencySlider.setValue(0);
207 _transparencySlider.setFocusable(false); // stop slider from stealing keyboard focus
208 _topPanel.add(_transparencySlider);
209 // Add checkbox button for enabling scale bar
210 _scaleCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.SCALEBAR_BUTTON), true);
211 _scaleCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.SCALEBAR_BUTTON_ON));
212 _scaleCheckBox.setOpaque(false);
213 _scaleCheckBox.setToolTipText(I18nManager.getText("menu.map.showscalebar"));
214 _scaleCheckBox.addItemListener(new ItemListener() {
215 public void itemStateChanged(ItemEvent e) {
216 _scaleBar.setVisible(_scaleCheckBox.isSelected());
219 _scaleCheckBox.setFocusable(false); // stop button from stealing keyboard focus
220 _topPanel.add(_scaleCheckBox);
221 // Add checkbox button for enabling maps or not
222 _mapCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.MAP_BUTTON), false);
223 _mapCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.MAP_BUTTON_ON));
224 _mapCheckBox.setOpaque(false);
225 _mapCheckBox.setToolTipText(I18nManager.getText("menu.map.showmap"));
226 _mapCheckBox.addItemListener(mapCheckListener);
227 _mapCheckBox.setFocusable(false); // stop button from stealing keyboard focus
228 _topPanel.add(_mapCheckBox);
229 // Add checkbox button for enabling autopan or not
230 _autopanCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON), true);
231 _autopanCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON_ON));
232 _autopanCheckBox.setOpaque(false);
233 _autopanCheckBox.setToolTipText(I18nManager.getText("menu.map.autopan"));
234 _autopanCheckBox.addItemListener(itemListener);
235 _autopanCheckBox.setFocusable(false); // stop button from stealing keyboard focus
236 _topPanel.add(_autopanCheckBox);
237 // Add checkbox button for connecting points or not
238 _connectCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON), true);
239 _connectCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
240 _connectCheckBox.setOpaque(false);
241 _connectCheckBox.setToolTipText(I18nManager.getText("menu.map.connect"));
242 _connectCheckBox.addItemListener(itemListener);
243 _connectCheckBox.setFocusable(false); // stop button from stealing keyboard focus
244 _topPanel.add(_connectCheckBox);
246 // Add checkbox button for edit mode or not
247 _editmodeCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.EDIT_MODE_BUTTON), false);
248 _editmodeCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.EDIT_MODE_BUTTON_ON));
249 _editmodeCheckBox.setOpaque(false);
250 _editmodeCheckBox.setToolTipText(I18nManager.getText("menu.map.editmode"));
251 _editmodeCheckBox.addItemListener(itemListener);
252 _editmodeCheckBox.setFocusable(false); // stop button from stealing keyboard focus
253 _topPanel.add(_editmodeCheckBox);
255 // Add zoom in, zoom out buttons
256 _sidePanel = new OverlayPanel();
257 _sidePanel.setLayout(new BoxLayout(_sidePanel, BoxLayout.Y_AXIS));
258 JButton zoomInButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_IN_BUTTON));
259 zoomInButton.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
260 zoomInButton.setContentAreaFilled(false);
261 zoomInButton.setToolTipText(I18nManager.getText("menu.map.zoomin"));
262 zoomInButton.addActionListener(new ActionListener() {
263 public void actionPerformed(ActionEvent e)
268 zoomInButton.setFocusable(false); // stop button from stealing keyboard focus
269 _sidePanel.add(zoomInButton);
270 JButton zoomOutButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_OUT_BUTTON));
271 zoomOutButton.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
272 zoomOutButton.setContentAreaFilled(false);
273 zoomOutButton.setToolTipText(I18nManager.getText("menu.map.zoomout"));
274 zoomOutButton.addActionListener(new ActionListener() {
275 public void actionPerformed(ActionEvent e)
280 zoomOutButton.setFocusable(false); // stop button from stealing keyboard focus
281 _sidePanel.add(zoomOutButton);
283 // Bottom panel for scale bar
284 _scaleBar = new ScaleBar();
286 // add control panels to this one
287 setLayout(new BorderLayout());
288 _topPanel.setVisible(false);
289 _sidePanel.setVisible(false);
290 add(_topPanel, BorderLayout.NORTH);
291 add(_sidePanel, BorderLayout.WEST);
292 add(_scaleBar, BorderLayout.SOUTH);
299 * Make the popup menu for right-clicking the map
301 private void makePopup()
303 _popup = new JPopupMenu();
304 JMenuItem zoomInItem = new JMenuItem(I18nManager.getText("menu.map.zoomin"));
305 zoomInItem.addActionListener(new ActionListener() {
306 public void actionPerformed(ActionEvent e)
308 panMap((_popupMenuX - getWidth()/2)/2, (_popupMenuY - getHeight()/2)/2);
311 _popup.add(zoomInItem);
312 JMenuItem zoomOutItem = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
313 zoomOutItem.addActionListener(new ActionListener() {
314 public void actionPerformed(ActionEvent e)
316 panMap(-(_popupMenuX - getWidth()/2), -(_popupMenuY - getHeight()/2));
319 _popup.add(zoomOutItem);
320 JMenuItem zoomFullItem = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
321 zoomFullItem.addActionListener(new ActionListener() {
322 public void actionPerformed(ActionEvent e)
328 _popup.add(zoomFullItem);
329 _popup.addSeparator();
331 JMenuItem setMapBgItem = new JMenuItem(
332 I18nManager.getText(FunctionLibrary.FUNCTION_SET_MAP_BG.getNameKey()));
333 setMapBgItem.addActionListener(new ActionListener() {
334 public void actionPerformed(ActionEvent e)
336 FunctionLibrary.FUNCTION_SET_MAP_BG.begin();
338 _popup.add(setMapBgItem);
340 JMenuItem newPointItem = new JMenuItem(I18nManager.getText("menu.map.newpoint"));
341 newPointItem.addActionListener(new ActionListener() {
342 public void actionPerformed(ActionEvent e)
344 _app.createPoint(createPointFromClick(_popupMenuX, _popupMenuY));
346 _popup.add(newPointItem);
348 JMenuItem drawPointsItem = new JMenuItem(I18nManager.getText("menu.map.drawpoints"));
349 drawPointsItem.addActionListener(new ActionListener() {
350 public void actionPerformed(ActionEvent e)
352 _drawMode = MODE_DRAW_POINTS_START;
355 _popup.add(drawPointsItem);
360 * Zoom to fit the current data area
362 private void zoomToFit()
364 _latRange = _track.getLatRange();
365 _lonRange = _track.getLonRange();
366 _xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
367 MapUtils.getXFromLongitude(_lonRange.getMaximum()));
368 _yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
369 MapUtils.getYFromLatitude(_latRange.getMaximum()));
370 _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
371 getWidth(), getHeight());
377 * @see java.awt.Canvas#paint(java.awt.Graphics)
379 public void paint(Graphics inG)
382 if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
385 if (_track.getNumPoints() > 0)
387 // Check for autopan if enabled / necessary
388 if (_autopanCheckBox.isSelected())
390 int selectedPoint = _selection.getCurrentPointIndex();
391 if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
393 autopanToPoint(selectedPoint);
395 _prevSelectedPoint = selectedPoint;
398 // Draw the map contents if necessary
399 if ((_mapImage == null || _recalculate))
402 _scaleBar.updateScale(_mapPosition.getZoom(), _mapPosition.getYFromPixels(0, 0));
404 // Draw the prepared image onto the panel
405 if (_mapImage != null) {
406 inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
411 case MODE_DRAG_POINT:
412 drawDragLines(inG, _selection.getCurrentPointIndex()-1, _selection.getCurrentPointIndex()+1);
415 case MODE_CREATE_MIDPOINT:
416 drawDragLines(inG, _clickedPoint-1, _clickedPoint);
420 case MODE_MARK_RECTANGLE:
421 if (_dragFromX != -1 && _dragFromY != -1)
423 // Draw the zoom rectangle if necessary
424 inG.setColor(Color.RED);
425 inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
426 inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
427 inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
428 inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
432 case MODE_DRAW_POINTS_CONT:
433 // draw line to mouse position to show drawing mode
434 inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
435 int prevIndex = _track.getNumPoints()-1;
436 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(prevIndex));
437 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(prevIndex));
438 inG.drawLine(px, py, _dragToX, _dragToY);
444 inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
445 inG.fillRect(0, 0, getWidth(), getHeight());
446 inG.setColor(COLOR_MESSAGES);
447 inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
448 _scaleBar.updateScale(-1, 0);
450 // Draw slider etc on top
455 * @return true if the currently selected point is visible, false if off-screen or nothing selected
457 private boolean isCurrentPointVisible()
459 if (_trackInfo.getCurrentPoint() == null) {return false;}
460 final int selectedPoint = _selection.getCurrentPointIndex();
461 final int xFromCentre = Math.abs(_mapPosition.getXFromCentre(_track.getX(selectedPoint)));
462 if (xFromCentre > (getWidth()/2)) {return false;}
463 final int yFromCentre = Math.abs(_mapPosition.getYFromCentre(_track.getY(selectedPoint)));
464 return yFromCentre < (getHeight()/2);
468 * If the specified point isn't visible, pan to it
469 * @param inIndex index of selected point
471 private void autopanToPoint(int inIndex)
473 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inIndex));
474 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inIndex));
477 if (px < PAN_DISTANCE) {
478 panX = px - AUTOPAN_DISTANCE;
480 else if (px > (getWidth()-PAN_DISTANCE)) {
481 panX = AUTOPAN_DISTANCE + px - getWidth();
483 if (py < PAN_DISTANCE) {
484 panY = py - AUTOPAN_DISTANCE;
486 if (py > (getHeight()-PAN_DISTANCE)) {
487 panY = AUTOPAN_DISTANCE + py - getHeight();
489 if (panX != 0 || panY != 0) {
490 _mapPosition.pan(panX, panY);
495 * Paint the map tiles and the points on to the _mapImage
497 private void paintMapContents()
499 if (_mapImage == null || _mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())
501 _mapImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
505 Graphics g = _mapImage.getGraphics();
506 // Clear to background
507 g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND));
508 g.fillRect(0, 0, getWidth(), getHeight());
510 // Check whether maps are on or not
511 boolean showMap = Config.getConfigBoolean(Config.KEY_SHOW_MAP);
512 _mapCheckBox.setSelected(showMap);
514 // reset error message
515 if (!showMap) {_shownOsmErrorAlready = false;}
516 _recalculate = false;
517 // Only get map tiles if selected
521 _tileManager.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
523 boolean loadingFailed = false;
524 if (_mapImage == null) return;
526 if (_tileManager.isOverzoomed())
528 // display overzoom message
529 g.setColor(COLOR_MESSAGES);
530 g.drawString(I18nManager.getText("map.overzoom"), 50, getHeight()/2);
534 int numLayers = _tileManager.getNumLayers();
535 // Loop over tiles drawing each one
536 int[] tileIndices = _mapPosition.getTileIndices(getWidth(), getHeight());
537 int[] pixelOffsets = _mapPosition.getDisplayOffsets(getWidth(), getHeight());
538 for (int tileX = tileIndices[0]; tileX <= tileIndices[1] && !loadingFailed; tileX++)
540 int x = (tileX - tileIndices[0]) * 256 - pixelOffsets[0];
541 for (int tileY = tileIndices[2]; tileY <= tileIndices[3]; tileY++)
543 int y = (tileY - tileIndices[2]) * 256 - pixelOffsets[1];
545 for (int l=0; l<numLayers; l++)
547 Image image = _tileManager.getTile(l, tileX, tileY);
549 g.drawImage(image, x, y, 256, 256, null);
555 // Make maps brighter / fainter according to slider
556 final int brightnessIndex = Math.max(1, _transparencySlider.getValue()) - 1;
557 if (brightnessIndex > 0)
559 final int[] alphas = {0, 40, 80, 120, 160, 210};
560 Color bgColor = Config.getColourScheme().getColour(ColourScheme.IDX_BACKGROUND);
561 bgColor = new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), alphas[brightnessIndex]);
563 g.fillRect(0, 0, getWidth(), getHeight());
568 // Paint the track points on top
569 int pointsPainted = 1;
572 pointsPainted = paintPoints(g);
574 catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
575 catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
580 // Zoom to fit if no points found
581 if (pointsPainted <= 0 && _checkBounds) {
586 _checkBounds = false;
587 // enable / disable transparency slider
588 _transparencySlider.setEnabled(showMap);
593 * Paint the points using the given graphics object
594 * @param inG Graphics object to use for painting
595 * @return number of points painted, if any
597 private int paintPoints(Graphics inG)
600 final ColourScheme cs = Config.getColourScheme();
601 final int[] opacities = {255, 190, 130, 80, 40, 0};
603 if (_transparencySlider.getValue() < 0)
604 opacity = opacities[-1 - _transparencySlider.getValue()];
605 final Color pointColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_POINT), opacity);
606 final Color rangeColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_SELECTION), opacity);
607 final Color currentColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_PRIMARY), opacity);
608 final Color secondColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_SECONDARY), opacity);
609 final Color textColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_TEXT), opacity);
611 final int winWidth = getWidth();
612 final int winHeight = getHeight();
613 final int halfWinWidth = winWidth / 2;
614 final int halfWinHeight = winHeight / 2;
616 final int numPoints = _track.getNumPoints();
617 final int[] xPixels = new int[numPoints];
618 final int[] yPixels = new int[numPoints];
620 // try to set line width for painting
621 if (inG instanceof Graphics2D)
623 int lineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
624 if (lineWidth < 1 || lineWidth > 4) {lineWidth = 2;}
625 ((Graphics2D) inG).setStroke(new BasicStroke(lineWidth));
627 int pointsPainted = 0;
629 inG.setColor(pointColour);
630 int prevX = -1, prevY = -1;
631 boolean connectPoints = _connectCheckBox.isSelected();
632 boolean prevPointVisible = false, currPointVisible = false;
633 boolean anyWaypoints = false;
634 boolean isWaypoint = false;
635 for (int i=0; i<numPoints; i++)
637 // Calculate pixel position of point from its x, y coordinates
638 int px = halfWinWidth + _mapPosition.getXFromCentre(_track.getX(i));
639 int py = halfWinHeight + _mapPosition.getYFromCentre(_track.getY(i));
640 px = wrapLongitudeValue(px, winWidth, _mapPosition.getZoom());
641 // Remember these calculated pixel values so they don't have to be recalculated
642 xPixels[i] = px; yPixels[i] = py;
644 currPointVisible = px >= 0 && px < winWidth && py >= 0 && py < winHeight;
645 isWaypoint = _track.getPoint(i).isWaypoint();
646 anyWaypoints = anyWaypoints || isWaypoint;
647 if (currPointVisible)
651 // Draw rectangle for track point
652 if (_track.getPoint(i).getDeleteFlag()) {
653 inG.setColor(currentColour);
656 inG.setColor(pointColour);
658 inG.drawRect(px-2, py-2, 3, 3);
664 // Connect track points if either of them are visible
665 if (connectPoints && (currPointVisible || prevPointVisible)
666 && !(prevX == -1 && prevY == -1)
667 && !_track.getPoint(i).getSegmentStart())
669 inG.drawLine(prevX, prevY, px, py);
671 prevX = px; prevY = py;
673 prevPointVisible = currPointVisible;
676 // Loop over points, just drawing blobs for waypoints
677 inG.setColor(textColour);
678 FontMetrics fm = inG.getFontMetrics();
679 int nameHeight = fm.getHeight();
682 int numWaypoints = 0;
683 for (int i=0; i<_track.getNumPoints(); i++)
685 if (_track.getPoint(i).isWaypoint())
689 if (px >= 0 && px < winWidth && py >= 0 && py < winHeight)
691 inG.fillRect(px-3, py-3, 6, 6);
697 // Take more care with waypoint names if less than 100 are visible
698 final int numNameSteps = (numWaypoints > 100 ? 1 : 4);
699 final int numPointSteps = (numWaypoints > 1000 ? 2 : 1);
701 // Loop over points again, now draw names for waypoints
702 int[] nameXs = {0, 0, 0, 0};
703 int[] nameYs = {0, 0, 0, 0};
704 for (int i=0; i<_track.getNumPoints(); i += numPointSteps)
706 if (_track.getPoint(i).isWaypoint())
710 if (px >= 0 && px < winWidth && py >= 0 && py < winHeight)
712 // Figure out where to draw waypoint name so it doesn't obscure track
713 String waypointName = _track.getPoint(i).getWaypointName();
714 int nameWidth = fm.stringWidth(waypointName);
715 boolean drawnName = false;
716 // Make arrays for coordinates right left up down
717 nameXs[0] = px + 2; nameXs[1] = px - nameWidth - 2;
718 nameXs[2] = nameXs[3] = px - nameWidth/2;
719 nameYs[0] = nameYs[1] = py + (nameHeight/2);
720 nameYs[2] = py - 2; nameYs[3] = py + nameHeight + 2;
721 for (int extraSpace = 0; extraSpace < numNameSteps && !drawnName; extraSpace++)
723 // Shift arrays for coordinates right left up down
724 nameXs[0] += 3; nameXs[1] -= 3;
725 nameYs[2] -= 3; nameYs[3] += 3;
726 // Check each direction in turn right left up down
727 for (int a=0; a<4; a++)
729 if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < winWidth
730 && nameYs[a] < winHeight && (nameYs[a] - nameHeight) > 0
731 && !MapUtils.overlapsPoints(_mapImage, nameXs[a], nameYs[a], nameWidth, nameHeight, textColour))
733 // Found a rectangle to fit - draw name here and quit
734 inG.drawString(waypointName, nameXs[a], nameYs[a]);
744 // Loop over points, drawing blobs for photo / audio points
745 inG.setColor(secondColour);
746 for (int i=0; i<_track.getNumPoints(); i++)
748 if (_track.getPoint(i).hasMedia())
752 if (px >= 0 && px < winWidth && py >= 0 && py < winHeight)
754 inG.drawRect(px-1, py-1, 2, 2);
755 inG.drawRect(px-2, py-2, 4, 4);
761 // Draw selected range
762 if (_selection.hasRangeSelected())
764 inG.setColor(rangeColour);
765 for (int i=_selection.getStart(); i<=_selection.getEnd(); i++)
769 inG.drawRect(px-1, py-1, 2, 2);
773 // Draw crosshairs at selected point
774 int selectedPoint = _selection.getCurrentPointIndex();
775 if (selectedPoint >= 0)
777 int px = xPixels[selectedPoint];
778 int py = yPixels[selectedPoint];
779 inG.setColor(currentColour);
781 inG.drawLine(px, 0, px, winHeight);
782 inG.drawLine(0, py, winWidth, py);
784 // Return the number of points painted
785 return pointsPainted;
789 * Wrap the given pixel value if appropriate and possible
790 * @param inPx Pixel x coordinate
791 * @param inWinWidth window width in pixels
792 * @param inZoom zoom level
793 * @return modified pixel x coordinate
795 private static int wrapLongitudeValue(int inPx, int inWinWidth, int inZoom)
797 if (inPx > inWinWidth)
799 // Pixel is too far right, could we wrap it back onto the screen?
801 while (px > inWinWidth) {
802 px -= (256 << inZoom);
805 return px; // successfully wrapped back onto the screen
810 // Pixel is too far left, could we wrap it back onto the screen?
813 px += (256 << inZoom);
815 if (px < inWinWidth) {
816 return px; // successfully wrapped back onto the screen
819 // Either it's already on the screen or couldn't be wrapped
824 * Draw the lines while dragging a point
825 * @param inG graphics object
826 * @param inPrevIndex index of point to draw from
827 * @param inNextIndex index of point to draw to
829 private void drawDragLines(Graphics inG, int inPrevIndex, int inNextIndex)
831 inG.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
832 // line from prev point to cursor
833 if (inPrevIndex > -1 && !_track.getPoint(inPrevIndex+1).getSegmentStart())
835 final int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inPrevIndex));
836 final int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inPrevIndex));
837 inG.drawLine(px, py, _dragToX, _dragToY);
839 if (inNextIndex < _track.getNumPoints() && !_track.getPoint(inNextIndex).getSegmentStart())
841 final int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(inNextIndex));
842 final int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(inNextIndex));
843 inG.drawLine(px, py, _dragToX, _dragToY);
848 * Make a semi-transparent colour for drawing with
849 * @param inColour base colour (fully opaque)
850 * @param inOpacity opacity where 0=invisible and 255=full
851 * @return new colour object
853 private static Color makeTransparentColour(Color inColour, int inOpacity)
855 if (inOpacity > 240) return inColour;
856 return new Color(inColour.getRed(), inColour.getGreen(), inColour.getBlue(), inOpacity);
860 * Inform that tiles have been updated and the map can be repainted
861 * @param inIsOk true if data loaded ok, false for error
863 public synchronized void tilesUpdated(boolean inIsOk)
865 // Show message if loading failed (but not too many times)
866 if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
868 _shownOsmErrorAlready = true;
869 // use separate thread to show message about failing to load osm images
870 new Thread(new Runnable() {
872 try {Thread.sleep(500);} catch (InterruptedException ie) {}
873 _app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
882 * Zoom out, if not already at minimum zoom
884 public void zoomOut()
886 _mapPosition.zoomOut();
892 * Zoom in, if not already at maximum zoom
896 // See if selected point is currently visible, if so (and autopan on) then autopan after zoom to keep it visible
897 boolean wasVisible = _autopanCheckBox.isSelected() && isCurrentPointVisible();
898 _mapPosition.zoomIn();
899 if (wasVisible && !isCurrentPointVisible()) {
900 autopanToPoint(_selection.getCurrentPointIndex());
908 * @param inDeltaX x shift
909 * @param inDeltaY y shift
911 public void panMap(int inDeltaX, int inDeltaY)
913 _mapPosition.pan(inDeltaX, inDeltaY);
919 * Create a DataPoint object from the given click coordinates
920 * @param inX x coordinate of click
921 * @param inY y coordinate of click
922 * @return DataPoint with given coordinates and no altitude
924 private DataPoint createPointFromClick(int inX, int inY)
926 double lat = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(inY, getHeight()));
927 double lon = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(inX, getWidth()));
928 return new DataPoint(new Latitude(lat, Coordinate.FORMAT_NONE),
929 new Longitude(lon, Coordinate.FORMAT_NONE), null);
933 * Move a DataPoint object to the given mouse coordinates
934 * @param startX start x coordinate of mouse
935 * @param startY start y coordinate of mouse
936 * @param endX end x coordinate of mouse
937 * @param endY end y coordinate of mouse
939 private void movePointToMouse(int startX, int startY, int endX, int endY )
941 double lat1 = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(startY, getHeight()));
942 double lon1 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(startX, getWidth()));
943 double lat_delta = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(endY, getHeight())) - lat1;
944 double lon_delta = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(endX, getWidth())) - lon1;
946 DataPoint point = _trackInfo.getCurrentPoint();
951 // Make lists for edit and undo, and add each changed field in turn
952 FieldEditList editList = new FieldEditList();
953 FieldEditList undoList = new FieldEditList();
956 FieldList fieldList = _track.getFieldList();
957 int numFields = fieldList.getNumFields();
958 for (int i=0; i<numFields; i++)
960 Field field = fieldList.getField(i);
961 if (field == Field.LATITUDE) {
962 editList.addEdit(new FieldEdit(field, Double.toString(point.getLatitude().getDouble() + lat_delta)));
963 undoList.addEdit(new FieldEdit(field, point.getFieldValue(Field.LATITUDE)));
965 else if (field == Field.LONGITUDE) {
966 editList.addEdit(new FieldEdit(field, Double.toString(point.getLongitude().getDouble() + lon_delta)));
967 undoList.addEdit(new FieldEdit(field, point.getFieldValue(Field.LONGITUDE)));
970 _app.completePointEdit(editList, undoList);
975 * @see javax.swing.JComponent#getMinimumSize()
977 public Dimension getMinimumSize()
979 final Dimension minSize = new Dimension(512, 300);
984 * @see javax.swing.JComponent#getPreferredSize()
986 public Dimension getPreferredSize()
988 return getMinimumSize();
993 * Respond to mouse click events
994 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
996 public void mouseClicked(MouseEvent inE)
998 if (_track != null && _track.getNumPoints() > 0)
1000 // select point if it's a left-click
1001 if (!inE.isMetaDown())
1003 if (inE.getClickCount() == 1)
1006 if (_drawMode == MODE_DEFAULT)
1008 int pointIndex = _clickedPoint;
1009 if (pointIndex == INDEX_UNKNOWN)
1011 // index hasn't been calculated yet
1012 pointIndex = _track.getNearestPointIndex(
1013 _mapPosition.getXFromPixels(inE.getX(), getWidth()),
1014 _mapPosition.getYFromPixels(inE.getY(), getHeight()),
1015 _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
1017 // Extend selection for shift-click
1018 if (inE.isShiftDown()) {
1019 _trackInfo.extendSelection(pointIndex);
1022 _trackInfo.selectPoint(pointIndex);
1025 else if (_drawMode == MODE_DRAW_POINTS_START)
1027 _app.createPoint(createPointFromClick(inE.getX(), inE.getY()));
1028 _dragToX = inE.getX();
1029 _dragToY = inE.getY();
1030 _drawMode = MODE_DRAW_POINTS_CONT;
1032 else if (_drawMode == MODE_DRAW_POINTS_CONT)
1034 DataPoint point = createPointFromClick(inE.getX(), inE.getY());
1035 _app.createPoint(point);
1036 point.setSegmentStart(false);
1039 else if (inE.getClickCount() == 2)
1042 if (_drawMode == MODE_DEFAULT) {
1043 panMap(inE.getX() - getWidth()/2, inE.getY() - getHeight()/2);
1046 else if (_drawMode == MODE_DRAW_POINTS_START || _drawMode == MODE_DRAW_POINTS_CONT) {
1047 _drawMode = MODE_DEFAULT;
1053 // show the popup menu for right-clicks
1054 _popupMenuX = inE.getX();
1055 _popupMenuY = inE.getY();
1056 _popup.show(this, _popupMenuX, _popupMenuY);
1060 _app.setCurrentMode(App.AppMode.NORMAL);
1061 if (_drawMode == MODE_MARK_RECTANGLE) _drawMode = MODE_DEFAULT;
1065 * Ignore mouse enter events
1066 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
1068 public void mouseEntered(MouseEvent inE)
1074 * Ignore mouse exited events
1075 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
1077 public void mouseExited(MouseEvent inE)
1083 * React to mouse pressed events to initiate a point drag
1084 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
1086 public void mousePressed(MouseEvent inE)
1088 _clickedPoint = INDEX_UNKNOWN;
1089 if (_track == null || _track.getNumPoints() <= 0)
1091 if (!inE.isMetaDown())
1093 // Left mouse drag - check if point is near; if so select it for dragging
1094 if (_drawMode == MODE_DEFAULT)
1096 /* Drag points if edit mode is enabled OR ALT is pressed */
1097 if (_editmodeCheckBox.isSelected() || inE.isAltDown() || inE.isAltGraphDown())
1099 final double clickX = _mapPosition.getXFromPixels(inE.getX(), getWidth());
1100 final double clickY = _mapPosition.getYFromPixels(inE.getY(), getHeight());
1101 final double clickSens = _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY);
1102 _clickedPoint = _track.getNearestPointIndex(clickX, clickY, clickSens, false);
1104 if (_clickedPoint >= 0)
1106 // TODO: maybe use another color of the cross or remove the cross while dragging???
1108 _trackInfo.selectPoint(_clickedPoint);
1109 if (_trackInfo.getCurrentPoint() != null)
1111 _drawMode = MODE_DRAG_POINT;
1112 _dragFromX = _dragToX = inE.getX();
1113 _dragFromY = _dragToY = inE.getY();
1118 // Not a click on a point, so check half-way between two (connected) trackpoints
1119 int midpointIndex = _midpoints.getNearestPointIndex(clickX, clickY, clickSens);
1120 if (midpointIndex > 0)
1122 _drawMode = MODE_CREATE_MIDPOINT;
1123 _clickedPoint = midpointIndex;
1124 _dragFromX = _dragToX = inE.getX();
1125 _dragFromY = _dragToY = inE.getY();
1131 // else right-press ignored
1135 * Respond to mouse released events
1136 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
1138 public void mouseReleased(MouseEvent inE)
1140 _recalculate = true;
1142 if (_drawMode == MODE_DRAG_POINT)
1144 if (Math.abs(_dragToX - _dragFromX) > 2
1145 || Math.abs(_dragToY - _dragFromY) > 2)
1147 movePointToMouse(_dragFromX, _dragFromY, _dragToX, _dragToY );
1149 _drawMode = MODE_DEFAULT;
1151 else if (_drawMode == MODE_CREATE_MIDPOINT)
1153 _drawMode = MODE_DEFAULT;
1154 _app.createPoint(createPointFromClick(_dragToX, _dragToY), _clickedPoint);
1156 else if (_drawMode == MODE_ZOOM_RECT)
1158 if (Math.abs(_dragToX - _dragFromX) > 20
1159 && Math.abs(_dragToY - _dragFromY) > 20)
1161 _mapPosition.zoomToPixels(_dragFromX, _dragToX, _dragFromY, _dragToY, getWidth(), getHeight());
1163 _drawMode = MODE_DEFAULT;
1165 else if (_drawMode == MODE_MARK_RECTANGLE)
1168 _app.setCurrentMode(App.AppMode.NORMAL);
1169 _drawMode = MODE_DEFAULT;
1170 // Call a function to mark the points
1171 MarkPointsInRectangleFunction marker = new MarkPointsInRectangleFunction(_app);
1172 double lon1 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragFromX, getWidth()));
1173 double lat1 = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_dragFromY, getHeight()));
1174 double lon2 = MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_dragToX, getWidth()));
1175 double lat2 = MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_dragToY, getHeight()));
1176 // Invalidate rectangle if pixel coords are (-1,-1)
1177 if (_dragFromX < 0 || _dragFromY < 0) {
1181 marker.setRectCoords(lon1, lat1, lon2, lat2);
1184 _dragFromX = _dragFromY = -1;
1189 * Respond to mouse drag events
1190 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
1192 public void mouseDragged(MouseEvent inE)
1194 if (!inE.isMetaDown())
1196 // Left mouse drag - either drag the point or pan the map
1197 if (_drawMode == MODE_DRAG_POINT || _drawMode == MODE_CREATE_MIDPOINT)
1200 _dragToX = inE.getX();
1201 _dragToY = inE.getY();
1202 _recalculate = true;
1205 else if (_drawMode == MODE_MARK_RECTANGLE)
1207 // draw a rectangle for marking points
1208 if (_dragFromX == -1) {
1209 _dragFromX = inE.getX();
1210 _dragFromY = inE.getY();
1212 _dragToX = inE.getX();
1213 _dragToY = inE.getY();
1218 // regular left-drag pans map by appropriate amount
1219 if (_dragFromX != -1)
1221 panMap(_dragFromX - inE.getX(), _dragFromY - inE.getY());
1223 _dragFromX = _dragToX = inE.getX();
1224 _dragFromY = _dragToY = inE.getY();
1229 // Right-click and drag - update rectangle
1230 _drawMode = MODE_ZOOM_RECT;
1231 if (_dragFromX == -1) {
1232 _dragFromX = inE.getX();
1233 _dragFromY = inE.getY();
1235 _dragToX = inE.getX();
1236 _dragToY = inE.getY();
1242 * Respond to mouse move events without button pressed
1243 * @param inEvent ignored
1245 public void mouseMoved(MouseEvent inEvent)
1247 boolean useCrosshairs = false;
1248 boolean useResize = false;
1249 // Ignore unless we're drawing points
1250 if (_drawMode == MODE_DRAW_POINTS_CONT)
1252 _dragToX = inEvent.getX();
1253 _dragToY = inEvent.getY();
1256 else if (_drawMode == MODE_MARK_RECTANGLE) {
1259 else if (_editmodeCheckBox.isSelected() || inEvent.isAltDown() || inEvent.isAltGraphDown())
1261 // Try to find a point or a midpoint at this location, and if there is one
1262 // then change the cursor to crosshairs
1263 final double clickX = _mapPosition.getXFromPixels(inEvent.getX(), getWidth());
1264 final double clickY = _mapPosition.getYFromPixels(inEvent.getY(), getHeight());
1265 final double clickSens = _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY);
1266 useCrosshairs = (_track.getNearestPointIndex(clickX, clickY, clickSens, false) >= 0
1267 || _midpoints.getNearestPointIndex(clickX, clickY, clickSens) >= 0
1270 if (useCrosshairs && !isCursorSet()) {
1271 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
1273 else if (useResize && !isCursorSet()) {
1274 setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
1276 else if (!useCrosshairs && !useResize && isCursorSet()) {
1282 * Respond to status bar message from broker
1283 * @param inMessage message, ignored
1285 public void actionCompleted(String inMessage)
1291 * Respond to data updated message from broker
1292 * @param inUpdateType type of update
1294 public void dataUpdated(byte inUpdateType)
1296 _recalculate = true;
1297 if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) {
1298 _checkBounds = true;
1300 if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
1301 _tileManager.resetConfig();
1303 if ((inUpdateType & (DataSubscriber.DATA_ADDED_OR_REMOVED + DataSubscriber.DATA_EDITED)) > 0) {
1304 _midpoints.updateData(_track);
1306 // See if rect mode has been activated
1307 if (_app.getCurrentMode() == App.AppMode.DRAWRECT)
1309 _drawMode = MODE_MARK_RECTANGLE;
1310 if (!isCursorSet()) {
1311 setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
1315 // enable or disable components
1316 boolean hasData = _track.getNumPoints() > 0;
1317 _topPanel.setVisible(hasData);
1318 _sidePanel.setVisible(hasData);
1319 // grab focus for the key presses
1320 this.requestFocus();
1324 * Respond to key presses on the map canvas
1325 * @param inE key event
1327 public void keyPressed(KeyEvent inE)
1329 int code = inE.getKeyCode();
1330 int currPointIndex = _selection.getCurrentPointIndex();
1331 // Check for Ctrl key (for Linux/Win) or meta key (Clover key for Mac)
1332 if (inE.isControlDown() || inE.isMetaDown())
1334 // Shift as well makes things faster
1335 final int pointIncrement = inE.isShiftDown()?3:1;
1336 // Check for arrow keys to zoom in and out
1337 if (code == KeyEvent.VK_UP)
1339 else if (code == KeyEvent.VK_DOWN)
1341 // Key nav for next/prev point
1342 else if (code == KeyEvent.VK_LEFT && currPointIndex > 0)
1343 _trackInfo.incrementPointIndex(-pointIncrement);
1344 else if (code == KeyEvent.VK_RIGHT)
1345 _trackInfo.incrementPointIndex(pointIncrement);
1346 else if (code == KeyEvent.VK_PAGE_UP)
1347 _trackInfo.selectPoint(Checker.getPreviousSegmentStart(
1348 _trackInfo.getTrack(), _trackInfo.getSelection().getCurrentPointIndex()));
1349 else if (code == KeyEvent.VK_PAGE_DOWN)
1350 _trackInfo.selectPoint(Checker.getNextSegmentStart(
1351 _trackInfo.getTrack(), _trackInfo.getSelection().getCurrentPointIndex()));
1352 // Check for home and end
1353 else if (code == KeyEvent.VK_HOME)
1354 _trackInfo.selectPoint(0);
1355 else if (code == KeyEvent.VK_END)
1356 _trackInfo.selectPoint(_trackInfo.getTrack().getNumPoints()-1);
1360 // Check for arrow keys to pan
1362 if (code == KeyEvent.VK_UP)
1363 upwardsPan = -PAN_DISTANCE;
1364 else if (code == KeyEvent.VK_DOWN)
1365 upwardsPan = PAN_DISTANCE;
1366 int rightwardsPan = 0;
1367 if (code == KeyEvent.VK_RIGHT)
1368 rightwardsPan = PAN_DISTANCE;
1369 else if (code == KeyEvent.VK_LEFT)
1370 rightwardsPan = -PAN_DISTANCE;
1371 panMap(rightwardsPan, upwardsPan);
1373 if (code == KeyEvent.VK_ESCAPE)
1374 _drawMode = MODE_DEFAULT;
1375 // Check for backspace key to delete current point (delete key already handled by menu)
1376 else if (code == KeyEvent.VK_BACK_SPACE && currPointIndex >= 0) {
1377 _app.deleteCurrentPoint();
1383 * @param inE key released event, ignored
1385 public void keyReleased(KeyEvent e)
1391 * @param inE key typed event, ignored
1393 public void keyTyped(KeyEvent inE)
1399 * @param inE mouse wheel event indicating scroll direction
1401 public void mouseWheelMoved(MouseWheelEvent inE)
1403 int clicks = inE.getWheelRotation();
1405 panMap((inE.getX() - getWidth()/2)/2, (inE.getY() - getHeight()/2)/2);
1408 else if (clicks > 0) {
1409 panMap(-(inE.getX() - getWidth()/2), -(inE.getY() - getHeight()/2));
1415 * @return current map position
1417 public MapPosition getMapPosition()
1419 return _mapPosition;