altosuilib: Use new map code for map preload UI
[fw/altos] / telegps / TeleGPS.java
1 /*
2  * Copyright © 2014 Keith Packard <keithp@keithp.com>
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.telegps;
19
20 import java.awt.*;
21 import java.awt.event.*;
22 import javax.swing.*;
23 import java.io.*;
24 import java.util.concurrent.*;
25 import java.util.*;
26 import java.text.*;
27 import org.altusmetrum.altoslib_7.*;
28 import org.altusmetrum.altosuilib_7.*;
29
30 public class TeleGPS
31         extends AltosUIFrame
32         implements AltosFlightDisplay, AltosFontListener, AltosUnitsListener, ActionListener
33 {
34
35         static String[] telegps_icon_names = {
36                 "/altusmetrum-telegps-16.png",
37                 "/altusmetrum-telegps-32.png",
38                 "/altusmetrum-telegps-48.png",
39                 "/altusmetrum-telegps-64.png",
40                 "/altusmetrum-telegps-128.png",
41                 "/altusmetrum-telegps-256.png"
42         };
43
44         static { set_icon_names(telegps_icon_names); }
45
46         static AltosVoice       voice;
47
48         static AltosVoice voice() {
49                 if (voice == null)
50                         voice = new AltosVoice();
51                 return voice;
52         }
53
54         AltosFlightReader       reader;
55         TeleGPSDisplayThread    thread;
56
57         JMenuBar                menu_bar;
58
59         JMenu                   file_menu;
60         JMenu                   monitor_menu;
61         JMenu                   device_menu;
62         AltosUIFreqList         frequencies;
63         ActionListener          frequency_listener;
64         AltosUIRateList         rates;
65         ActionListener          rate_listener;
66
67         Container               bag;
68
69         TeleGPSStatus           telegps_status;
70         TeleGPSStatusUpdate     status_update;
71
72         JTabbedPane             pane;
73
74         AltosUIMapNew           map;
75         TeleGPSInfo             gps_info;
76         TeleGPSState            gps_state;
77         AltosInfoTable          info_table;
78
79         LinkedList<AltosFlightDisplay>  displays;
80
81         /* File menu */
82         final static String     new_command = "new";
83         final static String     graph_command = "graph";
84         final static String     export_command = "export";
85         final static String     load_maps_command = "loadmaps";
86         final static String     preferences_command = "preferences";
87         final static String     close_command = "close";
88         final static String     exit_command = "exit";
89
90         static final String[][] file_menu_entries = new String[][] {
91                 { "New Window",         new_command },
92                 { "Graph Data",         graph_command },
93                 { "Export Data",        export_command },
94                 { "Load Maps",          load_maps_command },
95                 { "Preferences",        preferences_command },
96                 { "Close",              close_command },
97                 { "Exit",               exit_command },
98         };
99
100         /* Monitor menu */
101         final static String     connect_command = "connect";
102         final static String     disconnect_command = "disconnect";
103         final static String     scan_command = "scan";
104
105         static final String[][] monitor_menu_entries = new String[][] {
106                 { "Connect Device",     connect_command },
107                 { "Disconnect",         disconnect_command },
108                 { "Scan Channels",      scan_command },
109         };
110
111         /* Device menu */
112         final static String     download_command = "download";
113         final static String     configure_command = "configure";
114         final static String     flash_command = "flash";
115
116         static final String[][] device_menu_entries = new String[][] {
117                 { "Download Data",      download_command },
118                 { "Configure Device",   configure_command },
119                 { "Flash Device",       flash_command },
120         };
121
122         void stop_display() {
123                 if (thread != null && thread.isAlive()) {
124                         thread.interrupt();
125                         try {
126                                 thread.join();
127                         } catch (InterruptedException ie) {}
128                 }
129                 thread = null;
130         }
131
132         public void reset() {
133                 for (AltosFlightDisplay display : displays)
134                         display.reset();
135         }
136
137         public void font_size_changed(int font_size) {
138                 for (AltosFlightDisplay display : displays)
139                         display.font_size_changed(font_size);
140         }
141
142         public void units_changed(boolean imperial_units) {
143                 for (AltosFlightDisplay display : displays)
144                         display.units_changed(imperial_units);
145         }
146
147         public void show(AltosState state, AltosListenerState listener_state) {
148                 try {
149                         status_update.saved_state = state;
150                         status_update.saved_listener_state = listener_state;
151
152                         if (state == null)
153                                 state = new AltosState();
154
155                         int i = 0;
156                         for (AltosFlightDisplay display : displays) {
157                                 display.show(state, listener_state);
158                                 i++;
159                         }
160                 } catch (Exception ex) {
161                         System.out.printf("Exception %s\n", ex.toString());
162                         for (StackTraceElement e : ex.getStackTrace())
163                                 System.out.printf("%s\n", e.toString());
164                 }
165         }
166
167         void new_window() {
168                 new TeleGPS();
169         }
170
171         void preferences() {
172                 new TeleGPSPreferences(this, voice());
173         }
174
175         void load_maps() {
176                 new AltosUIMapPreloadNew(this);
177         }
178
179         void disconnect() {
180                 setTitle("TeleGPS");
181                 stop_display();
182                 telegps_status.stop();
183
184                 telegps_status.disable_receive();
185                 disable_frequency_menu();
186                 disable_rate_menu();
187         }
188
189         void connect(AltosDevice device) {
190                 if (reader != null)
191                         disconnect();
192                 try {
193                         AltosFlightReader       reader = new AltosTelemetryReader(new AltosSerial(device));
194                         set_reader(reader, device);
195                 } catch (FileNotFoundException ee) {
196                         JOptionPane.showMessageDialog(this,
197                                                       ee.getMessage(),
198                                                       String.format ("Cannot open %s", device.toShortString()),
199                                                       JOptionPane.ERROR_MESSAGE);
200                 } catch (AltosSerialInUseException si) {
201                         JOptionPane.showMessageDialog(this,
202                                                       String.format("Device \"%s\" already in use",
203                                                                     device.toShortString()),
204                                                       "Device in use",
205                                                       JOptionPane.ERROR_MESSAGE);
206                 } catch (IOException ee) {
207                         JOptionPane.showMessageDialog(this,
208                                                       String.format ("Unknown I/O error on %s", device.toShortString()),
209                                                       "Unknown I/O error",
210                                                       JOptionPane.ERROR_MESSAGE);
211                 } catch (TimeoutException te) {
212                         JOptionPane.showMessageDialog(this,
213                                                       String.format ("Timeout on %s", device.toShortString()),
214                                                       "Timeout error",
215                                                       JOptionPane.ERROR_MESSAGE);
216                 } catch (InterruptedException ie) {
217                         JOptionPane.showMessageDialog(this,
218                                                       String.format("Interrupted %s", device.toShortString()),
219                                                       "Interrupted exception",
220                                                       JOptionPane.ERROR_MESSAGE);
221                 }
222         }
223
224         void connect() {
225                 AltosDevice     device = AltosDeviceUIDialog.show(this,
226                                                                   AltosLib.product_basestation);
227                 if (device == null)
228                         return;
229                 connect(device);
230         }
231
232         public void scan_device_selected(AltosDevice device) {
233                 connect(device);
234         }
235
236         void scan() {
237                 new AltosScanUI(this, false);
238         }
239
240         void download(){
241                 new AltosEepromManage(this, AltosLib.product_telegps);
242         }
243
244         void configure() {
245                 new TeleGPSConfig(this);
246         }
247
248         void export() {
249                 AltosDataChooser chooser;
250                 chooser = new AltosDataChooser(this);
251                 AltosStateIterable states = chooser.runDialog();
252                 if (states == null)
253                         return;
254                 new AltosCSVUI(this, states, chooser.file());
255         }
256
257         void graph() {
258                 AltosDataChooser chooser;
259                 chooser = new AltosDataChooser(this);
260                 AltosStateIterable states = chooser.runDialog();
261                 if (states == null)
262                         return;
263                 try {
264                         new TeleGPSGraphUI(states, chooser.file());
265                 } catch (InterruptedException ie) {
266                 } catch (IOException ie) {
267                 }
268         }
269
270         void flash() {
271                 AltosFlashUI.show(this);
272         }
273
274         public void actionPerformed(ActionEvent ev) {
275
276                 /* File menu */
277                 if (new_command.equals(ev.getActionCommand())) {
278                         new_window();
279                         return;
280                 }
281                 if (preferences_command.equals(ev.getActionCommand())) {
282                         preferences();
283                         return;
284                 }
285                 if (load_maps_command.equals(ev.getActionCommand())) {
286                         load_maps();
287                         return;
288                 }
289                 if (close_command.equals(ev.getActionCommand())) {
290                         close();
291                         return;
292                 }
293                 if (exit_command.equals(ev.getActionCommand()))
294                         System.exit(0);
295
296                 /* Monitor menu */
297                 if (connect_command.equals(ev.getActionCommand())) {
298                         connect();
299                         return;
300                 }
301                 if (disconnect_command.equals(ev.getActionCommand())) {
302                         disconnect();
303                         return;
304                 }
305                 if (scan_command.equals(ev.getActionCommand())) {
306                         scan();
307                         return;
308                 }
309
310                 /* Device menu */
311                 if (download_command.equals(ev.getActionCommand())) {
312                         download();
313                         return;
314                 }
315                 if (configure_command.equals(ev.getActionCommand())) {
316                         configure();
317                         return;
318                 }
319                 if (export_command.equals(ev.getActionCommand())) {
320                         export();
321                         return;
322                 }
323                 if (graph_command.equals(ev.getActionCommand())) {
324                         graph();
325                         return;
326                 }
327                 if (flash_command.equals(ev.getActionCommand())) {
328                         flash();
329                         return;
330                 }
331         }
332
333         void enable_frequency_menu(int serial, final AltosFlightReader reader) {
334
335                 if (frequency_listener != null)
336                         disable_frequency_menu();
337
338                 frequency_listener = new ActionListener() {
339                                 public void actionPerformed(ActionEvent e) {
340                                         double frequency = frequencies.frequency();
341                                         try {
342                                                 reader.set_frequency(frequency);
343                                         } catch (TimeoutException te) {
344                                         } catch (InterruptedException ie) {
345                                         }
346                                         reader.save_frequency();
347                                 }
348                         };
349
350                 frequencies.addActionListener(frequency_listener);
351                 frequencies.set_product("Monitor");
352                 frequencies.set_serial(serial);
353                 frequencies.set_frequency(AltosUIPreferences.frequency(serial));
354                 frequencies.setEnabled(true);
355
356         }
357
358         void disable_frequency_menu() {
359                 if (frequency_listener != null) {
360                         frequencies.removeActionListener(frequency_listener);
361                         frequencies.setEnabled(false);
362                         frequency_listener = null;
363                 }
364
365         }
366
367         void enable_rate_menu(int serial, final AltosFlightReader reader) {
368
369                 if (rate_listener != null)
370                         disable_rate_menu();
371
372                 rate_listener = new ActionListener() {
373                                 public void actionPerformed(ActionEvent e) {
374                                         int rate = rates.rate();
375                                         try {
376                                                 System.out.printf("set rate %d\n", rate);
377                                                 reader.set_telemetry_rate(rate);
378                                         } catch (TimeoutException te) {
379                                         } catch (InterruptedException ie) {
380                                         }
381                                         reader.save_telemetry_rate();
382                                 }
383                         };
384
385                 rates.addActionListener(rate_listener);
386                 rates.set_product("Monitor");
387                 rates.set_serial(serial);
388                 rates.set_rate(AltosUIPreferences.telemetry_rate(serial));
389                 rates.setEnabled(reader.supports_telemetry_rate(AltosLib.ao_telemetry_rate_2400));
390         }
391
392         void disable_rate_menu() {
393                 if (rate_listener != null) {
394                         rates.removeActionListener(rate_listener);
395                         rates.setEnabled(false);
396                         rate_listener = null;
397                 }
398
399         }
400
401         public void set_reader(AltosFlightReader reader, AltosDevice device) {
402                 status_update = new TeleGPSStatusUpdate(telegps_status);
403
404                 telegps_status.start(status_update);
405
406                 setTitle(String.format("TeleGPS %s", reader.name));
407                 thread = new TeleGPSDisplayThread(this, voice(), this, reader);
408                 thread.start();
409
410                 if (device != null) {
411                         enable_frequency_menu(device.getSerial(), reader);
412                         enable_rate_menu(device.getSerial(), reader);
413                 }
414         }
415
416         static int      number_of_windows;
417
418         static public void add_window() {
419                 ++number_of_windows;
420         }
421
422         static public void subtract_window() {
423                 --number_of_windows;
424                 if (number_of_windows == 0)
425                         System.exit(0);
426         }
427
428         private void close() {
429                 disconnect();
430                 AltosUIPreferences.unregister_font_listener(this);
431                 AltosPreferences.unregister_units_listener(this);
432                 setVisible(false);
433                 dispose();
434                 subtract_window();
435         }
436
437         private void add_menu(JMenu menu, String label, String action) {
438                 JMenuItem       item = new JMenuItem(label);
439                 menu.add(item);
440                 item.addActionListener(this);
441                 item.setActionCommand(action);
442         }
443
444
445         private JMenu make_menu(String label, String[][] items) {
446                 JMenu   menu = new JMenu(label);
447                 for (int i = 0; i < items.length; i++) {
448                         if (MAC_OS_X) {
449                                 if (items[i][1].equals("exit"))
450                                         continue;
451                                 if (items[i][1].equals("preferences"))
452                                         continue;
453                         }
454                         add_menu(menu, items[i][0], items[i][1]);
455                 }
456                 menu_bar.add(menu);
457                 return menu;
458         }
459
460         /* OSXAdapter interfaces */
461         public void macosx_file_handler(String path) {
462                 process_graph(new File(path));
463         }
464
465         public void macosx_quit_handler() {
466                 System.exit(0);
467         }
468
469         public void macosx_preferences_handler() {
470                 preferences();
471         }
472
473         public TeleGPS() {
474
475                 AltosUIPreferences.set_component(this);
476
477                 register_for_macosx_events();
478
479                 reader = null;
480
481                 bag = getContentPane();
482                 bag.setLayout(new GridBagLayout());
483
484                 GridBagConstraints c = new GridBagConstraints();
485
486                 setTitle("TeleGPS");
487
488                 menu_bar = new JMenuBar();
489                 setJMenuBar(menu_bar);
490
491                 file_menu = make_menu("File", file_menu_entries);
492                 monitor_menu = make_menu("Monitor", monitor_menu_entries);
493                 device_menu = make_menu("Device", device_menu_entries);
494
495                 frequencies = new AltosUIFreqList();
496                 frequencies.setEnabled(false);
497                 c.gridx = 0;
498                 c.gridy = 0;
499                 c.fill = GridBagConstraints.NONE;
500                 c.anchor = GridBagConstraints.WEST;
501                 c.weightx = 0;
502                 c.gridwidth = 1;
503                 bag.add(frequencies, c);
504
505                 rates = new AltosUIRateList();
506                 rates.setEnabled(false);
507                 c.gridx = 1;
508                 c.gridy = 0;
509                 c.fill = GridBagConstraints.NONE;
510                 c.anchor = GridBagConstraints.WEST;
511                 c.weightx = 0;
512                 c.gridwidth = 1;
513                 bag.add(rates, c);
514
515                 displays = new LinkedList<AltosFlightDisplay>();
516
517                 int serial = -1;
518
519                 /* TeleGPS status is always visible */
520                 telegps_status = new TeleGPSStatus();
521                 c.gridx = 0;
522                 c.gridy = 1;
523                 c.fill = GridBagConstraints.HORIZONTAL;
524                 c.weightx = 1;
525                 c.gridwidth = 2;
526                 bag.add(telegps_status, c);
527                 c.gridwidth = 1;
528                 displays.add(telegps_status);
529
530
531                 /* The rest of the window uses a tabbed pane to
532                  * show one of the alternate data views
533                  */
534                 pane = new JTabbedPane();
535
536                 /* Make the tabbed pane use the rest of the window space */
537                 c.gridx = 0;
538                 c.gridy = 2;
539                 c.fill = GridBagConstraints.BOTH;
540                 c.weightx = 1;
541                 c.weighty = 1;
542                 c.gridwidth = 2;
543                 bag.add(pane, c);
544
545                 map = new AltosUIMapNew();
546                 pane.add(map.getName(), map);
547                 displays.add(map);
548
549                 gps_info = new TeleGPSInfo();
550                 pane.add(gps_info.getName(), gps_info);
551                 displays.add(gps_info);
552
553                 gps_state = new TeleGPSState();
554                 pane.add(gps_state.getName(), gps_state);
555                 displays.add(gps_state);
556
557                 info_table = new AltosInfoTable();
558                 pane.add("Table", info_table);
559                 displays.add(info_table);
560
561                 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
562
563                 AltosUIPreferences.register_font_listener(this);
564                 AltosPreferences.register_units_listener(this);
565
566                 addWindowListener(new WindowAdapter() {
567                                 @Override
568                                 public void windowClosing(WindowEvent e) {
569                                         close();
570                                 }
571                         });
572
573                 pack();
574                 setVisible(true);
575
576                 add_window();
577         }
578
579         public TeleGPS(AltosFlightReader reader) {
580                 this();
581                 set_reader(reader, null);
582         }
583
584         public TeleGPS(AltosDevice device) {
585                 this();
586                 connect(device);
587         }
588
589         static AltosStateIterable record_iterable(File file) {
590                 FileInputStream in;
591                 try {
592                         in = new FileInputStream(file);
593                 } catch (Exception e) {
594                         System.out.printf("Failed to open file '%s'\n", file);
595                         return null;
596                 }
597                 if (file.getName().endsWith("telem"))
598                         return new AltosTelemetryFile(in);
599                 else
600                         return new AltosEepromFile(in);
601         }
602
603         static AltosReplayReader replay_file(File file) {
604                 AltosStateIterable states = record_iterable(file);
605                 if (states == null)
606                         return null;
607                 return new AltosReplayReader(states.iterator(), file);
608         }
609
610         static boolean process_graph(File file) {
611                 AltosStateIterable states = record_iterable(file);
612                 if (states == null)
613                         return false;
614                 try {
615                         new TeleGPSGraphUI(states, file);
616                 } catch (Exception e) {
617                         return false;
618                 }
619                 return true;
620         }
621
622         static boolean process_replay(File file) {
623                 AltosReplayReader new_reader = replay_file(file);
624                 if (new_reader == null)
625                         return false;
626
627                 new TeleGPS(new_reader);
628                 return true;
629         }
630
631         static final int process_none = 0;
632         static final int process_csv = 1;
633         static final int process_kml = 2;
634         static final int process_graph = 3;
635         static final int process_replay = 4;
636         static final int process_summary = 5;
637         static final int process_cat = 6;
638
639         public static boolean load_library(Frame frame) {
640                 if (!AltosUILib.load_library()) {
641                         JOptionPane.showMessageDialog(frame,
642                                                       String.format("No AltOS library in \"%s\"",
643                                                                     System.getProperty("java.library.path","<undefined>")),
644                                                       "Cannot load device access library",
645                                                       JOptionPane.ERROR_MESSAGE);
646                         return false;
647                 }
648                 return true;
649         }
650
651         public static void help(int code) {
652                 System.out.printf("Usage: altosui [OPTION]... [FILE]...\n");
653                 System.out.printf("  Options:\n");
654                 System.out.printf("    --fetchmaps <lat> <lon>\tpre-fetch maps for site map view\n");
655                 System.out.printf("    --replay <filename>\t\trelive the glory of past flights \n");
656                 System.out.printf("    --graph <filename>\t\tgraph a flight\n");
657                 System.out.printf("    --csv\tgenerate comma separated output for spreadsheets, etc\n");
658                 System.out.printf("    --kml\tgenerate KML output for use with Google Earth\n");
659                 System.exit(code);
660         }
661
662         public static void main(String[] args) {
663                 int     errors = 0;
664
665                 load_library(null);
666                 try {
667                         UIManager.setLookAndFeel(AltosUIPreferences.look_and_feel());
668                 } catch (Exception e) {
669                 }
670
671                 boolean any_created = false;
672
673
674                 /* Handle batch-mode */
675                 int process = process_none;
676                 for (int i = 0; i < args.length; i++) {
677                         if (args[i].equals("--help"))
678                                 help(0);
679                         else if (args[i].equals("--fetchmaps")) {
680                                 if (args.length < i + 3) {
681                                         help(1);
682                                 } else {
683                                         try {
684                                         double lat = AltosParse.parse_double_locale(args[i+1]);
685                                         double lon = AltosParse.parse_double_locale(args[i+2]);
686                                         AltosUIMap.prefetch_maps(lat, lon);
687                                         } catch (ParseException e) {
688                                                 System.out.printf("Can't parse number %s\n", e.toString());
689                                         }
690                                         i += 2;
691                                 }
692                         } else if (args[i].equals("--replay"))
693                                 process = process_replay;
694                         else if (args[i].equals("--kml"))
695                                 process = process_kml;
696                         else if (args[i].equals("--csv"))
697                                 process = process_csv;
698                         else if (args[i].equals("--graph"))
699                                 process = process_graph;
700                         else if (args[i].equals("--summary"))
701                                 process = process_summary;
702                         else if (args[i].equals("--cat"))
703                                 process = process_cat;
704                         else if (args[i].startsWith("--"))
705                                 help(1);
706                         else {
707                                 File file = new File(args[i]);
708                                 switch (process) {
709                                 case process_none:
710                                 case process_graph:
711                                         if (!process_graph(file))
712                                                 ++errors;
713                                         break;
714                                 case process_replay:
715                                         if (!process_replay(file))
716                                                 ++errors;
717                                         any_created = true;
718                                         break;
719                                 case process_kml:
720                                         ++errors;
721                                         break;
722                                 case process_csv:
723                                         ++errors;
724                                         break;
725                                 case process_summary:
726                                         ++errors;
727                                         break;
728                                 case process_cat:
729                                         ++errors;
730                                 }
731                         }
732                 }
733                 if (errors != 0)
734                         System.exit(errors);
735                 if (number_of_windows == 0) {
736                         java.util.List<AltosDevice> devices = AltosUSBDevice.list(AltosLib.product_basestation);
737                         if (devices != null)
738                                 for (AltosDevice device : devices) {
739                                         new TeleGPS(device);
740                                         any_created = true;
741                                 }
742                         if (number_of_windows == 0)
743                                 new TeleGPS();
744                 }
745         }
746 }