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