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);
73 private boolean is_drag_event(MouseEvent e) {
74 return e.getModifiers() == InputEvent.BUTTON1_MASK;
79 private void drag(MouseEvent e) {
80 if (drag_start == null)
83 int dx = e.getPoint().x - drag_start.x;
84 int dy = e.getPoint().y - drag_start.y;
86 AltosUILatLon new_centre = transform.screen_lat_lon(new Point(getWidth() / 2 - dx, getHeight() / 2 - dy));
87 centre (new_centre.lat, new_centre.lon);
88 drag_start = e.getPoint();
91 private void drag_start(MouseEvent e) {
92 drag_start = e.getPoint();
95 private void notice_user_input() {
96 user_input_time = System.currentTimeMillis();
99 private boolean recent_user_input() {
100 return (System.currentTimeMillis() - user_input_time) < auto_scroll_delay;
103 /* MouseMotionListener methods */
105 public void mouseDragged(MouseEvent e) {
107 if (is_drag_event(e))
110 line.dragged(e, transform);
115 public void mouseMoved(MouseEvent e) {
118 /* MouseListener methods */
119 public void mouseClicked(MouseEvent e) {
122 public void mouseEntered(MouseEvent e) {
125 public void mouseExited(MouseEvent e) {
128 public void mousePressed(MouseEvent e) {
130 if (is_drag_event(e))
133 line.pressed(e, transform);
136 public void mouseReleased(MouseEvent e) {
139 /* MouseWheelListener methods */
141 public void mouseWheelMoved(MouseWheelEvent e) {
142 int zoom_change = e.getWheelRotation();
145 AltosUILatLon mouse_lat_lon = transform.screen_lat_lon(e.getPoint());
146 set_zoom(zoom() - zoom_change);
148 Point2D.Double new_mouse = transform.screen(mouse_lat_lon);
150 int dx = getWidth()/2 - e.getPoint().x;
151 int dy = getHeight()/2 - e.getPoint().y;
153 AltosUILatLon new_centre = transform.screen_lat_lon(new Point((int) new_mouse.x + dx, (int) new_mouse.y + dy));
155 centre(new_centre.lat, new_centre.lon);
158 /* ComponentListener methods */
160 public void componentHidden(ComponentEvent e) {
163 public void componentMoved(ComponentEvent e) {
166 public void componentResized(ComponentEvent e) {
170 public void componentShown(ComponentEvent e) {
174 public void repaint(Rectangle r, int pad) {
175 repaint(r.x - pad, r.y - pad, r.width + pad*2, r.height + pad*2);
178 public void repaint(AltosUIMapRectangle rect, int pad) {
179 repaint (transform.screen(rect), pad);
182 private boolean far_from_centre(AltosUILatLon lat_lon) {
184 if (centre == null || transform == null)
187 Point2D.Double screen = transform.screen(lat_lon);
189 int width = getWidth();
190 int dx = Math.abs ((int) screen.x - width/2);
195 int height = getHeight();
196 int dy = Math.abs ((int) screen.y - height/2);
204 public void show(AltosState state, AltosListenerState listener_state) {
206 /* If insufficient gps data, nothing to update
208 AltosGPS gps = state.gps;
213 if (!gps.locked && gps.nsat < 4)
216 AltosUIMapRectangle damage = path.add(gps.lat, gps.lon, state.state);
218 switch (state.state) {
219 case AltosLib.ao_flight_boost:
221 add_mark(gps.lat, gps.lon, state.state);
225 case AltosLib.ao_flight_landed:
227 add_mark(gps.lat, gps.lon, state.state);
234 repaint(damage, AltosUIMapPath.stroke_width);
235 maybe_centre(gps.lat, gps.lon);
238 private void set_transform() {
239 Rectangle bounds = getBounds();
241 transform = new AltosUIMapTransform(bounds.width, bounds.height, zoom, centre);
245 public boolean set_zoom(int zoom) {
246 if (min_zoom <= zoom && zoom <= max_zoom && zoom != this.zoom) {
251 for (AltosUIMapZoomListener listener : zoom_listeners)
252 listener.zoom_changed(this.zoom);
259 public void add_zoom_listener(AltosUIMapZoomListener listener) {
260 if (!zoom_listeners.contains(listener))
261 zoom_listeners.add(listener);
264 public void remove_zoom_listener(AltosUIMapZoomListener listener) {
265 zoom_listeners.remove(listener);
268 public void set_load_params(double lat, double lon, int radius, AltosUIMapTileListener listener) {
269 load_centre = new AltosUILatLon(lat, lon);
270 load_radius = radius;
271 load_listener = listener;
274 for (AltosUIMapTile tile : tiles.values()) {
275 tile.add_store_listener(this);
276 if (tile.store_status() != AltosUIMapStore.loading)
277 listener.notify_tile(tile, tile.store_status());
282 public boolean all_fetched() {
283 for (AltosUIMapTile tile : tiles.values()) {
284 if (tile.store_status() == AltosUIMapStore.loading)
290 public boolean set_maptype(int maptype) {
291 if (maptype != this.maptype) {
292 this.maptype = maptype;
300 public int get_maptype() {
308 public void centre(AltosUILatLon lat_lon) {
313 public void centre(double lat, double lon) {
314 centre(new AltosUILatLon(lat, lon));
317 public void maybe_centre(double lat, double lon) {
318 AltosUILatLon lat_lon = new AltosUILatLon(lat, lon);
319 if (centre == null || (!recent_user_input() && far_from_centre(lat_lon)))
323 private VolatileImage create_back_buffer() {
324 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
327 private Point floor(Point2D.Double point) {
328 return new Point ((int) Math.floor(point.x / px_size) * px_size,
329 (int) Math.floor(point.y / px_size) * px_size);
332 private Point ceil(Point2D.Double point) {
333 return new Point ((int) Math.ceil(point.x / px_size) * px_size,
334 (int) Math.ceil(point.y / px_size) * px_size);
337 private void make_tiles() {
341 if (load_centre != null) {
342 Point centre = floor(transform.point(load_centre));
344 upper_left = new Point(centre.x - load_radius * px_size,
345 centre.y - load_radius * px_size);
346 lower_right = new Point(centre.x + load_radius * px_size,
347 centre.y + load_radius * px_size);
349 upper_left = floor(transform.screen_point(new Point(0, 0)));
350 lower_right = floor(transform.screen_point(new Point(getWidth(), getHeight())));
352 LinkedList<Point> to_remove = new LinkedList<Point>();
354 for (Point point : tiles.keySet()) {
355 if (point.x < upper_left.x || lower_right.x < point.x ||
356 point.y < upper_left.y || lower_right.y < point.y) {
357 to_remove.add(point);
361 for (Point point : to_remove)
364 AltosUIMapCache.set_cache_size(((lower_right.y - upper_left.y) / px_size + 1) * ((lower_right.x - upper_left.x) / px_size + 1));
365 for (int y = upper_left.y; y <= lower_right.y; y += px_size) {
366 for (int x = upper_left.x; x <= lower_right.x; x += px_size) {
367 Point point = new Point(x, y);
369 if (!tiles.containsKey(point)) {
370 AltosUILatLon ul = transform.lat_lon(new Point2D.Double(x, y));
371 AltosUILatLon center = transform.lat_lon(new Point2D.Double(x + px_size/2, y + px_size/2));
372 AltosUIMapTile tile = new AltosUIMapTile(this, ul, center, zoom, maptype,
373 px_size, AltosUILib.value_font);
374 tiles.put(point, tile);
380 /* AltosUIMapTileListener methods */
381 public void notify_tile(AltosUIMapTile tile, int status) {
382 for (Point point : tiles.keySet()) {
383 if (tile == tiles.get(point)) {
384 Point screen = transform.screen(point);
385 repaint(screen.x, screen.y, px_size, px_size);
390 /* AltosUIMapStoreListener methods */
391 public void notify_store(AltosUIMapStore store, int status) {
392 if (load_listener != null) {
393 for (AltosUIMapTile tile : tiles.values())
394 if (store.equals(tile.store))
395 load_listener.notify_tile(tile, status);
399 private void do_paint(Graphics g) {
400 Graphics2D g2d = (Graphics2D) g;
404 for (AltosUIMapTile tile : tiles.values())
405 tile.paint(g2d, transform);
407 synchronized(marks) {
408 for (AltosUIMapMark mark : marks)
409 mark.paint(g2d, transform);
412 path.paint(g2d, transform);
414 line.paint(g2d, transform);
417 public void paint(Graphics g) {
419 VolatileImage back_buffer = create_back_buffer();
421 GraphicsConfiguration gc = getGraphicsConfiguration();
422 int code = back_buffer.validate(gc);
423 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
424 back_buffer = create_back_buffer();
426 Graphics g_back = back_buffer.getGraphics();
427 g_back.setClip(g.getClip());
431 g.drawImage(back_buffer, 0, 0, this);
432 } while (back_buffer.contentsLost());
436 public void update(Graphics g) {
440 public void add_mark(double lat, double lon, int state) {
441 synchronized(marks) {
442 marks.add(new AltosUIMapMark(lat, lon, state));
447 public void clear_marks() {
448 synchronized(marks) {
453 public AltosUIMapView() {
456 addComponentListener(this);
457 addMouseMotionListener(this);
458 addMouseListener(this);
459 addMouseWheelListener(this);