Add shipping products to fat_altos target, note that in Releasing
[fw/altos] / altosuilib / AltosUIMap.java
1 /*
2  * Copyright © 2010 Anthony Towns <aj@erisian.com.au>
3  *
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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.altosuilib_13;
20
21 import java.awt.*;
22 import java.awt.event.*;
23 import java.awt.image.*;
24 import javax.swing.*;
25 import java.io.*;
26 import java.lang.Math;
27 import java.awt.geom.*;
28 import java.util.*;
29 import java.util.concurrent.*;
30 import javax.imageio.*;
31 import org.altusmetrum.altoslib_13.*;
32
33 public class AltosUIMap extends JComponent implements AltosFlightDisplay, AltosMapInterface {
34
35         AltosMap        map;
36         Graphics2D      g;
37         Font            tile_font;
38         Font            line_font;
39
40         static Point2D.Double point2d(AltosPointDouble pt) {
41                 return new Point2D.Double(pt.x, pt.y);
42         }
43
44         static final AltosPointDouble point_double(Point pt) {
45                 return new AltosPointDouble(pt.x, pt.y);
46         }
47
48         class MapMark extends AltosMapMark {
49                 public void paint(AltosMapTransform t) {
50                         AltosPointDouble pt = t.screen(lat_lon);
51
52                         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
53                                            RenderingHints.VALUE_ANTIALIAS_ON);
54                         g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
55
56                         if (0 <= state && state < AltosUIMap.stateColors.length)
57                                 g.setColor(AltosUIMap.stateColors[state]);
58                         else
59                                 g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
60
61                         g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
62                         g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
63                         g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
64                 }
65
66                 MapMark(double lat, double lon, int state) {
67                         super(lat, lon, state);
68                 }
69         }
70
71         class MapView extends JComponent implements MouseMotionListener, MouseListener, ComponentListener, MouseWheelListener {
72
73                 private VolatileImage create_back_buffer() {
74                         return getGraphicsConfiguration().createCompatibleVolatileImage(getWidth(), getHeight());
75                 }
76
77                 private void do_paint(Graphics my_g) {
78                         g = (Graphics2D) my_g;
79
80                         map.paint();
81                 }
82
83                 public void paint(Graphics my_g) {
84                         VolatileImage   back_buffer = create_back_buffer();
85
86                         Graphics2D      top_g = (Graphics2D) my_g;
87
88                         do {
89                                 GraphicsConfiguration gc = getGraphicsConfiguration();
90                                 int code = back_buffer.validate(gc);
91                                 if (code == VolatileImage.IMAGE_INCOMPATIBLE)
92                                         back_buffer = create_back_buffer();
93
94                                 Graphics g_back = back_buffer.getGraphics();
95                                 g_back.setClip(top_g.getClip());
96                                 do_paint(g_back);
97                                 g_back.dispose();
98
99                                 top_g.drawImage(back_buffer, 0, 0, this);
100                         } while (back_buffer.contentsLost());
101                         back_buffer.flush();
102                 }
103
104                 public void repaint(AltosRectangle damage) {
105                         repaint(damage.x, damage.y, damage.width, damage.height);
106                 }
107
108                 private boolean is_drag_event(MouseEvent e) {
109                         return e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK;
110                 }
111
112                 /* MouseMotionListener methods */
113
114                 public void mouseDragged(MouseEvent e) {
115                         map.touch_continue(e.getPoint().x, e.getPoint().y, is_drag_event(e));
116                 }
117
118                 public void mouseMoved(MouseEvent e) {
119                 }
120
121                 /* MouseListener methods */
122                 public void mouseClicked(MouseEvent e) {
123                 }
124
125                 public void mouseEntered(MouseEvent e) {
126                 }
127
128                 public void mouseExited(MouseEvent e) {
129                 }
130
131                 public void mousePressed(MouseEvent e) {
132                         map.touch_start(e.getPoint().x, e.getPoint().y, is_drag_event(e));
133                 }
134
135                 public void mouseReleased(MouseEvent e) {
136                 }
137
138                 /* MouseWheelListener methods */
139
140                 public void mouseWheelMoved(MouseWheelEvent e) {
141                         int     zoom_change = e.getWheelRotation();
142
143                         map.set_zoom_centre(map.get_zoom() - zoom_change, new AltosPointInt(e.getPoint().x, e.getPoint().y));
144                 }
145
146                 /* ComponentListener methods */
147
148                 public void componentHidden(ComponentEvent e) {
149                 }
150
151                 public void componentMoved(ComponentEvent e) {
152                 }
153
154                 public void componentResized(ComponentEvent e) {
155                         map.set_transform();
156                 }
157
158                 public void componentShown(ComponentEvent e) {
159                         map.set_transform();
160                 }
161
162                 MapView() {
163                         addComponentListener(this);
164                         addMouseMotionListener(this);
165                         addMouseListener(this);
166                         addMouseWheelListener(this);
167                 }
168         }
169
170         class MapLine extends AltosMapLine {
171
172                 public void paint(AltosMapTransform t) {
173
174                         if (start == null || end == null)
175                                 return;
176
177                         g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
178
179                         Line2D.Double line = new Line2D.Double(point2d(t.screen(start)),
180                                                                point2d(t.screen(end)));
181
182                         g.setColor(Color.WHITE);
183                         g.setStroke(new BasicStroke(stroke_width+4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
184                         g.draw(line);
185
186                         g.setColor(Color.BLUE);
187                         g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
188                         g.draw(line);
189
190                         String  message = line_dist();
191                         Rectangle2D     bounds;
192                         bounds = line_font.getStringBounds(message, g.getFontRenderContext());
193
194                         float x = (float) line.x1;
195                         float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
196
197                         if (line.x1 < line.x2) {
198                                 x -= (float) bounds.getWidth() + 2.0f;
199                         } else {
200                                 x += 2.0f;
201                         }
202
203                         g.setFont(line_font);
204                         g.setColor(Color.WHITE);
205                         for (int dy = -2; dy <= 2; dy += 2)
206                                 for (int dx = -2; dx <= 2; dx += 2)
207                                         g.drawString(message, x + dx, y + dy);
208                         g.setColor(Color.BLUE);
209                         g.drawString(message, x, y);
210                 }
211
212                 public MapLine() {
213                 }
214         }
215
216         class MapPath extends AltosMapPath {
217                 public void paint(AltosMapTransform t) {
218                         Point2D.Double  prev = null;
219
220                         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
221                                            RenderingHints.VALUE_ANTIALIAS_ON);
222                         g.setStroke(new BasicStroke(stroke_width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
223
224                         for (AltosMapPathPoint point : points) {
225                                 Point2D.Double  cur = point2d(t.screen(point.lat_lon));
226                                 if (prev != null) {
227                                         Line2D.Double   line = new Line2D.Double (prev, cur);
228                                         Rectangle       bounds = line.getBounds();
229
230                                         if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height)) {
231                                                 if (0 <= point.state && point.state < AltosUIMap.stateColors.length)
232                                                         g.setColor(AltosUIMap.stateColors[point.state]);
233                                                 else
234                                                         g.setColor(AltosUIMap.stateColors[AltosLib.ao_flight_invalid]);
235
236                                                 g.draw(line);
237                                         }
238                                 }
239                                 prev = cur;
240                         }
241                 }
242         }
243
244         class MapTile extends AltosMapTile {
245                 public MapTile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
246                         super(cache, upper_left, center, zoom, maptype, px_size, scale);
247                 }
248
249                 public void paint(AltosMapTransform t) {
250
251                         AltosPointDouble        point_double = t.screen(upper_left);
252                         Point                   point = new Point((int) (point_double.x + 0.5),
253                                                                   (int) (point_double.y + 0.5));
254
255                         if (!g.hitClip(point.x, point.y, px_size, px_size))
256                                 return;
257
258                         AltosImage      altos_image = get_image();
259                         AltosUIImage    ui_image = (AltosUIImage) altos_image;
260                         Image           image = null;
261
262                         if (ui_image != null)
263                                 image = ui_image.image;
264
265                         if (image != null) {
266                                 g.drawImage(image, point.x, point.y, null);
267 /*
268  * Useful when debugging map fetching problems
269  *
270                                 String message = String.format("%.6f %.6f", center.lat, center.lon);
271                                 g.setFont(tile_font);
272                                 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
273                                 Rectangle2D bounds = tile_font.getStringBounds(message, g.getFontRenderContext());
274
275                                 float x = px_size / 2.0f;
276                                 float y = px_size / 2.0f;
277                                 x = x - (float) bounds.getWidth() / 2.0f;
278                                 y = y + (float) bounds.getHeight() / 2.0f;
279                                 g.setColor(Color.RED);
280                                 g.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
281 */
282                         } else {
283                                 g.setColor(Color.GRAY);
284                                 g.fillRect(point.x, point.y, px_size, px_size);
285
286                                 if (t.has_location()) {
287                                         String  message = null;
288                                         switch (status) {
289                                         case AltosMapTile.fetching:
290                                                 message = "Fetching...";
291                                                 break;
292                                         case AltosMapTile.bad_request:
293                                                 message = "Internal error";
294                                                 break;
295                                         case AltosMapTile.failed:
296                                                 message = "Network error";
297                                                 break;
298                                         case AltosMapTile.forbidden:
299                                                 message = "Outside of known launch areas";
300                                                 break;
301                                         }
302                                         if (message != null && tile_font != null) {
303                                                 g.setFont(tile_font);
304                                                 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
305                                                 Rectangle2D bounds = tile_font.getStringBounds(message, g.getFontRenderContext());
306
307                                                 float x = px_size / 2.0f;
308                                                 float y = px_size / 2.0f;
309                                                 x = x - (float) bounds.getWidth() / 2.0f;
310                                                 y = y + (float) bounds.getHeight() / 2.0f;
311                                                 g.setColor(Color.BLACK);
312                                                 g.drawString(message, (float) point_double.x + x, (float) point_double.y + y);
313                                         }
314                                 }
315                         }
316                 }
317         }
318
319         public static final Color stateColors[] = {
320                 Color.WHITE,  // startup
321                 Color.WHITE,  // idle
322                 Color.WHITE,  // pad
323                 Color.RED,    // boost
324                 Color.PINK,   // fast
325                 Color.YELLOW, // coast
326                 Color.CYAN,   // drogue
327                 Color.BLUE,   // main
328                 Color.BLACK,  // landed
329                 Color.BLACK,  // invalid
330                 Color.CYAN,   // stateless
331         };
332
333         /* AltosMapInterface functions */
334
335         public AltosMapPath new_path() {
336                 return new MapPath();
337         }
338
339         public AltosMapLine new_line() {
340                 return new MapLine();
341         }
342
343         public AltosImage load_image(File file) throws Exception {
344                 return new AltosUIImage(ImageIO.read(file));
345         }
346
347         public AltosMapMark new_mark(double lat, double lon, int state) {
348                 return new MapMark(lat, lon, state);
349         }
350
351         public AltosMapTile new_tile(AltosMapCache cache, AltosLatLon upper_left, AltosLatLon center, int zoom, int maptype, int px_size, int scale) {
352                 return new MapTile(cache, upper_left, center, zoom, maptype, px_size, scale);
353         }
354
355         public int width() {
356                 return view.getWidth();
357         }
358
359         public int height() {
360                 return view.getHeight();
361         }
362
363         public void repaint() {
364                 view.repaint();
365         }
366
367         public void repaint(AltosRectangle damage) {
368                 view.repaint(damage);
369         }
370
371         public void set_zoom_label(String label) {
372                 zoom_label.setText(label);
373         }
374
375         public void select_object(AltosLatLon latlon) {
376                 debug("select at %f,%f\n", latlon.lat, latlon.lon);
377         }
378
379         public void debug(String format, Object ... arguments) {
380                 if (AltosUIPreferences.serial_debug())
381                         System.out.printf(format, arguments);
382         }
383
384
385         /* AltosFlightDisplay interface */
386
387         public void set_font() {
388                 tile_font = AltosUILib.value_font;
389                 line_font = AltosUILib.status_font;
390         }
391
392         public void font_size_changed(int font_size) {
393                 set_font();
394                 repaint();
395         }
396
397         public void units_changed(boolean imperial_units) {
398                 repaint();
399         }
400
401         JLabel  zoom_label;
402
403         public void set_maptype(int type) {
404 /*
405                 map.set_maptype(type);
406                 maptype_combo.setSelectedIndex(type);
407 */
408         }
409
410         /* AltosUIMapPreload functions */
411
412         public void set_zoom(int zoom) {
413                 map.set_zoom(zoom);
414         }
415
416         public void add_mark(double lat, double lon, int status) {
417                 map.add_mark(lat, lon, status);
418         }
419
420         public void clear_marks() {
421                 map.clear_marks();
422         }
423
424         /* AltosFlightDisplay interface */
425         public void reset() {
426                 // nothing
427         }
428
429         public void show(AltosState state, AltosListenerState listener_state) {
430                 map.show(state, listener_state);
431         }
432
433         public void show(AltosGPS gps, int state) {
434                 map.show(gps, state);
435         }
436
437         public String getName() {
438                 return "Map";
439         }
440
441         /* AltosGraphUI interface */
442         public void centre(AltosState state) {
443                 map.centre(state);
444         }
445
446         public void centre(AltosGPS gps) {
447                 map.centre(gps);
448         }
449
450         /* internal layout bits */
451         private GridBagLayout layout = new GridBagLayout();
452
453 /*
454         JComboBox<String>       maptype_combo;
455 */
456
457         MapView view;
458
459         public AltosUIMap() {
460
461                 set_font();
462
463                 view = new MapView();
464
465                 view.setPreferredSize(new Dimension(500,500));
466                 view.setVisible(true);
467                 view.setEnabled(true);
468
469                 GridBagLayout   my_layout = new GridBagLayout();
470
471                 setLayout(my_layout);
472
473                 GridBagConstraints c = new GridBagConstraints();
474                 c.anchor = GridBagConstraints.CENTER;
475                 c.fill = GridBagConstraints.BOTH;
476                 c.gridx = 0;
477                 c.gridy = 0;
478                 c.gridwidth = 1;
479                 c.gridheight = 10;
480                 c.weightx = 1;
481                 c.weighty = 1;
482                 add(view, c);
483
484                 int     y = 0;
485
486                 zoom_label = new JLabel("", JLabel.CENTER);
487
488                 c = new GridBagConstraints();
489                 c.anchor = GridBagConstraints.CENTER;
490                 c.fill = GridBagConstraints.HORIZONTAL;
491                 c.gridx = 1;
492                 c.gridy = y++;
493                 c.weightx = 0;
494                 c.weighty = 0;
495                 add(zoom_label, c);
496
497                 JButton zoom_reset = new JButton("0");
498                 zoom_reset.addActionListener(new ActionListener() {
499                                 public void actionPerformed(ActionEvent e) {
500                                         map.set_zoom(map.default_zoom);
501                                 }
502                         });
503
504                 c = new GridBagConstraints();
505                 c.anchor = GridBagConstraints.CENTER;
506                 c.fill = GridBagConstraints.HORIZONTAL;
507                 c.gridx = 1;
508                 c.gridy = y++;
509                 c.weightx = 0;
510                 c.weighty = 0;
511                 add(zoom_reset, c);
512
513                 JButton zoom_in = new JButton("+");
514                 zoom_in.addActionListener(new ActionListener() {
515                                 public void actionPerformed(ActionEvent e) {
516                                         map.set_zoom(map.get_zoom() + 1);
517                                 }
518                         });
519
520                 c = new GridBagConstraints();
521                 c.anchor = GridBagConstraints.CENTER;
522                 c.fill = GridBagConstraints.HORIZONTAL;
523                 c.gridx = 1;
524                 c.gridy = y++;
525                 c.weightx = 0;
526                 c.weighty = 0;
527                 add(zoom_in, c);
528
529                 JButton zoom_out = new JButton("-");
530                 zoom_out.addActionListener(new ActionListener() {
531                                 public void actionPerformed(ActionEvent e) {
532                                         map.set_zoom(map.get_zoom() - 1);
533                                 }
534                         });
535                 c = new GridBagConstraints();
536                 c.anchor = GridBagConstraints.CENTER;
537                 c.fill = GridBagConstraints.HORIZONTAL;
538                 c.gridx = 1;
539                 c.gridy = y++;
540                 c.weightx = 0;
541                 c.weighty = 0;
542                 add(zoom_out, c);
543
544 /*
545                 maptype_combo = new JComboBox<String>(map.maptype_labels);
546
547                 maptype_combo.setEditable(false);
548                 maptype_combo.setMaximumRowCount(maptype_combo.getItemCount());
549                 maptype_combo.addItemListener(new ItemListener() {
550                                 public void itemStateChanged(ItemEvent e) {
551                                         map.set_maptype(maptype_combo.getSelectedIndex());
552                                 }
553                         });
554
555                 c = new GridBagConstraints();
556                 c.anchor = GridBagConstraints.CENTER;
557                 c.fill = GridBagConstraints.HORIZONTAL;
558                 c.gridx = 1;
559                 c.gridy = y++;
560                 c.weightx = 0;
561                 c.weighty = 0;
562                 add(maptype_combo, c);
563 */
564                 map = new AltosMap(this);
565         }
566 }