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_2;
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_4.*;
31 public class AltosUIMapView extends Canvas implements MouseMotionListener, MouseListener, MouseWheelListener, ComponentListener, AltosUIMapTileListener, AltosUIMapStoreListener {
33 AltosUIMapPath path = new AltosUIMapPath();
35 AltosUIMapLine line = new AltosUIMapLine();
37 LinkedList<AltosUIMapMark> marks = new LinkedList<AltosUIMapMark>();
39 LinkedList<AltosUIMapZoomListener> zoom_listeners = new LinkedList<AltosUIMapZoomListener>();
41 boolean have_boost = false;
42 boolean have_landed = false;
44 ConcurrentHashMap<Point,AltosUIMapTile> tiles = new ConcurrentHashMap<Point,AltosUIMapTile>();
46 static final int default_zoom = 15;
47 static final int min_zoom = 3;
48 static final int max_zoom = 21;
49 static final int px_size = 512;
52 AltosUILatLon load_centre = null;
53 AltosUIMapTileListener load_listener;
55 int zoom = default_zoom;
56 int maptype = AltosUIMap.maptype_default;
60 /* Milliseconds to wait after user action before auto-scrolling
62 static final long auto_scroll_delay = 20 * 1000;
64 AltosUIMapTransform transform;
67 public void set_font() {
68 line.set_font(AltosUILib.value_font);
69 for (AltosUIMapTile tile : tiles.values())
70 tile.set_font(AltosUILib.value_font);
74 public void set_units() {
78 private boolean is_drag_event(MouseEvent e) {
79 return e.getModifiers() == InputEvent.BUTTON1_MASK;
84 private void drag(MouseEvent e) {
85 if (drag_start == null)
88 int dx = e.getPoint().x - drag_start.x;
89 int dy = e.getPoint().y - drag_start.y;
91 AltosUILatLon new_centre = transform.screen_lat_lon(new Point(getWidth() / 2 - dx, getHeight() / 2 - dy));
92 centre (new_centre.lat, new_centre.lon);
93 drag_start = e.getPoint();
96 private void drag_start(MouseEvent e) {
97 drag_start = e.getPoint();
100 private void notice_user_input() {
101 user_input_time = System.currentTimeMillis();
104 private boolean recent_user_input() {
105 return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay;
108 /* MouseMotionListener methods */
110 public void mouseDragged(MouseEvent e) {
112 if (is_drag_event(e))
115 line.dragged(e, transform);
120 public void mouseMoved(MouseEvent e) {
123 /* MouseListener methods */
124 public void mouseClicked(MouseEvent e) {
127 public void mouseEntered(MouseEvent e) {
130 public void mouseExited(MouseEvent e) {
133 public void mousePressed(MouseEvent e) {
135 if (is_drag_event(e))
138 line.pressed(e, transform);
141 public void mouseReleased(MouseEvent e) {
144 /* MouseWheelListener methods */
146 public void mouseWheelMoved(MouseWheelEvent e) {
147 int zoom_change = e.getWheelRotation();
150 AltosUILatLon mouse_lat_lon = transform.screen_lat_lon(e.getPoint());
151 set_zoom(zoom() - zoom_change);
153 Point2D.Double new_mouse = transform.screen(mouse_lat_lon);
155 int dx = getWidth()/2 - e.getPoint().x;
156 int dy = getHeight()/2 - e.getPoint().y;
158 AltosUILatLon new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy));
160 centre(new_centre.lat, new_centre.lon);
163 /* ComponentListener methods */
165 public void componentHidden(ComponentEvent e) {
168 public void componentMoved(ComponentEvent e) {
171 public void componentResized(ComponentEvent e) {
175 public void componentShown(ComponentEvent e) {
179 public void repaint(Rectangle r, int pad) {
180 repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2);
183 public void repaint(AltosUIMapRectangle rect, int pad) {
184 repaint (transform.screen(rect), pad);
187 private boolean far_from_centre(AltosUILatLon lat_lon) {
189 if (centre == null || transform == null)
192 Point2D.Double screen = transform.screen(lat_lon);
194 int width = getWidth();
195 int dx = Math.abs ((int) screen.x - width/2);
200 int height = getHeight();
201 int dy = Math.abs ((int) screen.y - height/2);
209 public void show(AltosState state, AltosListenerState listener_state) {
211 /* If insufficient gps data, nothing to update
213 AltosGPS gps = state.gps;
218 if (!gps.locked && gps.nsat < 4)
221 AltosUIMapRectangle damage = path.add(gps.lat, gps.lon, state.state);
223 switch (state.state) {
224 case AltosLib.ao_flight_boost:
226 add_mark(gps.lat, gps.lon, state.state);
230 case AltosLib.ao_flight_landed:
232 add_mark(gps.lat, gps.lon, state.state);
239 repaint(damage, AltosUIMapPath.stroke_width);
240 maybe_centre(gps.lat, gps.lon);
243 private void set_transform() {
244 Rectangle bounds = getBounds();
246 transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre);
250 public boolean set_zoom(int zoom) {
251 if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) {
256 for (AltosUIMapZoomListener listener : zoom_listeners)
257 listener.zoom_changed(this.zoom);
264 public void add_zoom_listener(AltosUIMapZoomListener listener) {
265 if (!zoom_listeners.contains(listener))
266 zoom_listeners.add(listener);
269 public void remove_zoom_listener(AltosUIMapZoomListener listener) {
270 zoom_listeners.remove(listener);
273 public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
274 load_centre = new AltosUILatLon(lat, lon);
275 load_radius = radius;
276 load_listener = listener;
279 for (AltosUIMapTile tile : tiles.values()) {
280 tile.add_store_listener(this);
281 if (tile.store_status() != AltosUIMapStore.loading)
282 listener.notify_tile(tile, tile.store_status());
287 public boolean all_fetched() {
288 for (AltosUIMapTile tile : tiles.values()) {
289 if (tile.store_status() == AltosUIMapStore.loading)
295 public boolean set_maptype(int maptype) {
296 if (maptype != this.maptype) {
297 this.maptype = maptype;
305 public int get_maptype() {
313 public void centre(AltosUILatLon lat_lon) {
318 public void centre(double lat, double lon) {
319 centre(new AltosUILatLon(lat, lon));
322 public void maybe_centre(double lat, double lon) {
323 AltosUILatLon lat_lon = new AltosUILatLon(lat, lon);
324 if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
328 private VolatileImage create_back_buffer() {
329 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
332 private Point floor(Point2D.Double point) {
333 return new Point ((int) Math.floor(point.x / px_size) * px_size,
334 (int) Math.floor(point.y / px_size) * px_size);
337 private Point ceil(Point2D.Double point) {
338 return new Point ((int) Math.ceil(point.x / px_size) * px_size,
339 (int) Math.ceil(point.y / px_size) * px_size);
342 private void make_tiles() {
346 if (load_centre != null) {
347 Point centre = floor(transform.point(load_centre));
349 upper_left = new Point(centre.x - load_radius * px_size,
350 centre.y - load_radius * px_size);
351 lower_right = new Point(centre.x + load_radius * px_size,
352 centre.y + load_radius * px_size);
354 upper_left = floor(transform.screen_point(new Point(0, 0)));
355 lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight())));
357 LinkedList<Point> to_remove = new LinkedList<Point>();
359 for (Point point : tiles.keySet()) {
360 if (point.x < upper_left.x || lower_right.x < point.x ||
361 point.y < upper_left.y || lower_right.y < point.y) {
362 to_remove.add(point);
366 for (Point point : to_remove)
369 AltosUIMapCache.set_cache_size(((lower_right.y - upper_left.y) / px_size + 1) * ((lower_right.x - upper_left.x) / px_size + 1));
370 for (int y = upper_left.y; y <= lower_right.y; y += px_size) {
371 for (int x = upper_left.x; x <= lower_right.x; x += px_size) {
372 Point point = new Point(x, y);
374 if (!tiles.containsKey(point)) {
375 AltosUILatLon ul = transform.lat_lon(new Point2D.Double(x, y));
376 AltosUILatLon center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2));
377 AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype,
378 px_size, AltosUILib.value_font);
379 tiles.put(point, tile);
385 /* AltosUIMapTileListener methods */
386 public void notify_tile(AltosUIMapTile tile, int status) {
387 for (Point point : tiles.keySet()) {
388 if (tile == tiles.get(point)) {
389 Point screen = transform.screen(point);
390 repaint(screen.x, screen.y, px_size, px_size);
395 /* AltosUIMapStoreListener methods */
396 public void notify_store(AltosUIMapStore store, int status) {
397 if (load_listener != null) {
398 for (AltosUIMapTile tile : tiles.values())
399 if (store.equals(tile.store))
400 load_listener.notify_tile(tile, status);
404 private void do_paint(Graphics g) {
405 Graphics2D g2d = (Graphics2D) g;
409 for (AltosUIMapTile tile : tiles.values())
410 tile.paint(g2d, transform);
412 synchronized(marks) {
413 for (AltosUIMapMark mark : marks)
414 mark.paint(g2d, transform);
417 path.paint(g2d, transform);
419 line.paint(g2d, transform);
422 public void paint(Graphics g) {
424 VolatileImage back_buffer = create_back_buffer();
426 GraphicsConfiguration gc = getGraphicsConfiguration();
427 int code = back_buffer.validate(gc);
428 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
429 back_buffer = create_back_buffer();
431 Graphics g_back = back_buffer.getGraphics();
432 g_back.setClip(g.getClip());
436 g.drawImage(back_buffer, 0, 0, this);
437 } while (back_buffer.contentsLost());
441 public void update(Graphics g) {
445 public void add_mark(double lat, double lon, int state) {
446 synchronized(marks) {
447 marks.add(new AltosUIMapMark(lat, lon, state));
452 public void clear_marks() {
453 synchronized(marks) {
458 public AltosUIMapView() {
461 addComponentListener(this);
462 addMouseMotionListener(this);
463 addMouseListener(this);
464 addMouseWheelListener(this);