2 * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
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_7;
21 import java.awt.event.*;
22 import java.awt.image.*;
25 import java.lang.Math;
26 import java.awt.geom.*;
28 import java.util.concurrent.*;
29 import javax.imageio.*;
30 import org.altusmetrum.altoslib_7.*;
32 public class AltosUIMapNew extends JComponent implements AltosFlightDisplay, AltosMapInterface {
39 static Point2D.Double point2d(AltosPointDouble pt) {
40 return new Point2D.Double(pt.x, pt.y);
43 static final AltosPointDouble point_double(Point pt) {
44 return new AltosPointDouble(pt.x, pt.y);
47 class MapMark extends AltosMapMark {
48 public void paint(AltosMapTransform t) {
49 AltosPointDouble pt = t.screen(lat_lon);
51 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
52 RenderingHints.VALUE_ANTIALIAS_ON);
53 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
55 if (0 <= state && state < AltosUIMapNew.stateColors.length)
56 g.setColor(AltosUIMapNew.stateColors[state]);
58 g.setColor(AltosUIMapNew.stateColors[AltosLib.ao_flight_invalid]);
60 g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
61 g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
62 g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
65 MapMark(double lat, double lon, int state) {
66 super(lat, lon, state);
70 class MapView extends JComponent implements MouseMotionListener, MouseListener, ComponentListener, MouseWheelListener {
72 private VolatileImage create_back_buffer() {
73 return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
76 private void do_paint(Graphics my_g) {
77 g = (Graphics2D) my_g;
82 public void paint(Graphics my_g) {
83 VolatileImage back_buffer = create_back_buffer();
85 Graphics2D top_g = (Graphics2D) my_g;
88 GraphicsConfiguration gc = getGraphicsConfiguration();
89 int code = back_buffer.validate(gc);
90 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
91 back_buffer = create_back_buffer();
93 Graphics g_back = back_buffer.getGraphics();
94 g_back.setClip(top_g.getClip());
98 top_g.drawImage(back_buffer, 0, 0, this);
99 } while (back_buffer.contentsLost());
103 public void repaint(AltosRectangle damage) {
104 repaint(damage.x, damage.y, damage.width, damage.height);
107 private boolean is_drag_event(MouseEvent e) {
108 return e.getModifiers() == InputEvent.BUTTON1_MASK;
111 /* MouseMotionListener methods */
113 public void mouseDragged(MouseEvent e) {
114 map.touch_continue(e.getPoint().x, e.getPoint().y, is_drag_event(e));
117 public void mouseMoved(MouseEvent e) {
120 /* MouseListener methods */
121 public void mouseClicked(MouseEvent e) {
124 public void mouseEntered(MouseEvent e) {
127 public void mouseExited(MouseEvent e) {
130 public void mousePressed(MouseEvent e) {
131 map.touch_start(e.getPoint().x, e.getPoint().y, is_drag_event(e));
134 public void mouseReleased(MouseEvent e) {
137 /* MouseWheelListener methods */
139 public void mouseWheelMoved(MouseWheelEvent e) {
140 int zoom_change = e.getWheelRotation();
142 map.notice_user_input();
143 AltosLatLon mouse_lat_lon = map.transform.screen_lat_lon(new AltosPointInt(e.getPoint().x, e.getPoint().y));
144 map.set_zoom(map.get_zoom() - zoom_change);
146 AltosPointDouble new_mouse = map.transform.screen(mouse_lat_lon);
148 int dx = getWidth()/2 - e.getPoint().x;
149 int dy = getHeight()/2 - e.getPoint().y;
151 AltosLatLon new_centre = map.transform.screen_lat_lon(new AltosPointInt((int) new_mouse.x + dx, (int) new_mouse.y + dy));
153 map.centre(new_centre);
156 /* ComponentListener methods */
158 public void componentHidden(ComponentEvent e) {
161 public void componentMoved(ComponentEvent e) {
164 public void componentResized(ComponentEvent e) {
168 public void componentShown(ComponentEvent e) {
173 addComponentListener(this);
174 addMouseMotionListener(this);
175 addMouseListener(this);
176 addMouseWheelListener(this);
180 class MapLine extends AltosMapLine {
182 public void paint(AltosMapTransform t) {
184 if (start == null || end == null)
187 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
189 Line2D.Double line = new Line2D.Double(point2d(t.screen(start)),
190 point2d(t.screen(end)));
192 g.setColor(Color.WHITE);
193 g.setStroke(new BasicStroke(stroke_width+4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
196 g.setColor(Color.BLUE);
197 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
200 String message = line_dist();
202 bounds = line_font.getStringBounds(message, g.getFontRenderContext());
204 float x = (float) line.x1;
205 float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
207 if (line.x1 < line.x2) {
208 x -= (float) bounds.getWidth() + 2.0f;
213 g.setFont(line_font);
214 g.setColor(Color.WHITE);
215 for (int dy = -2; dy <= 2; dy += 2)
216 for (int dx = -2; dx <= 2; dx += 2)
217 g.drawString(message, x + dx, y + dy);
218 g.setColor(Color.BLUE);
219 g.drawString(message, x, y);
226 class MapPath extends AltosMapPath {
227 public void paint(AltosMapTransform t) {
228 Point2D.Double prev = null;
230 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
231 RenderingHints.VALUE_ANTIALIAS_ON);
232 g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
234 for (AltosMapPathPoint point : points) {
235 Point2D.Double cur = point2d(t.screen(point.lat_lon));
237 Line2D.Double line = new Line2D.Double (prev, cur);
238 Rectangle bounds = line.getBounds();
240 if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) {
241 if (0 <= point.state && point.state < AltosUIMapNew.stateColors.length)
242 g.setColor(AltosUIMapNew.stateColors[point.state]);
244 g.setColor(AltosUIMapNew.stateColors[AltosLib.ao_flight_invalid]);
254 class MapTile extends AltosMapTile {
255 public MapTile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
256 super(listener, upper_left, center, zoom, maptype, px_size);
259 public void paint(AltosMapTransform t) {
261 AltosPointDouble point_double = t.screen(upper_left);
262 Point point = new Point((int) (point_double.x + 0.5),
263 (int) (point_double.y + 0.5));
265 if (!g.hitClip(point.x, point.y, px_size, px_size))
268 AltosImage altos_image = cache.get(this, store, px_size, px_size);
270 AltosUIImage ui_image = (AltosUIImage) altos_image;
274 if (ui_image != null)
275 image = ui_image.image;
278 g.drawImage(image, point.x, point.y, null);
280 g.setColor(Color.GRAY);
281 g.fillRect(point.x, point.y, px_size, px_size);
283 if (t.has_location()) {
284 String message = null;
286 case AltosMapTile.loading:
287 message = "Loading...";
289 case AltosMapTile.bad_request:
290 message = "Internal error";
292 case AltosMapTile.failed:
293 message = "Network error, check connection";
295 case AltosMapTile.forbidden:
296 message = "Too many requests, try later";
299 if (message != null && tile_font != null) {
300 g.setFont(tile_font);
301 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
302 Rectangle2D bounds = tile_font.getStringBounds(message, g.getFontRenderContext());
304 float x = px_size / 2.0f;
305 float y = px_size / 2.0f;
306 x = x - (float) bounds.getWidth() / 2.0f;
307 y = y + (float) bounds.getHeight() / 2.0f;
308 g.setColor(Color.BLACK);
309 g.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
316 public static final Color stateColors[] = {
317 Color.WHITE, // startup
322 Color.YELLOW, // coast
323 Color.CYAN, // drogue
325 Color.BLACK, // landed
326 Color.BLACK, // invalid
327 Color.CYAN, // stateless
330 /* AltosMapInterface functions */
332 public AltosMapPath new_path() {
333 return new MapPath();
336 public AltosMapLine new_line() {
337 return new MapLine();
340 public AltosImage load_image(File file) throws Exception {
341 return new AltosUIImage(ImageIO.read(file));
344 public AltosMapMark new_mark(double lat, double lon, int state) {
345 return new MapMark(lat, lon, state);
348 public AltosMapTile new_tile(AltosMapTileListener listener, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size) {
349 return new MapTile(listener, upper_left, center, zoom, maptype, px_size);
353 return view.getWidth();
356 public int height() {
357 return view.getHeight();
360 public void repaint() {
364 public void repaint(AltosRectangle damage) {
365 view.repaint(damage);
368 public void set_zoom_label(String label) {
369 zoom_label.setText(label);
372 public void debug(String format, Object ... arguments) {
373 System.out.printf(format, arguments);
376 /* AltosFlightDisplay interface */
378 public void set_font() {
379 tile_font = AltosUILib.value_font;
380 line_font = AltosUILib.status_font;
383 public void font_size_changed(int font_size) {
388 public void units_changed(boolean imperial_units) {
394 public void set_maptype(int type) {
395 map.set_maptype(type);
396 maptype_combo.setSelectedIndex(type);
399 /* AltosUIMapPreload functions */
401 public void set_zoom(int zoom) {
405 public void add_mark(double lat, double lon, int status) {
406 map.add_mark(lat, lon, status);
409 public void clear_marks() {
413 /* AltosFlightDisplay interface */
414 public void reset() {
418 public void show(AltosState state, AltosListenerState listener_state) {
419 map.show(state, listener_state);
422 public String getName() {
426 /* AltosGraphUI interface */
427 public void centre(AltosState state) {
431 /* internal layout bits */
432 private GridBagLayout layout = new GridBagLayout();
434 JComboBox<String> maptype_combo;
438 public AltosUIMapNew() {
442 view = new MapView();
444 view.setPreferredSize(new Dimension(500,500));
445 view.setVisible(true);
446 view.setEnabled(true);
448 GridBagLayout my_layout = new GridBagLayout();
450 setLayout(my_layout);
452 GridBagConstraints c = new GridBagConstraints();
453 c.anchor = GridBagConstraints.CENTER;
454 c.fill = GridBagConstraints.BOTH;
465 zoom_label = new JLabel("", JLabel.CENTER);
467 c = new GridBagConstraints();
468 c.anchor = GridBagConstraints.CENTER;
469 c.fill = GridBagConstraints.HORIZONTAL;
476 JButton zoom_reset = new JButton("0");
477 zoom_reset.addActionListener(new ActionListener() {
478 public void actionPerformed(ActionEvent e) {
479 map.set_zoom(map.default_zoom);
483 c = new GridBagConstraints();
484 c.anchor = GridBagConstraints.CENTER;
485 c.fill = GridBagConstraints.HORIZONTAL;
492 JButton zoom_in = new JButton("+");
493 zoom_in.addActionListener(new ActionListener() {
494 public void actionPerformed(ActionEvent e) {
495 map.set_zoom(map.get_zoom() + 1);
499 c = new GridBagConstraints();
500 c.anchor = GridBagConstraints.CENTER;
501 c.fill = GridBagConstraints.HORIZONTAL;
508 JButton zoom_out = new JButton("-");
509 zoom_out.addActionListener(new ActionListener() {
510 public void actionPerformed(ActionEvent e) {
511 map.set_zoom(map.get_zoom() - 1);
514 c = new GridBagConstraints();
515 c.anchor = GridBagConstraints.CENTER;
516 c.fill = GridBagConstraints.HORIZONTAL;
523 maptype_combo = new JComboBox<String>(map.maptype_labels);
525 maptype_combo.setEditable(false);
526 maptype_combo.setMaximumRowCount(maptype_combo.getItemCount());
527 maptype_combo.addItemListener(new ItemListener() {
528 public void itemStateChanged(ItemEvent e) {
529 map.set_maptype(maptype_combo.getSelectedIndex());
533 c = new GridBagConstraints();
534 c.anchor = GridBagConstraints.CENTER;
535 c.fill = GridBagConstraints.HORIZONTAL;
540 add(maptype_combo, c);
542 map = new AltosMap(this);