2 * Copyright © 2014 Keith Packard <keithp@keithp.com>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18 package org.altusmetrum.altosuilib_3;
21 import java.awt.event.*;
22 import java.awt.image.*;
26 import java.awt.geom.*;
28 import java.util.concurrent.*;
29 import org.altusmetrum.altoslib_5.*;
31 public class AltosUIMapView extends Component implements MouseMotionListener, MouseListener, MouseWheelListener, ComponentListener, AltosUIMapTileListener, AltosUIMapStoreListener {
33 AltosUIMapPath path = new AltosUIMapPath();
35 AltosUIMapLine line = new AltosUIMapLine();
37 AltosUIMapCache cache = new AltosUIMapCache();
39 LinkedList<AltosUIMapMark> marks = new LinkedList<AltosUIMapMark>();
41 LinkedList<AltosUIMapZoomListener> zoom_listeners = new LinkedList<AltosUIMapZoomListener>();
43 boolean have_boost = false;
44 boolean have_landed = false;
46 ConcurrentHashMap<Point,AltosUIMapTile> tiles = new ConcurrentHashMap<Point,AltosUIMapTile>();
48 static final int default_zoom = 15;
49 static final int min_zoom = 3;
50 static final int max_zoom = 21;
51 static final int px_size = 512;
54 AltosUILatLon load_centre = null;
55 AltosUIMapTileListener load_listener;
57 int zoom = default_zoom;
58 int maptype = AltosUIMap.maptype_default;
62 /* Milliseconds to wait after user action before auto-scrolling
64 static final long auto_scroll_delay = 20 * 1000;
66 AltosUIMapTransform transform;
69 public void set_font() {
70 line.set_font(AltosUILib.value_font);
71 for (AltosUIMapTile tile : tiles.values())
72 tile.set_font(AltosUILib.value_font);
76 public void set_units() {
80 private boolean is_drag_event(MouseEvent e) {
81 return e.getModifiers() == InputEvent.BUTTON1_MASK;
86 private void drag(MouseEvent e) {
87 if (drag_start == null)
90 int dx = e.getPoint().x - drag_start.x;
91 int dy = e.getPoint().y - drag_start.y;
93 AltosUILatLon new_centre = transform.screen_lat_lon(new Point(getWidth() / 2 - dx, getHeight() / 2 - dy));
94 centre (new_centre.lat, new_centre.lon);
95 drag_start = e.getPoint();
98 private void drag_start(MouseEvent e) {
99 drag_start = e.getPoint();
102 private void notice_user_input() {
103 user_input_time = System.currentTimeMillis();
106 private boolean recent_user_input() {
107 return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay;
110 /* MouseMotionListener methods */
112 public void mouseDragged(MouseEvent e) {
114 if (is_drag_event(e))
117 line.dragged(e, transform);
122 public void mouseMoved(MouseEvent e) {
125 /* MouseListener methods */
126 public void mouseClicked(MouseEvent e) {
129 public void mouseEntered(MouseEvent e) {
132 public void mouseExited(MouseEvent e) {
135 public void mousePressed(MouseEvent e) {
137 if (is_drag_event(e))
140 line.pressed(e, transform);
145 public void mouseReleased(MouseEvent e) {
148 /* MouseWheelListener methods */
150 public void mouseWheelMoved(MouseWheelEvent e) {
151 int zoom_change = e.getWheelRotation();
154 AltosUILatLon mouse_lat_lon = transform.screen_lat_lon(e.getPoint());
155 set_zoom(zoom() - zoom_change);
157 Point2D.Double new_mouse = transform.screen(mouse_lat_lon);
159 int dx = getWidth()/2 - e.getPoint().x;
160 int dy = getHeight()/2 - e.getPoint().y;
162 AltosUILatLon new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy));
164 centre(new_centre.lat, new_centre.lon);
167 /* ComponentListener methods */
169 public void componentHidden(ComponentEvent e) {
172 public void componentMoved(ComponentEvent e) {
175 public void componentResized(ComponentEvent e) {
179 public void componentShown(ComponentEvent e) {
183 public void repaint(Rectangle r, int pad) {
184 repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2);
187 public void repaint(AltosUIMapRectangle rect, int pad) {
188 repaint (transform.screen(rect), pad);
191 private boolean far_from_centre(AltosUILatLon lat_lon) {
193 if (centre == null || transform == null)
196 Point2D.Double screen = transform.screen(lat_lon);
198 int width = getWidth();
199 int dx = Math.abs ((int) screen.x - width/2);
204 int height = getHeight();
205 int dy = Math.abs ((int) screen.y - height/2);
213 public void show(AltosState state, AltosListenerState listener_state) {
215 /* If insufficient gps data, nothing to update
217 AltosGPS gps = state.gps;
222 if (!gps.locked && gps.nsat < 4)
225 AltosUIMapRectangle damage = path.add(gps.lat, gps.lon, state.state);
227 switch (state.state) {
228 case AltosLib.ao_flight_boost:
230 add_mark(gps.lat, gps.lon, state.state);
234 case AltosLib.ao_flight_landed:
236 add_mark(gps.lat, gps.lon, state.state);
243 repaint(damage, AltosUIMapPath.stroke_width);
244 maybe_centre(gps.lat, gps.lon);
247 private void set_transform() {
248 Rectangle bounds = getBounds();
250 transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre);
254 public boolean set_zoom(int zoom) {
255 if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) {
260 for (AltosUIMapZoomListener listener : zoom_listeners)
261 listener.zoom_changed(this.zoom);
268 public void add_zoom_listener(AltosUIMapZoomListener listener) {
269 if (!zoom_listeners.contains(listener))
270 zoom_listeners.add(listener);
273 public void remove_zoom_listener(AltosUIMapZoomListener listener) {
274 zoom_listeners.remove(listener);
277 public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
278 load_centre = new AltosUILatLon(lat, lon);
279 load_radius = radius;
280 load_listener = listener;
283 for (AltosUIMapTile tile : tiles.values()) {
284 tile.add_store_listener(this);
285 if (tile.store_status() != AltosUIMapStore.loading)
286 listener.notify_tile(tile, tile.store_status());
291 public boolean all_fetched() {
292 for (AltosUIMapTile tile : tiles.values()) {
293 if (tile.store_status() == AltosUIMapStore.loading)
299 public boolean set_maptype(int maptype) {
300 if (maptype != this.maptype) {
301 this.maptype = maptype;
309 public int get_maptype() {
317 public void centre(AltosUILatLon lat_lon) {
322 public void centre(double lat, double lon) {
323 centre(new AltosUILatLon(lat, lon));
326 public void maybe_centre(double lat, double lon) {
327 AltosUILatLon lat_lon = new AltosUILatLon(lat, lon);
328 if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
332 private VolatileImage create_back_buffer() {
333 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
336 private Point floor(Point2D.Double point) {
337 return new Point ((int) Math.floor(point.x / px_size) * px_size,
338 (int) Math.floor(point.y / px_size) * px_size);
341 private Point ceil(Point2D.Double point) {
342 return new Point ((int) Math.ceil(point.x / px_size) * px_size,
343 (int) Math.ceil(point.y / px_size) * px_size);
346 private void make_tiles() {
350 if (load_centre != null) {
351 Point centre = floor(transform.point(load_centre));
353 upper_left = new Point(centre.x - load_radius * px_size,
354 centre.y - load_radius * px_size);
355 lower_right = new Point(centre.x + load_radius * px_size,
356 centre.y + load_radius * px_size);
358 upper_left = floor(transform.screen_point(new Point(0, 0)));
359 lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight())));
361 LinkedList<Point> to_remove = new LinkedList<Point>();
363 for (Point point : tiles.keySet()) {
364 if (point.x < upper_left.x || lower_right.x < point.x ||
365 point.y < upper_left.y || lower_right.y < point.y) {
366 to_remove.add(point);
370 for (Point point : to_remove)
373 cache.set_cache_size((getWidth() / px_size + 2) * (getHeight() / px_size + 2));
374 for (int y = upper_left.y; y <= lower_right.y; y += px_size) {
375 for (int x = upper_left.x; x <= lower_right.x; x += px_size) {
376 Point point = new Point(x, y);
378 if (!tiles.containsKey(point)) {
379 AltosUILatLon ul = transform.lat_lon(new Point2D.Double(x, y));
380 AltosUILatLon center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2));
381 AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype,
382 px_size, AltosUILib.value_font);
383 tiles.put(point, tile);
389 /* AltosUIMapTileListener methods */
390 public synchronized void notify_tile(AltosUIMapTile tile, int status) {
391 for (Point point : tiles.keySet()) {
392 if (tile == tiles.get(point)) {
393 Point screen = transform.screen(point);
394 repaint(screen.x, screen.y, px_size, px_size);
399 public AltosUIMapCache cache() { return cache; }
401 /* AltosUIMapStoreListener methods */
402 public synchronized void notify_store(AltosUIMapStore store, int status) {
403 if (load_listener != null) {
404 for (AltosUIMapTile tile : tiles.values())
405 if (store.equals(tile.store))
406 load_listener.notify_tile(tile, status);
410 private void do_paint(Graphics g) {
411 Graphics2D g2d = (Graphics2D) g;
415 for (AltosUIMapTile tile : tiles.values())
416 tile.paint(g2d, transform);
418 synchronized(marks) {
419 for (AltosUIMapMark mark : marks)
420 mark.paint(g2d, transform);
423 path.paint(g2d, transform);
425 line.paint(g2d, transform);
428 public void paint(Graphics g) {
429 VolatileImage back_buffer = create_back_buffer();
431 GraphicsConfiguration gc = getGraphicsConfiguration();
432 int code = back_buffer.validate(gc);
433 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
434 back_buffer = create_back_buffer();
436 Graphics g_back = back_buffer.getGraphics();
437 g_back.setClip(g.getClip());
441 g.drawImage(back_buffer, 0, 0, this);
442 } while (back_buffer.contentsLost());
446 public void update(Graphics g) {
450 public void add_mark(double lat, double lon, int state) {
451 synchronized(marks) {
452 marks.add(new AltosUIMapMark(lat, lon, state));
457 public void clear_marks() {
458 synchronized(marks) {
463 public AltosUIMapView() {
466 addComponentListener(this);
467 addMouseMotionListener(this);
468 addMouseListener(this);
469 addMouseWheelListener(this);