1 package tim.prune.gui.map;
3 import java.awt.BorderLayout;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics;
10 import java.awt.RenderingHints;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.event.ItemEvent;
14 import java.awt.event.ItemListener;
15 import java.awt.event.KeyEvent;
16 import java.awt.event.KeyListener;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseListener;
19 import java.awt.event.MouseMotionListener;
20 import java.awt.event.MouseWheelEvent;
21 import java.awt.event.MouseWheelListener;
22 import java.awt.image.BufferedImage;
23 import java.awt.image.RescaleOp;
25 import javax.swing.BorderFactory;
26 import javax.swing.BoxLayout;
27 import javax.swing.JButton;
28 import javax.swing.JCheckBox;
29 import javax.swing.JMenuItem;
30 import javax.swing.JPanel;
31 import javax.swing.JPopupMenu;
32 import javax.swing.JSlider;
33 import javax.swing.event.ChangeEvent;
34 import javax.swing.event.ChangeListener;
37 import tim.prune.DataSubscriber;
38 import tim.prune.FunctionLibrary;
39 import tim.prune.I18nManager;
40 import tim.prune.data.DoubleRange;
41 import tim.prune.data.Selection;
42 import tim.prune.data.Track;
43 import tim.prune.data.TrackInfo;
44 import tim.prune.gui.IconManager;
47 * Class for the map canvas, to display a background map and draw on it
49 public class MapCanvas extends JPanel implements MouseListener, MouseMotionListener, DataSubscriber,
50 KeyListener, MouseWheelListener
52 /** App object for callbacks */
53 private App _app = null;
55 private Track _track = null;
56 /** Selection object */
57 private Selection _selection = null;
58 /** Previously selected point */
59 private int _prevSelectedPoint = -1;
61 private MapTileCacher _tileCacher = new MapTileCacher(this);
62 /** Image to display */
63 private BufferedImage _mapImage = null;
64 /** Slider for transparency */
65 private JSlider _transparencySlider = null;
66 /** Checkbox for maps */
67 private JCheckBox _mapCheckBox = null;
68 /** Checkbox for autopan */
69 private JCheckBox _autopanCheckBox = null;
70 /** Checkbox for connecting track points */
71 private JCheckBox _connectCheckBox = null;
72 /** Right-click popup menu */
73 private JPopupMenu _popup = null;
74 /** Top component panel */
75 private JPanel _topPanel = null;
76 /** Side component panel */
77 private JPanel _sidePanel = null;
79 private DoubleRange _latRange = null, _lonRange = null;
80 private DoubleRange _xRange = null, _yRange = null;
81 private boolean _recalculate = false;
82 /** Flag to check bounds on next paint */
83 private boolean _checkBounds = false;
85 private MapPosition _mapPosition = null;
86 /** x coordinate of drag from point */
87 private int _dragFromX = -1;
88 /** y coordinate of drag from point */
89 private int _dragFromY = -1;
90 /** Flag set to true for right-click dragging */
91 private boolean _zoomDragging = false;
92 /** x coordinate of drag to point */
93 private int _dragToX = -1;
94 /** y coordinate of drag to point */
95 private int _dragToY = -1;
96 /** x coordinate of popup menu */
97 private int _popupMenuX = -1;
98 /** y coordinate of popup menu */
99 private int _popupMenuY = -1;
100 /** Flag to prevent showing too often the error message about loading maps */
101 private boolean _shownOsmErrorAlready = false;
103 /** Constant for click sensitivity when selecting nearest point */
104 private static final int CLICK_SENSITIVITY = 10;
105 /** Constant for pan distance from key presses */
106 private static final int PAN_DISTANCE = 20;
107 /** Constant for pan distance from autopan */
108 private static final int AUTOPAN_DISTANCE = 75;
111 private static final Color COLOR_BG = Color.WHITE;
112 private static final Color COLOR_MESSAGES = Color.GRAY;
113 private static final Color COLOR_POINT = Color.BLUE;
114 private static final Color COLOR_POINT_DELETED = Color.RED;
115 private static final Color COLOR_CURR_RANGE = Color.GREEN;
116 private static final Color COLOR_CROSSHAIRS = Color.RED;
117 private static final Color COLOR_WAYPT_NAME = Color.BLACK;
118 private static final Color COLOR_PHOTO_PT = Color.ORANGE;
123 * @param inApp App object for callbacks
124 * @param inTrackInfo track info object
126 public MapCanvas(App inApp, TrackInfo inTrackInfo)
129 _track = inTrackInfo.getTrack();
130 _selection = inTrackInfo.getSelection();
131 _mapPosition = new MapPosition();
132 addMouseListener(this);
133 addMouseMotionListener(this);
134 addMouseWheelListener(this);
135 addKeyListener(this);
137 // Make listener for changes to controls
138 ItemListener itemListener = new ItemListener() {
139 public void itemStateChanged(ItemEvent e)
145 // Make special listener for changes to map checkbox
146 ItemListener mapCheckListener = new ItemListener() {
147 public void itemStateChanged(ItemEvent e)
149 _tileCacher.clearAll();
154 _topPanel = new JPanel();
155 _topPanel.setLayout(new FlowLayout());
156 _topPanel.setOpaque(false);
157 // Make slider for transparency
158 _transparencySlider = new JSlider(0, 5, 0);
159 _transparencySlider.setPreferredSize(new Dimension(100, 20));
160 _transparencySlider.setMajorTickSpacing(1);
161 _transparencySlider.setSnapToTicks(true);
162 _transparencySlider.setOpaque(false);
163 _transparencySlider.addChangeListener(new ChangeListener() {
164 public void stateChanged(ChangeEvent e)
170 _transparencySlider.setFocusable(false); // stop slider from stealing keyboard focus
171 _topPanel.add(_transparencySlider);
172 // Add checkbox button for enabling maps or not
173 _mapCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.MAP_BUTTON), false);
174 _mapCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.MAP_BUTTON_ON));
175 _mapCheckBox.setOpaque(false);
176 _mapCheckBox.setToolTipText(I18nManager.getText("menu.map.showmap"));
177 _mapCheckBox.addItemListener(mapCheckListener);
178 _mapCheckBox.setFocusable(false); // stop button from stealing keyboard focus
179 _topPanel.add(_mapCheckBox);
180 // Add checkbox button for enabling autopan or not
181 _autopanCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON), true);
182 _autopanCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON_ON));
183 _autopanCheckBox.setOpaque(false);
184 _autopanCheckBox.setToolTipText(I18nManager.getText("menu.map.autopan"));
185 _autopanCheckBox.addItemListener(itemListener);
186 _autopanCheckBox.setFocusable(false); // stop button from stealing keyboard focus
187 _topPanel.add(_autopanCheckBox);
188 // Add checkbox button for connecting points or not
189 _connectCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON), true);
190 _connectCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
191 _connectCheckBox.setOpaque(false);
192 _connectCheckBox.setToolTipText(I18nManager.getText("menu.map.connect"));
193 _connectCheckBox.addItemListener(itemListener);
194 _connectCheckBox.setFocusable(false); // stop button from stealing keyboard focus
195 _topPanel.add(_connectCheckBox);
197 // Add zoom in, zoom out buttons
198 _sidePanel = new JPanel();
199 _sidePanel.setLayout(new BoxLayout(_sidePanel, BoxLayout.Y_AXIS));
200 _sidePanel.setOpaque(false);
201 JButton zoomInButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_IN_BUTTON));
202 zoomInButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
203 zoomInButton.setContentAreaFilled(false);
204 zoomInButton.setToolTipText(I18nManager.getText("menu.map.zoomin"));
205 zoomInButton.addActionListener(new ActionListener() {
206 public void actionPerformed(ActionEvent e)
211 zoomInButton.setFocusable(false); // stop button from stealing keyboard focus
212 _sidePanel.add(zoomInButton);
213 JButton zoomOutButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_OUT_BUTTON));
214 zoomOutButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
215 zoomOutButton.setContentAreaFilled(false);
216 zoomOutButton.setToolTipText(I18nManager.getText("menu.map.zoomout"));
217 zoomOutButton.addActionListener(new ActionListener() {
218 public void actionPerformed(ActionEvent e)
223 zoomOutButton.setFocusable(false); // stop button from stealing keyboard focus
224 _sidePanel.add(zoomOutButton);
226 // add control panels to this one
227 setLayout(new BorderLayout());
228 _topPanel.setVisible(false);
229 _sidePanel.setVisible(false);
230 add(_topPanel, BorderLayout.NORTH);
231 add(_sidePanel, BorderLayout.WEST);
238 * Make the popup menu for right-clicking the map
240 private void makePopup()
242 _popup = new JPopupMenu();
243 JMenuItem zoomInItem = new JMenuItem(I18nManager.getText("menu.map.zoomin"));
244 zoomInItem.addActionListener(new ActionListener() {
245 public void actionPerformed(ActionEvent e)
249 zoomInItem.setEnabled(true);
250 _popup.add(zoomInItem);
251 JMenuItem zoomOutItem = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
252 zoomOutItem.addActionListener(new ActionListener() {
253 public void actionPerformed(ActionEvent e)
257 zoomOutItem.setEnabled(true);
258 _popup.add(zoomOutItem);
259 JMenuItem zoomFullItem = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
260 zoomFullItem.addActionListener(new ActionListener() {
261 public void actionPerformed(ActionEvent e)
267 zoomFullItem.setEnabled(true);
268 _popup.add(zoomFullItem);
269 _popup.addSeparator();
271 JMenuItem setMapBgItem = new JMenuItem(
272 I18nManager.getText(FunctionLibrary.FUNCTION_SET_MAP_BG.getNameKey()));
273 setMapBgItem.addActionListener(new ActionListener() {
274 public void actionPerformed(ActionEvent e)
276 FunctionLibrary.FUNCTION_SET_MAP_BG.begin();
278 _popup.add(setMapBgItem);
280 JMenuItem newPointItem = new JMenuItem(I18nManager.getText("menu.map.newpoint"));
281 newPointItem.addActionListener(new ActionListener() {
282 public void actionPerformed(ActionEvent e)
284 _app.createPoint(MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight())),
285 MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth())));
287 newPointItem.setEnabled(true);
288 _popup.add(newPointItem);
293 * Zoom to fit the current data area
295 private void zoomToFit()
297 _latRange = _track.getLatRange();
298 _lonRange = _track.getLonRange();
299 _xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
300 MapUtils.getXFromLongitude(_lonRange.getMaximum()));
301 _yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
302 MapUtils.getYFromLatitude(_latRange.getMaximum()));
303 _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
304 getWidth(), getHeight());
310 * @see java.awt.Canvas#paint(java.awt.Graphics)
312 public void paint(Graphics inG)
315 if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
318 if (_track.getNumPoints() > 0)
320 // Check for autopan if enabled / necessary
321 if (_autopanCheckBox.isSelected())
323 int selectedPoint = _selection.getCurrentPointIndex();
324 if (selectedPoint >= 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
326 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
327 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
330 if (px < PAN_DISTANCE) {
331 panX = px - AUTOPAN_DISTANCE;
333 else if (px > (getWidth()-PAN_DISTANCE)) {
334 panX = AUTOPAN_DISTANCE + px - getWidth();
336 if (py < PAN_DISTANCE) {
337 panY = py - AUTOPAN_DISTANCE;
339 if (py > (getHeight()-PAN_DISTANCE)) {
340 panY = AUTOPAN_DISTANCE + py - getHeight();
342 if (panX != 0 || panY != 0) {
343 _mapPosition.pan(panX, panY);
346 _prevSelectedPoint = selectedPoint;
349 // Draw the mapImage if necessary
350 if ((_mapImage == null || _recalculate)) {
353 // Draw the prepared image onto the panel
354 if (_mapImage != null) {
355 inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
357 // Draw the zoom rectangle if necessary
360 inG.setColor(Color.RED);
361 inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
362 inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
363 inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
364 inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
369 inG.setColor(COLOR_BG);
370 inG.fillRect(0, 0, getWidth(), getHeight());
371 inG.setColor(COLOR_MESSAGES);
372 inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
374 // Draw slider etc on top
380 * Get the map tiles for the current zoom level and given tile parameters
382 private void getMapTiles()
384 if (_mapImage == null || _mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())
386 _mapImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
390 Graphics g = _mapImage.getGraphics();
392 g.setColor(COLOR_BG);
393 g.fillRect(0, 0, getWidth(), getHeight());
395 // reset error message
396 if (!_mapCheckBox.isSelected()) {_shownOsmErrorAlready = false;}
397 // Only get map tiles if selected
398 if (_mapCheckBox.isSelected())
401 _tileCacher.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
403 boolean loadingFailed = false;
404 if (_mapImage == null) return;
406 if (_tileCacher.isOverzoomed())
408 // display overzoom message
409 g.setColor(COLOR_MESSAGES);
410 g.drawString(I18nManager.getText("map.overzoom"), 50, getHeight()/2);
414 // Loop over tiles drawing each one
415 int[] tileIndices = _mapPosition.getTileIndices(getWidth(), getHeight());
416 int[] pixelOffsets = _mapPosition.getDisplayOffsets(getWidth(), getHeight());
417 for (int tileX = tileIndices[0]; tileX <= tileIndices[1] && !loadingFailed; tileX++)
419 int x = (tileX - tileIndices[0]) * 256 - pixelOffsets[0];
420 for (int tileY = tileIndices[2]; tileY <= tileIndices[3]; tileY++)
422 int y = (tileY - tileIndices[2]) * 256 - pixelOffsets[1];
423 Image image = _tileCacher.getTile(tileX, tileY);
425 g.drawImage(image, x, y, 256, 256, null);
430 // Make maps brighter / fainter
431 float[] scaleFactors = {1.0f, 1.05f, 1.1f, 1.2f, 1.6f, 2.0f};
432 float scaleFactor = scaleFactors[_transparencySlider.getValue()];
433 if (scaleFactor > 1.0f) {
434 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
435 hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
436 RescaleOp op = new RescaleOp(scaleFactor, 0, hints);
437 op.filter(_mapImage, _mapImage);
442 // Paint the track points on top
443 int pointsPainted = 1;
446 pointsPainted = paintPoints(g);
448 catch (NullPointerException npe) { // ignore, probably due to data being changed during drawing
454 _recalculate = false;
455 // Zoom to fit if no points found
456 if (pointsPainted <= 0 && _checkBounds) {
461 _checkBounds = false;
462 // enable / disable transparency slider
463 _transparencySlider.setEnabled(_mapCheckBox.isSelected());
468 * Paint the points using the given graphics object
469 * @param inG Graphics object to use for painting
470 * @return number of points painted, if any
472 private int paintPoints(Graphics inG)
474 int pointsPainted = 0;
476 inG.setColor(COLOR_POINT);
477 int prevX = -1, prevY = -1;
478 boolean connectPoints = _connectCheckBox.isSelected();
479 boolean prevPointVisible = false, currPointVisible = false;
480 for (int i=0; i<_track.getNumPoints(); i++)
482 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
483 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
484 currPointVisible = px >= 0 && px < getWidth() && py >= 0 && py < getHeight();
485 if (currPointVisible)
487 if (!_track.getPoint(i).isWaypoint())
489 // Draw rectangle for track point
490 if (_track.getPoint(i).getDeleteFlag()) {
491 inG.setColor(COLOR_POINT_DELETED);
494 inG.setColor(COLOR_POINT);
496 inG.drawRect(px-2, py-2, 3, 3);
500 if (!_track.getPoint(i).isWaypoint())
502 // Connect track points if either of them are visible
503 if (connectPoints && (currPointVisible || prevPointVisible)
504 && !(prevX == -1 && prevY == -1)
505 && !_track.getPoint(i).getSegmentStart())
507 inG.drawLine(prevX, prevY, px, py);
509 prevX = px; prevY = py;
511 prevPointVisible = currPointVisible;
514 // Loop over points, just drawing blobs for waypoints
515 inG.setColor(COLOR_WAYPT_NAME);
516 FontMetrics fm = inG.getFontMetrics();
517 int nameHeight = fm.getHeight();
518 int width = getWidth();
519 int height = getHeight();
520 for (int i=0; i<_track.getNumPoints(); i++)
522 if (_track.getPoint(i).isWaypoint())
524 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
525 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
526 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
528 inG.fillRect(px-3, py-3, 6, 6);
533 // Loop over points again, now draw names for waypoints
534 for (int i=0; i<_track.getNumPoints(); i++)
536 if (_track.getPoint(i).isWaypoint())
538 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
539 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
540 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
542 // Figure out where to draw waypoint name so it doesn't obscure track
543 String waypointName = _track.getPoint(i).getWaypointName();
544 int nameWidth = fm.stringWidth(waypointName);
545 boolean drawnName = false;
546 // Make arrays for coordinates right left up down
547 int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
548 int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
549 for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
551 // Shift arrays for coordinates right left up down
552 nameXs[0] += 2; nameXs[1] -= 2;
553 nameYs[2] -= 2; nameYs[3] += 2;
554 // Check each direction in turn right left up down
555 for (int a=0; a<4; a++)
557 if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < width
558 && nameYs[a] < height && (nameYs[a] - nameHeight) > 0
559 && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight))
561 // Found a rectangle to fit - draw name here and quit
562 inG.drawString(waypointName, nameXs[a], nameYs[a]);
571 // Loop over points, drawing blobs for photo points
572 inG.setColor(COLOR_PHOTO_PT);
573 for (int i=0; i<_track.getNumPoints(); i++)
575 if (_track.getPoint(i).getPhoto() != null)
577 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
578 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
579 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
581 inG.drawRect(px-1, py-1, 2, 2);
582 inG.drawRect(px-2, py-2, 4, 4);
588 // Draw selected range
589 if (_selection.hasRangeSelected())
591 inG.setColor(COLOR_CURR_RANGE);
592 for (int i=_selection.getStart(); i<=_selection.getEnd(); i++)
594 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(i));
595 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(i));
596 inG.drawRect(px-1, py-1, 2, 2);
600 // Draw selected point, crosshairs
601 int selectedPoint = _selection.getCurrentPointIndex();
602 if (selectedPoint >= 0)
604 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getX(selectedPoint));
605 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getY(selectedPoint));
606 inG.setColor(COLOR_CROSSHAIRS);
608 inG.drawLine(px, 0, px, getHeight());
609 inG.drawLine(0, py, getWidth(), py);
611 inG.drawOval(px - 2, py - 2, 4, 4);
612 inG.drawOval(px - 3, py - 3, 6, 6);
614 // Return the number of points painted
615 return pointsPainted;
620 * Tests whether there are any dark pixels within the specified x,y rectangle
621 * @param inX left X coordinate
622 * @param inY bottom Y coordinate
623 * @param inWidth width of rectangle
624 * @param inHeight height of rectangle
625 * @return true if there's at least one data point in the rectangle
627 private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
629 // each of the colour channels must be brighter than this to count as empty
630 final int BRIGHTNESS_LIMIT = 210;
633 // loop over x coordinate of rectangle
634 for (int x=0; x<inWidth; x++)
636 // loop over y coordinate of rectangle
637 for (int y=0; y<inHeight; y++)
639 int pixelColor = _mapImage.getRGB(inX + x, inY - y);
640 // split into four components rgba
641 int lowestBit = pixelColor & 255;
642 int secondBit = (pixelColor >> 8) & 255;
643 int thirdBit = (pixelColor >> 16) & 255;
644 //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
645 if (lowestBit < BRIGHTNESS_LIMIT || secondBit < BRIGHTNESS_LIMIT || thirdBit < BRIGHTNESS_LIMIT) return true;
649 catch (NullPointerException e) {
650 // ignore null pointers, just return false
657 * Inform that tiles have been updated and the map can be repainted
658 * @param inIsOk true if data loaded ok, false for error
660 public synchronized void tilesUpdated(boolean inIsOk)
662 // Show message if loading failed (but not too many times)
663 if (!inIsOk && !_shownOsmErrorAlready && _mapCheckBox.isSelected())
665 _shownOsmErrorAlready = true;
666 // use separate thread to show message about failing to load osm images
667 new Thread(new Runnable() {
669 try {Thread.sleep(500);} catch (InterruptedException ie) {}
670 _app.showErrorMessage("error.osmimage.dialogtitle", "error.osmimage.failed");
679 * Zoom out, if not already at minimum zoom
681 public void zoomOut()
683 _mapPosition.zoomOut();
689 * Zoom in, if not already at maximum zoom
693 _mapPosition.zoomIn();
700 * @param inDeltaX x shift
701 * @param inDeltaY y shift
703 public void panMap(int inDeltaX, int inDeltaY)
705 _mapPosition.pan(inDeltaX, inDeltaY);
711 * @see javax.swing.JComponent#getMinimumSize()
713 public Dimension getMinimumSize()
715 final Dimension minSize = new Dimension(512, 300);
720 * @see javax.swing.JComponent#getPreferredSize()
722 public Dimension getPreferredSize()
724 return getMinimumSize();
729 * Respond to mouse click events
730 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
732 public void mouseClicked(MouseEvent inE)
734 if (_track != null && _track.getNumPoints() > 0)
736 // select point if it's a left-click
737 if (!inE.isMetaDown())
739 int pointIndex = _track.getNearestPointIndex(
740 _mapPosition.getXFromPixels(inE.getX(), getWidth()),
741 _mapPosition.getYFromPixels(inE.getY(), getHeight()),
742 _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
743 _selection.selectPoint(pointIndex);
747 // show the popup menu for right-clicks
748 _popupMenuX = inE.getX();
749 _popupMenuY = inE.getY();
750 _popup.show(this, _popupMenuX, _popupMenuY);
756 * Ignore mouse enter events
757 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
759 public void mouseEntered(MouseEvent inE)
765 * Ignore mouse exited events
766 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
768 public void mouseExited(MouseEvent inE)
774 * Ignore mouse pressed events
775 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
777 public void mousePressed(MouseEvent inE)
783 * Respond to mouse released events
784 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
786 public void mouseReleased(MouseEvent inE)
789 if (_zoomDragging && Math.abs(_dragToX - _dragFromX) > 20 && Math.abs(_dragToY - _dragFromY) > 20)
791 //System.out.println("Finished zoom: " + _dragFromX + ", " + _dragFromY + " to " + _dragToX + ", " + _dragToY);
792 _mapPosition.zoomToPixels(_dragFromX, _dragToX, _dragFromY, _dragToY, getWidth(), getHeight());
794 _dragFromX = _dragFromY = -1;
795 _zoomDragging = false;
800 * Respond to mouse drag events
801 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
803 public void mouseDragged(MouseEvent inE)
805 if (!inE.isMetaDown())
807 // Left mouse drag - pan map by appropriate amount
808 _zoomDragging = false;
809 if (_dragFromX != -1)
811 panMap(_dragFromX - inE.getX(), _dragFromY - inE.getY());
815 _dragFromX = inE.getX();
816 _dragFromY = inE.getY();
820 // Right-click and drag - draw rectangle and control zoom
821 _zoomDragging = true;
822 if (_dragFromX == -1) {
823 _dragFromX = inE.getX();
824 _dragFromY = inE.getY();
826 _dragToX = inE.getX();
827 _dragToY = inE.getY();
833 * Respond to mouse move events without button pressed
834 * @param inEvent ignored
836 public void mouseMoved(MouseEvent inEvent)
842 * Respond to status bar message from broker
843 * @param inMessage message, ignored
845 public void actionCompleted(String inMessage)
851 * Respond to data updated message from broker
852 * @param inUpdateType type of update
854 public void dataUpdated(byte inUpdateType)
857 if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) {
860 if ((inUpdateType & DataSubscriber.MAPSERVER_CHANGED) > 0) {
861 _tileCacher.setTileConfig(new MapTileConfig());
864 // enable or disable components
865 boolean hasData = _track.getNumPoints() > 0;
866 _topPanel.setVisible(hasData);
867 _sidePanel.setVisible(hasData);
868 // grab focus for the key presses
873 * Respond to key presses on the map canvas
874 * @param inE key event
876 public void keyPressed(KeyEvent inE)
878 int code = inE.getKeyCode();
879 // Check for meta key
880 if (inE.isControlDown())
882 // Check for arrow keys to zoom in and out
883 if (code == KeyEvent.VK_UP)
885 else if (code == KeyEvent.VK_DOWN)
887 // Key nav for next/prev point
888 else if (code == KeyEvent.VK_LEFT)
889 _selection.selectPreviousPoint();
890 else if (code == KeyEvent.VK_RIGHT)
891 _selection.selectNextPoint();
895 // Check for arrow keys to pan
897 if (code == KeyEvent.VK_UP)
898 upwardsPan = -PAN_DISTANCE;
899 else if (code == KeyEvent.VK_DOWN)
900 upwardsPan = PAN_DISTANCE;
901 int rightwardsPan = 0;
902 if (code == KeyEvent.VK_RIGHT)
903 rightwardsPan = PAN_DISTANCE;
904 else if (code == KeyEvent.VK_LEFT)
905 rightwardsPan = -PAN_DISTANCE;
906 panMap(rightwardsPan, upwardsPan);
907 // Check for delete key to delete current point
908 if (code == KeyEvent.VK_DELETE && _selection.getCurrentPointIndex() >= 0)
910 _app.deleteCurrentPoint();
916 * @param inE key released event, ignored
918 public void keyReleased(KeyEvent e)
924 * @param inE key typed event, ignored
926 public void keyTyped(KeyEvent inE)
932 * @param inE mouse wheel event indicating scroll direction
934 public void mouseWheelMoved(MouseWheelEvent inE)
936 int clicks = inE.getWheelRotation();