altosuilib: Decompress map images asynchronously and in parallel
[fw/altos] / altosuilib / AltosSiteMapTile.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_2;
19
20 import java.awt.*;
21 import java.awt.image.*;
22 import javax.swing.*;
23 import javax.imageio.*;
24 import java.awt.geom.*;
25 import java.io.*;
26 import java.util.*;
27 import java.awt.RenderingHints.*;
28 import org.altusmetrum.altoslib_4.*;
29
30 class AltosPoint {
31         Point2D.Double  pt;
32         int             state;
33
34         AltosPoint(Point2D.Double pt, int state) {
35                 this.pt = pt;
36                 this.state = state;
37         }
38 }
39
40 public class AltosSiteMapTile extends JComponent {
41         int px_size;
42         File file;
43         int status;
44
45         Point2D.Double  boost;
46         Point2D.Double  landed;
47         Line2D.Double   line;
48         double          line_course;
49         double          line_dist;
50
51         LinkedList<AltosPoint>  points;
52
53         public synchronized void queue_repaint() {
54                 if (SwingUtilities.isEventDispatchThread())
55                         repaint();
56                 else {
57                         SwingUtilities.invokeLater(new Runnable() {
58                                         public void run() {
59                                                 repaint();
60                                         }
61                                 });
62                 }
63         }
64
65         public void load_map(File pngFile) {
66                 file = pngFile;
67                 queue_repaint();
68         }
69
70         private Font    font = null;
71
72         public void set_font(Font font) {
73                 this.font = font;
74                 this.status = AltosSiteMapCache.success;
75         }
76
77         public void set_status(int status) {
78                 if (status != this.status || file != null) {
79                         file = null;
80                         this.status = status;
81                         queue_repaint();
82                 }
83         }
84
85         public void clearMap() {
86                 boost = null;
87                 landed = null;
88                 points = null;
89                 file = null;
90                 status = AltosSiteMapCache.success;
91                 line = null;
92         }
93
94         static Color stateColors[] = {
95                 Color.WHITE,  // startup
96                 Color.WHITE,  // idle
97                 Color.WHITE,  // pad
98                 Color.RED,    // boost
99                 Color.PINK,   // fast
100                 Color.YELLOW, // coast
101                 Color.CYAN,   // drogue
102                 Color.BLUE,   // main
103                 Color.BLACK   // landed
104         };
105
106         private void draw_circle(Graphics g, Point2D.Double pt) {
107                 g.drawOval((int)pt.x-5, (int)pt.y-5, 10, 10);
108                 g.drawOval((int)pt.x-20, (int)pt.y-20, 40, 40);
109                 g.drawOval((int)pt.x-35, (int)pt.y-35, 70, 70);
110         }
111
112         public void set_boost(Point2D.Double boost) {
113                 this.boost = boost;
114                 queue_repaint();
115         }
116
117         public void set_line(Line2D.Double line, double distance) {
118                 this.line = line;
119                 line_dist = distance;
120                 queue_repaint();
121         }
122
123         private String line_dist() {
124                 String  format;
125                 double  distance = line_dist;
126
127                 if (AltosConvert.imperial_units) {
128                         distance = AltosConvert.meters_to_feet(distance);
129                         if (distance < 10000) {
130                                 format = "%4.0fft";
131                         } else {
132                                 distance /= 5280;
133                                 if (distance < 10)
134                                         format = "%5.3fmi";
135                                 else if (distance < 100)
136                                         format = "%5.2fmi";
137                                 else if (distance < 1000)
138                                         format = "%5.1fmi";
139                                 else
140                                         format = "%5.0fmi";
141                         }
142                 } else {
143                         if (distance < 10000) {
144                                 format = "%4.0fm";
145                         } else {
146                                 distance /= 1000;
147                                 if (distance < 100)
148                                         format = "%5.2fkm";
149                                 else if (distance < 1000)
150                                         format = "%5.1fkm";
151                                 else
152                                         format = "%5.0fkm";
153                         }
154                 }
155                 return String.format(format, distance);
156         }
157
158         boolean painting;
159
160         public void paint_graphics(Graphics2D g2d, Image image) {
161                 if (image != null) {
162                         AltosSiteMap.debug_component(this, "paint_graphics");
163                         g2d.drawImage(image, 0, 0, null);
164                 } else {
165                         AltosSiteMap.debug_component(this, "erase_graphics");
166                         g2d.setColor(Color.GRAY);
167                         g2d.fillRect(0, 0, getWidth(), getHeight());
168                         String  message = null;
169                         switch (status) {
170                         case AltosSiteMapCache.loading:
171                                 message = "Loading...";
172                                 break;
173                         case AltosSiteMapCache.bad_request:
174                                 message = "Internal error";
175                                 break;
176                         case AltosSiteMapCache.failed:
177                                 message = "Network error, check connection";
178                                 break;
179                         case AltosSiteMapCache.forbidden:
180                                 message = "Too many requests, try later";
181                                 break;
182                         }
183                         if (message != null && font != null) {
184                                 g2d.setFont(font);
185                                 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
186                                 Rectangle2D     bounds;
187                                 bounds = font.getStringBounds(message, g2d.getFontRenderContext());
188
189                                 float x = getWidth() / 2.0f;
190                                 float y = getHeight() / 2.0f;
191                                 x = x - (float) bounds.getWidth() / 2.0f;
192                                 y = y + (float) bounds.getHeight() / 2.0f;
193                                 g2d.setColor(Color.BLACK);
194                                 g2d.drawString(message, x, y);
195                         }
196                 }
197
198                 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
199                                    RenderingHints.VALUE_ANTIALIAS_ON);
200                 g2d.setStroke(new BasicStroke(6, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
201
202                 if (points != null) {
203                         AltosPoint              prev = null;
204                         for (AltosPoint point : points) {
205                                 if (prev != null) {
206                                         if (0 <= point.state && point.state < stateColors.length)
207                                                 g2d.setColor(stateColors[point.state]);
208                                         g2d.draw(new Line2D.Double(prev.pt, point.pt));
209                                 }
210                                 prev = point;
211                         }
212                 }
213                 if (boost != null) {
214                         g2d.setColor(Color.RED);
215                         draw_circle(g2d, boost);
216                 }
217                 if (landed != null) {
218                         g2d.setColor(Color.BLACK);
219                         draw_circle(g2d, landed);
220                 }
221
222                 if (line != null) {
223                         g2d.setColor(Color.BLUE);
224                         g2d.draw(line);
225
226                         String  message = line_dist();
227                         g2d.setFont(font);
228                         g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
229                         Rectangle2D     bounds;
230                         bounds = font.getStringBounds(message, g2d.getFontRenderContext());
231
232                         float x = (float) line.x1;
233                         float y = (float) line.y1 + (float) bounds.getHeight() / 2.0f;
234
235                         if (line.x1 < line.x2) {
236                                 x -= (float) bounds.getWidth() + 2.0f;
237                         } else {
238                                 x += 2.0f;
239                         }
240                         g2d.drawString(message, x, y);
241                 }
242                 painting = false;
243         }
244
245         public void paint(Graphics g) {
246                 Graphics2D              g2d = (Graphics2D) g;
247                 Image                   image = null;
248                 boolean                 queued = false;
249
250                 if (painting) {
251                         AltosSiteMap.debug_component(this, "already painting");
252                         return;
253                 }
254                 AltosSiteMap.debug_component(this, "paint");
255
256                 if (file != null) {
257                         AltosSiteMapImage       aimage;
258
259                         aimage = AltosSiteMapCache.get_image(this, file, px_size, px_size);
260                         if (aimage != null) {
261                                 if (aimage.validate())
262                                         image = aimage.image;
263                                 else
264                                         queued = true;
265                         }
266                 }
267                 if (!queued)
268                         paint_graphics(g2d, image);
269                 painting = queued;
270         }
271
272         public void show(int state, Point2D.Double last_pt, Point2D.Double pt)
273         {
274                 if (points == null)
275                         points = new LinkedList<AltosPoint>();
276
277                 points.add(new AltosPoint(pt, state));
278
279                 if (state == AltosLib.ao_flight_boost && boost == null)
280                         boost = pt;
281                 if (state == AltosLib.ao_flight_landed && landed == null)
282                         landed = pt;
283                 queue_repaint();
284         }
285
286         public AltosSiteMapTile(int in_px_size) {
287                 px_size = in_px_size;
288                 setPreferredSize(new Dimension(px_size, px_size));
289         }
290 }