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.JOptionPane;
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.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_POINT = Color.BLUE;
113 private static final Color COLOR_CURR_RANGE = Color.GREEN;
114 private static final Color COLOR_CROSSHAIRS = Color.RED;
115 private static final Color COLOR_WAYPT_NAME = Color.BLACK;
116 private static final Color COLOR_PHOTO_PT = Color.ORANGE;
121 * @param inApp App object for callbacks
122 * @param inTrackInfo track info object
124 public MapCanvas(App inApp, TrackInfo inTrackInfo)
127 _track = inTrackInfo.getTrack();
128 _selection = inTrackInfo.getSelection();
129 _mapPosition = new MapPosition();
130 addMouseListener(this);
131 addMouseMotionListener(this);
132 addMouseWheelListener(this);
133 addKeyListener(this);
135 // Make listener for changes to controls
136 ItemListener itemListener = new ItemListener() {
137 public void itemStateChanged(ItemEvent e)
143 // Make special listener for changes to map checkbox
144 ItemListener mapCheckListener = new ItemListener() {
145 public void itemStateChanged(ItemEvent e)
147 _tileCacher.clearAll();
152 _topPanel = new JPanel();
153 _topPanel.setLayout(new FlowLayout());
154 _topPanel.setOpaque(false);
155 // Make slider for transparency
156 _transparencySlider = new JSlider(0, 5, 0);
157 _transparencySlider.setPreferredSize(new Dimension(100, 20));
158 _transparencySlider.setMajorTickSpacing(1);
159 _transparencySlider.setSnapToTicks(true);
160 _transparencySlider.setOpaque(false);
161 _transparencySlider.addChangeListener(new ChangeListener() {
162 public void stateChanged(ChangeEvent e)
168 _transparencySlider.setFocusable(false); // stop slider from stealing keyboard focus
169 _topPanel.add(_transparencySlider);
170 // Add checkbox button for enabling maps or not
171 _mapCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.MAP_BUTTON), false);
172 _mapCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.MAP_BUTTON_ON));
173 _mapCheckBox.setOpaque(false);
174 _mapCheckBox.setToolTipText(I18nManager.getText("menu.map.showmap"));
175 _mapCheckBox.addItemListener(mapCheckListener);
176 _mapCheckBox.setFocusable(false); // stop button from stealing keyboard focus
177 _topPanel.add(_mapCheckBox);
178 // Add checkbox button for enabling autopan or not
179 _autopanCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON), true);
180 _autopanCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.AUTOPAN_BUTTON_ON));
181 _autopanCheckBox.setOpaque(false);
182 _autopanCheckBox.setToolTipText(I18nManager.getText("menu.map.autopan"));
183 _autopanCheckBox.addItemListener(itemListener);
184 _autopanCheckBox.setFocusable(false); // stop button from stealing keyboard focus
185 _topPanel.add(_autopanCheckBox);
186 // Add checkbox button for connecting points or not
187 _connectCheckBox = new JCheckBox(IconManager.getImageIcon(IconManager.POINTS_DISCONNECTED_BUTTON), true);
188 _connectCheckBox.setSelectedIcon(IconManager.getImageIcon(IconManager.POINTS_CONNECTED_BUTTON));
189 _connectCheckBox.setOpaque(false);
190 _connectCheckBox.setToolTipText(I18nManager.getText("menu.map.connect"));
191 _connectCheckBox.addItemListener(itemListener);
192 _connectCheckBox.setFocusable(false); // stop button from stealing keyboard focus
193 _topPanel.add(_connectCheckBox);
195 // Add zoom in, zoom out buttons
196 _sidePanel = new JPanel();
197 _sidePanel.setLayout(new BoxLayout(_sidePanel, BoxLayout.Y_AXIS));
198 _sidePanel.setOpaque(false);
199 JButton zoomInButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_IN_BUTTON));
200 zoomInButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
201 zoomInButton.setContentAreaFilled(false);
202 zoomInButton.setToolTipText(I18nManager.getText("menu.map.zoomin"));
203 zoomInButton.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e)
209 zoomInButton.setFocusable(false); // stop button from stealing keyboard focus
210 _sidePanel.add(zoomInButton);
211 JButton zoomOutButton = new JButton(IconManager.getImageIcon(IconManager.ZOOM_OUT_BUTTON));
212 zoomOutButton.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
213 zoomOutButton.setContentAreaFilled(false);
214 zoomOutButton.setToolTipText(I18nManager.getText("menu.map.zoomout"));
215 zoomOutButton.addActionListener(new ActionListener() {
216 public void actionPerformed(ActionEvent e)
221 zoomOutButton.setFocusable(false); // stop button from stealing keyboard focus
222 _sidePanel.add(zoomOutButton);
224 // add control panels to this one
225 setLayout(new BorderLayout());
226 _topPanel.setVisible(false);
227 _sidePanel.setVisible(false);
228 add(_topPanel, BorderLayout.NORTH);
229 add(_sidePanel, BorderLayout.WEST);
236 * Make the popup menu for right-clicking the map
238 private void makePopup()
240 _popup = new JPopupMenu();
241 JMenuItem zoomInItem = new JMenuItem(I18nManager.getText("menu.map.zoomin"));
242 zoomInItem.addActionListener(new ActionListener() {
243 public void actionPerformed(ActionEvent e)
247 zoomInItem.setEnabled(true);
248 _popup.add(zoomInItem);
249 JMenuItem zoomOutItem = new JMenuItem(I18nManager.getText("menu.map.zoomout"));
250 zoomOutItem.addActionListener(new ActionListener() {
251 public void actionPerformed(ActionEvent e)
255 zoomOutItem.setEnabled(true);
256 _popup.add(zoomOutItem);
257 JMenuItem zoomFullItem = new JMenuItem(I18nManager.getText("menu.map.zoomfull"));
258 zoomFullItem.addActionListener(new ActionListener() {
259 public void actionPerformed(ActionEvent e)
265 zoomFullItem.setEnabled(true);
266 _popup.add(zoomFullItem);
268 JMenuItem newPointItem = new JMenuItem(I18nManager.getText("menu.map.newpoint"));
269 newPointItem.addActionListener(new ActionListener() {
270 public void actionPerformed(ActionEvent e)
272 _app.createPoint(MapUtils.getLatitudeFromY(_mapPosition.getYFromPixels(_popupMenuY, getHeight())),
273 MapUtils.getLongitudeFromX(_mapPosition.getXFromPixels(_popupMenuX, getWidth())));
275 newPointItem.setEnabled(true);
276 _popup.add(newPointItem);
281 * Zoom to fit the current data area
283 private void zoomToFit()
285 _latRange = _track.getLatRange();
286 _lonRange = _track.getLonRange();
287 _xRange = new DoubleRange(MapUtils.getXFromLongitude(_lonRange.getMinimum()),
288 MapUtils.getXFromLongitude(_lonRange.getMaximum()));
289 _yRange = new DoubleRange(MapUtils.getYFromLatitude(_latRange.getMinimum()),
290 MapUtils.getYFromLatitude(_latRange.getMaximum()));
291 _mapPosition.zoomToXY(_xRange.getMinimum(), _xRange.getMaximum(), _yRange.getMinimum(), _yRange.getMaximum(),
292 getWidth(), getHeight());
298 * @see java.awt.Canvas#paint(java.awt.Graphics)
300 public void paint(Graphics inG)
303 if (_mapImage != null && (_mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())) {
306 if (_track.getNumPoints() > 0)
308 // Check for autopan if enabled / necessary
309 if (_autopanCheckBox.isSelected())
311 int selectedPoint = _selection.getCurrentPointIndex();
312 if (selectedPoint > 0 && _dragFromX == -1 && selectedPoint != _prevSelectedPoint)
314 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(selectedPoint));
315 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(selectedPoint));
318 if (px < PAN_DISTANCE) {
319 panX = px - AUTOPAN_DISTANCE;
321 else if (px > (getWidth()-PAN_DISTANCE)) {
322 panX = AUTOPAN_DISTANCE + px - getWidth();
324 if (py < PAN_DISTANCE) {
325 panY = py - AUTOPAN_DISTANCE;
327 if (py > (getHeight()-PAN_DISTANCE)) {
328 panY = AUTOPAN_DISTANCE + py - getHeight();
330 if (panX != 0 || panY != 0) {
331 _mapPosition.pan(panX, panY);
334 _prevSelectedPoint = selectedPoint;
337 // Draw the mapImage if necessary
338 if ((_mapImage == null || _recalculate)) {
341 // Draw the prepared image onto the panel
342 if (_mapImage != null) {
343 inG.drawImage(_mapImage, 0, 0, getWidth(), getHeight(), null);
345 // Draw the zoom rectangle if necessary
348 inG.setColor(Color.RED);
349 inG.drawLine(_dragFromX, _dragFromY, _dragFromX, _dragToY);
350 inG.drawLine(_dragFromX, _dragFromY, _dragToX, _dragFromY);
351 inG.drawLine(_dragToX, _dragFromY, _dragToX, _dragToY);
352 inG.drawLine(_dragFromX, _dragToY, _dragToX, _dragToY);
357 inG.setColor(COLOR_BG);
358 inG.fillRect(0, 0, getWidth(), getHeight());
359 inG.setColor(Color.GRAY);
360 inG.drawString(I18nManager.getText("display.nodata"), 50, getHeight()/2);
362 // Draw slider etc on top
368 * Get the map tiles for the current zoom level and given tile parameters
370 private void getMapTiles()
372 if (_mapImage == null || _mapImage.getWidth() != getWidth() || _mapImage.getHeight() != getHeight())
374 _mapImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
378 Graphics g = _mapImage.getGraphics();
380 g.setColor(COLOR_BG);
381 g.fillRect(0, 0, getWidth(), getHeight());
383 // reset error message
384 if (!_mapCheckBox.isSelected()) {_shownOsmErrorAlready = false;}
385 // Only get map tiles if selected
386 if (_mapCheckBox.isSelected())
389 _tileCacher.centreMap(_mapPosition.getZoom(), _mapPosition.getCentreTileX(), _mapPosition.getCentreTileY());
391 boolean loadingFailed = false;
392 if (_mapImage == null) return;
394 // Loop over tiles drawing each one
395 int[] tileIndices = _mapPosition.getTileIndices(getWidth(), getHeight());
396 int[] pixelOffsets = _mapPosition.getDisplayOffsets(getWidth(), getHeight());
397 for (int tileX = tileIndices[0]; tileX <= tileIndices[1] && !loadingFailed; tileX++)
399 int x = (tileX - tileIndices[0]) * 256 - pixelOffsets[0];
400 for (int tileY = tileIndices[2]; tileY <= tileIndices[3]; tileY++)
402 int y = (tileY - tileIndices[2]) * 256 - pixelOffsets[1];
403 Image image = _tileCacher.getTile(tileX, tileY);
405 g.drawImage(image, x, y, 256, 256, null);
410 // Make maps brighter / fainter
411 float[] scaleFactors = {1.0f, 1.05f, 1.1f, 1.2f, 1.6f, 2.0f};
412 float scaleFactor = scaleFactors[_transparencySlider.getValue()];
413 if (scaleFactor > 1.0f) {
414 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
415 hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
416 RescaleOp op = new RescaleOp(scaleFactor, 0, hints);
417 op.filter(_mapImage, _mapImage);
421 int pointsPainted = 0;
423 g.setColor(COLOR_POINT);
424 int prevX = -1, prevY = -1;
425 boolean connectPoints = _connectCheckBox.isSelected();
426 for (int i=0; i<_track.getNumPoints(); i++)
428 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
429 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
430 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
432 if (!_track.getPoint(i).isWaypoint())
434 g.drawRect(px-2, py-2, 3, 3);
435 // Connect track points
436 if (connectPoints && prevX != -1 && prevY != -1 && !_track.getPoint(i).getSegmentStart()) {
437 g.drawLine(prevX, prevY, px, py);
440 prevX = px; prevY = py;
444 prevX = -1; prevY = -1;
448 // Loop over points, just drawing blobs for waypoints
449 g.setColor(COLOR_WAYPT_NAME);
450 FontMetrics fm = g.getFontMetrics();
451 int nameHeight = fm.getHeight();
452 int width = getWidth();
453 int height = getHeight();
454 for (int i=0; i<_track.getNumPoints(); i++)
456 if (_track.getPoint(i).isWaypoint())
458 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
459 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
460 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
462 g.fillRect(px-3, py-3, 6, 6);
467 // Loop over points again, now draw names for waypoints
468 for (int i=0; i<_track.getNumPoints(); i++)
470 if (_track.getPoint(i).isWaypoint())
472 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
473 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
474 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
476 // Figure out where to draw waypoint name so it doesn't obscure track
477 String waypointName = _track.getPoint(i).getWaypointName();
478 int nameWidth = fm.stringWidth(waypointName);
479 boolean drawnName = false;
480 // Make arrays for coordinates right left up down
481 int[] nameXs = {px + 2, px - nameWidth - 2, px - nameWidth/2, px - nameWidth/2};
482 int[] nameYs = {py + (nameHeight/2), py + (nameHeight/2), py - 2, py + nameHeight + 2};
483 for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2)
485 // Shift arrays for coordinates right left up down
486 nameXs[0] += 2; nameXs[1] -= 2;
487 nameYs[2] -= 2; nameYs[3] += 2;
488 // Check each direction in turn right left up down
489 for (int a=0; a<4; a++)
491 if (nameXs[a] > 0 && (nameXs[a] + nameWidth) < width
492 && nameYs[a] < height && (nameYs[a] - nameHeight) > 0
493 && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight))
495 // Found a rectangle to fit - draw name here and quit
496 g.drawString(waypointName, nameXs[a], nameYs[a]);
505 // Loop over points, drawing blobs for photo points
506 g.setColor(COLOR_PHOTO_PT);
507 for (int i=0; i<_track.getNumPoints(); i++)
509 if (_track.getPoint(i).getPhoto() != null)
511 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
512 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
513 if (px >= 0 && px < getWidth() && py >= 0 && py < getHeight())
515 g.drawRect(px-1, py-1, 2, 2);
516 g.drawRect(px-2, py-2, 4, 4);
522 // Draw selected range
523 if (_selection.hasRangeSelected())
525 g.setColor(COLOR_CURR_RANGE);
526 for (int i=_selection.getStart(); i<=_selection.getEnd(); i++)
528 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(i));
529 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(i));
530 g.drawRect(px-1, py-1, 2, 2);
534 // Draw selected point, crosshairs
535 int selectedPoint = _selection.getCurrentPointIndex();
536 if (selectedPoint >= 0)
538 int px = getWidth() / 2 + _mapPosition.getXFromCentre(_track.getXNew(selectedPoint));
539 int py = getHeight() / 2 + _mapPosition.getYFromCentre(_track.getYNew(selectedPoint));
540 g.setColor(COLOR_CROSSHAIRS);
542 g.drawLine(px, 0, px, getHeight());
543 g.drawLine(0, py, getWidth(), py);
545 g.drawOval(px - 2, py - 2, 4, 4);
546 g.drawOval(px - 3, py - 3, 6, 6);
552 _recalculate = false;
553 // Zoom to fit if no points found
554 if (pointsPainted <= 0 && _checkBounds) {
559 _checkBounds = false;
560 // enable / disable transparency slider
561 _transparencySlider.setEnabled(_mapCheckBox.isSelected());
566 * Tests whether there are any dark pixels within the specified x,y rectangle
567 * @param inX left X coordinate
568 * @param inY bottom Y coordinate
569 * @param inWidth width of rectangle
570 * @param inHeight height of rectangle
571 * @return true if there's at least one data point in the rectangle
573 private boolean overlapsPoints(int inX, int inY, int inWidth, int inHeight)
575 // each of the colour channels must be brighter than this to count as empty
576 final int BRIGHTNESS_LIMIT = 210;
579 // loop over x coordinate of rectangle
580 for (int x=0; x<inWidth; x++)
582 // loop over y coordinate of rectangle
583 for (int y=0; y<inHeight; y++)
585 int pixelColor = _mapImage.getRGB(inX + x, inY - y);
586 // split into four components rgba
587 int lowestBit = pixelColor & 255;
588 int secondBit = (pixelColor >> 8) & 255;
589 int thirdBit = (pixelColor >> 16) & 255;
590 //int fourthBit = (pixelColor >> 24) & 255; // alpha ignored
591 if (lowestBit < BRIGHTNESS_LIMIT || secondBit < BRIGHTNESS_LIMIT || thirdBit < BRIGHTNESS_LIMIT) return true;
595 catch (NullPointerException e) {
596 // ignore null pointers, just return false
603 * Inform that tiles have been updated and the map can be repainted
604 * @param isOK true if data loaded ok, false for error
606 public synchronized void tilesUpdated(boolean inIsOk)
608 // Show message if loading failed (but not too many times)
609 if (!inIsOk && !_shownOsmErrorAlready)
611 _shownOsmErrorAlready = true;
612 // use separate thread to show message about failing to load osm images
613 new Thread(new Runnable() {
615 try {Thread.sleep(500);} catch (InterruptedException ie) {}
616 JOptionPane.showMessageDialog(MapCanvas.this,
617 I18nManager.getText("error.osmimage.failed"),
618 I18nManager.getText("error.osmimage.dialogtitle"),
619 JOptionPane.ERROR_MESSAGE);
628 * Zoom out, if not already at minimum zoom
630 public void zoomOut()
632 _mapPosition.zoomOut();
638 * Zoom in, if not already at maximum zoom
642 _mapPosition.zoomIn();
649 * @param inDeltaX x shift
650 * @param inDeltaY y shift
652 public void panMap(int inDeltaX, int inDeltaY)
654 _mapPosition.pan(inDeltaX, inDeltaY);
660 * @see javax.swing.JComponent#getMinimumSize()
662 public Dimension getMinimumSize()
664 final Dimension minSize = new Dimension(512, 300);
669 * @see javax.swing.JComponent#getPreferredSize()
671 public Dimension getPreferredSize()
673 return getMinimumSize();
678 * Respond to mouse click events
679 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
681 public void mouseClicked(MouseEvent inE)
683 if (_track != null && _track.getNumPoints() > 0)
685 // select point if it's a left-click
686 if (!inE.isMetaDown())
688 int pointIndex = _track.getNearestPointIndexNew(
689 _mapPosition.getXFromPixels(inE.getX(), getWidth()),
690 _mapPosition.getYFromPixels(inE.getY(), getHeight()),
691 _mapPosition.getBoundsFromPixels(CLICK_SENSITIVITY), false);
692 _selection.selectPoint(pointIndex);
696 // show the popup menu for right-clicks
697 _popupMenuX = inE.getX();
698 _popupMenuY = inE.getY();
699 _popup.show(this, _popupMenuX, _popupMenuY);
705 * Ignore mouse enter events
706 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
708 public void mouseEntered(MouseEvent inE)
714 * Ignore mouse exited events
715 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
717 public void mouseExited(MouseEvent inE)
723 * Ignore mouse pressed events
724 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
726 public void mousePressed(MouseEvent inE)
732 * Respond to mouse released events
733 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
735 public void mouseReleased(MouseEvent inE)
738 if (_zoomDragging && Math.abs(_dragToX - _dragFromX) > 20 && Math.abs(_dragToY - _dragFromY) > 20)
740 //System.out.println("Finished zoom: " + _dragFromX + ", " + _dragFromY + " to " + _dragToX + ", " + _dragToY);
741 _mapPosition.zoomToPixels(_dragFromX, _dragToX, _dragFromY, _dragToY, getWidth(), getHeight());
743 _dragFromX = _dragFromY = -1;
744 _zoomDragging = false;
749 * Respond to mouse drag events
750 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
752 public void mouseDragged(MouseEvent inE)
754 if (!inE.isMetaDown())
756 // Left mouse drag - pan map by appropriate amount
757 _zoomDragging = false;
758 if (_dragFromX != -1)
760 panMap(_dragFromX - inE.getX(), _dragFromY - inE.getY());
764 _dragFromX = inE.getX();
765 _dragFromY = inE.getY();
769 // Right-click and drag - draw rectangle and control zoom
770 _zoomDragging = true;
771 if (_dragFromX == -1) {
772 _dragFromX = inE.getX();
773 _dragFromY = inE.getY();
775 _dragToX = inE.getX();
776 _dragToY = inE.getY();
782 * Respond to mouse move events without button pressed
783 * @param inEvent ignored
785 public void mouseMoved(MouseEvent inEvent)
791 * Respond to status bar message from broker
792 * @param inMessage message, ignored
794 public void actionCompleted(String inMessage)
800 * Respond to data updated message from broker
801 * @param inUpdateType type of update
803 public void dataUpdated(byte inUpdateType)
806 if ((inUpdateType & DataSubscriber.DATA_ADDED_OR_REMOVED) > 0) {
810 // enable or disable components
811 boolean hasData = _track.getNumPoints() > 0;
812 _topPanel.setVisible(hasData);
813 _sidePanel.setVisible(hasData);
814 // grab focus for the key presses
819 * Respond to key presses on the map canvas
820 * @param inE key event
822 public void keyPressed(KeyEvent inE)
824 int code = inE.getKeyCode();
825 // Check for meta key
826 if (inE.isControlDown())
828 // Check for arrow keys to zoom in and out
829 if (code == KeyEvent.VK_UP)
831 else if (code == KeyEvent.VK_DOWN)
833 // Key nav for next/prev point
834 else if (code == KeyEvent.VK_LEFT)
835 _selection.selectPreviousPoint();
836 else if (code == KeyEvent.VK_RIGHT)
837 _selection.selectNextPoint();
841 // Check for arrow keys to pan
843 if (code == KeyEvent.VK_UP)
844 upwardsPan = -PAN_DISTANCE;
845 else if (code == KeyEvent.VK_DOWN)
846 upwardsPan = PAN_DISTANCE;
847 int rightwardsPan = 0;
848 if (code == KeyEvent.VK_RIGHT)
849 rightwardsPan = PAN_DISTANCE;
850 else if (code == KeyEvent.VK_LEFT)
851 rightwardsPan = -PAN_DISTANCE;
852 panMap(rightwardsPan, upwardsPan);
853 // Check for delete key to delete current point
854 if (code == KeyEvent.VK_DELETE && _selection.getCurrentPointIndex() >= 0)
856 _app.deleteCurrentPoint();
862 * @param inE key released event, ignored
864 public void keyReleased(KeyEvent e)
870 * @param inE key typed event, ignored
872 public void keyTyped(KeyEvent inE)
878 * @param inE mouse wheel event indicating scroll direction
880 public void mouseWheelMoved(MouseWheelEvent inE)
882 int clicks = inE.getWheelRotation();