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);
143 public void mouseReleased(MouseEvent e) {
146 /* MouseWheelListener methods */
148 public void mouseWheelMoved(MouseWheelEvent e) {
149 int zoom_change = e.getWheelRotation();
152 AltosUILatLon mouse_lat_lon = transform.screen_lat_lon(e.getPoint());
153 set_zoom(zoom() - zoom_change);
155 Point2D.Double new_mouse = transform.screen(mouse_lat_lon);
157 int dx = getWidth()/2 - e.getPoint().x;
158 int dy = getHeight()/2 - e.getPoint().y;
160 AltosUILatLon new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy));
162 centre(new_centre.lat, new_centre.lon);
165 /* ComponentListener methods */
167 public void componentHidden(ComponentEvent e) {
170 public void componentMoved(ComponentEvent e) {
173 public void componentResized(ComponentEvent e) {
177 public void componentShown(ComponentEvent e) {
181 public void repaint(Rectangle r, int pad) {
182 repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2);
185 public void repaint(AltosUIMapRectangle rect, int pad) {
186 repaint (transform.screen(rect), pad);
189 private boolean far_from_centre(AltosUILatLon lat_lon) {
191 if (centre == null || transform == null)
194 Point2D.Double screen = transform.screen(lat_lon);
196 int width = getWidth();
197 int dx = Math.abs ((int) screen.x - width/2);
202 int height = getHeight();
203 int dy = Math.abs ((int) screen.y - height/2);
211 public void show(AltosState state, AltosListenerState listener_state) {
213 /* If insufficient gps data, nothing to update
215 AltosGPS gps = state.gps;
220 if (!gps.locked && gps.nsat < 4)
223 AltosUIMapRectangle damage = path.add(gps.lat, gps.lon, state.state);
225 switch (state.state) {
226 case AltosLib.ao_flight_boost:
228 add_mark(gps.lat, gps.lon, state.state);
232 case AltosLib.ao_flight_landed:
234 add_mark(gps.lat, gps.lon, state.state);
241 repaint(damage, AltosUIMapPath.stroke_width);
242 maybe_centre(gps.lat, gps.lon);
245 private void set_transform() {
246 Rectangle bounds = getBounds();
248 transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre);
252 public boolean set_zoom(int zoom) {
253 if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) {
258 for (AltosUIMapZoomListener listener : zoom_listeners)
259 listener.zoom_changed(this.zoom);
266 public void add_zoom_listener(AltosUIMapZoomListener listener) {
267 if (!zoom_listeners.contains(listener))
268 zoom_listeners.add(listener);
271 public void remove_zoom_listener(AltosUIMapZoomListener listener) {
272 zoom_listeners.remove(listener);
275 public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
276 load_centre = new AltosUILatLon(lat, lon);
277 load_radius = radius;
278 load_listener = listener;
281 for (AltosUIMapTile tile : tiles.values()) {
282 tile.add_store_listener(this);
283 if (tile.store_status() != AltosUIMapStore.loading)
284 listener.notify_tile(tile, tile.store_status());
289 public boolean all_fetched() {
290 for (AltosUIMapTile tile : tiles.values()) {
291 if (tile.store_status() == AltosUIMapStore.loading)
297 public boolean set_maptype(int maptype) {
298 if (maptype != this.maptype) {
299 this.maptype = maptype;
307 public int get_maptype() {
315 public void centre(AltosUILatLon lat_lon) {
320 public void centre(double lat, double lon) {
321 centre(new AltosUILatLon(lat, lon));
324 public void maybe_centre(double lat, double lon) {
325 AltosUILatLon lat_lon = new AltosUILatLon(lat, lon);
326 if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
330 private VolatileImage create_back_buffer() {
331 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
334 private Point floor(Point2D.Double point) {
335 return new Point ((int) Math.floor(point.x / px_size) * px_size,
336 (int) Math.floor(point.y / px_size) * px_size);
339 private Point ceil(Point2D.Double point) {
340 return new Point ((int) Math.ceil(point.x / px_size) * px_size,
341 (int) Math.ceil(point.y / px_size) * px_size);
344 private void make_tiles() {
348 if (load_centre != null) {
349 Point centre = floor(transform.point(load_centre));
351 upper_left = new Point(centre.x - load_radius * px_size,
352 centre.y - load_radius * px_size);
353 lower_right = new Point(centre.x + load_radius * px_size,
354 centre.y + load_radius * px_size);
356 upper_left = floor(transform.screen_point(new Point(0, 0)));
357 lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight())));
359 LinkedList<Point> to_remove = new LinkedList<Point>();
361 for (Point point : tiles.keySet()) {
362 if (point.x < upper_left.x || lower_right.x < point.x ||
363 point.y < upper_left.y || lower_right.y < point.y) {
364 to_remove.add(point);
368 for (Point point : to_remove)
371 AltosUIMapCache.set_cache_size(((lower_right.y - upper_left.y) / px_size + 1) * ((lower_right.x - upper_left.x) / px_size + 1));
372 for (int y = upper_left.y; y <= lower_right.y; y += px_size) {
373 for (int x = upper_left.x; x <= lower_right.x; x += px_size) {
374 Point point = new Point(x, y);
376 if (!tiles.containsKey(point)) {
377 AltosUILatLon ul = transform.lat_lon(new Point2D.Double(x, y));
378 AltosUILatLon center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2));
379 AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype,
380 px_size, AltosUILib.value_font);
381 tiles.put(point, tile);
387 /* AltosUIMapTileListener methods */
388 public void notify_tile(AltosUIMapTile tile, int status) {
389 for (Point point : tiles.keySet()) {
390 if (tile == tiles.get(point)) {
391 Point screen = transform.screen(point);
392 repaint(screen.x, screen.y, px_size, px_size);
397 /* AltosUIMapStoreListener methods */
398 public void notify_store(AltosUIMapStore store, int status) {
399 if (load_listener != null) {
400 for (AltosUIMapTile tile : tiles.values())
401 if (store.equals(tile.store))
402 load_listener.notify_tile(tile, status);
406 private void do_paint(Graphics g) {
407 Graphics2D g2d = (Graphics2D) g;
411 for (AltosUIMapTile tile : tiles.values())
412 tile.paint(g2d, transform);
414 synchronized(marks) {
415 for (AltosUIMapMark mark : marks)
416 mark.paint(g2d, transform);
419 path.paint(g2d, transform);
421 line.paint(g2d, transform);
424 public void paint(Graphics g) {
426 VolatileImage back_buffer = create_back_buffer();
428 GraphicsConfiguration gc = getGraphicsConfiguration();
429 int code = back_buffer.validate(gc);
430 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
431 back_buffer = create_back_buffer();
433 Graphics g_back = back_buffer.getGraphics();
434 g_back.setClip(g.getClip());
438 g.drawImage(back_buffer, 0, 0, this);
439 } while (back_buffer.contentsLost());
443 public void update(Graphics g) {
447 public void add_mark(double lat, double lon, int state) {
448 synchronized(marks) {
449 marks.add(new AltosUIMapMark(lat, lon, state));
454 public void clear_marks() {
455 synchronized(marks) {
460 public AltosUIMapView() {
463 addComponentListener(this);
464 addMouseMotionListener(this);
465 addMouseListener(this);
466 addMouseWheelListener(this);