telegps: Add 'Info' tab
authorKeith Packard <keithp@keithp.com>
Thu, 29 May 2014 04:56:52 +0000 (21:56 -0700)
committerKeith Packard <keithp@keithp.com>
Thu, 29 May 2014 05:02:32 +0000 (22:02 -0700)
This contains a summary of the tracking info, including position,
speed and course.

Signed-off-by: Keith Packard <keithp@keithp.com>
25 files changed:
altoslib/AltosConvert.java
altoslib/AltosGreatCircle.java
altoslib/AltosState.java
altosui/AltosCSVUI.java [deleted file]
altosui/AltosDataChooser.java [deleted file]
altosui/AltosLed.java [deleted file]
altosui/AltosLights.java [deleted file]
altosui/Makefile.am
altosuilib/AltosCSVUI.java [new file with mode: 0644]
altosuilib/AltosDataChooser.java [new file with mode: 0644]
altosuilib/AltosLed.java [new file with mode: 0644]
altosuilib/AltosLights.java [new file with mode: 0644]
altosuilib/Makefile.am
icon/telegps-128.png [new file with mode: 0644]
icon/telegps-16.png [new file with mode: 0644]
icon/telegps-256.png [new file with mode: 0644]
icon/telegps-32.png [new file with mode: 0644]
icon/telegps-48.png [new file with mode: 0644]
icon/telegps-512.png [new file with mode: 0644]
icon/telegps-64.png [new file with mode: 0644]
icon/telegps.ico [new file with mode: 0644]
icon/telegps.svg [new file with mode: 0644]
telegps/Makefile.am
telegps/TeleGPS.java
telegps/TeleGPSInfo.java [new file with mode: 0644]

index 484f621..a65669d 100644 (file)
@@ -371,4 +371,30 @@ public class AltosConvert {
                        return 94;
                return (int) Math.floor (1.0/2.0 * (24.0e6/32.0) / freq + 0.5);
        }
+
+       public static final int BEARING_LONG = 0;
+       public static final int BEARING_SHORT = 1;
+       public static final int BEARING_VOICE = 2;
+
+       public static String bearing_to_words(int length, double bearing) {
+               String [][] bearing_string = {
+                       {
+                               "North", "North North East", "North East", "East North East",
+                               "East", "East South East", "South East", "South South East",
+                               "South", "South South West", "South West", "West South West",
+                               "West", "West North West", "North West", "North North West"
+                       }, {
+                               "N", "NNE", "NE", "ENE",
+                               "E", "ESE", "SE", "SSE",
+                               "S", "SSW", "SW", "WSW",
+                               "W", "WNW", "NW", "NNW"
+                       }, {
+                               "north", "nor nor east", "north east", "east nor east",
+                               "east", "east sow east", "south east", "sow sow east",
+                               "south", "sow sow west", "south west", "west sow west",
+                               "west", "west nor west", "north west", "nor nor west "
+                       }
+               };
+               return bearing_string[length][(int)((bearing / 90 * 8 + 1) / 2)%16];
+       }
 }
index 39df4fc..4782c34 100644 (file)
@@ -30,30 +30,12 @@ public class AltosGreatCircle implements Cloneable {
        static final double rad = Math.PI / 180;
        static final double earth_radius = 6371.2 * 1000;       /* in meters */
 
-       public static final int BEARING_LONG = 0;
-       public static final int BEARING_SHORT = 1;
-       public static final int BEARING_VOICE = 2;
+       public static final int BEARING_LONG = AltosConvert.BEARING_LONG;
+       public static final int BEARING_SHORT = AltosConvert.BEARING_SHORT;
+       public static final int BEARING_VOICE = AltosConvert.BEARING_VOICE;
 
        public String bearing_words(int length) {
-               String [][] bearing_string = {
-                       {
-                               "North", "North North East", "North East", "East North East",
-                               "East", "East South East", "South East", "South South East",
-                               "South", "South South West", "South West", "West South West",
-                               "West", "West North West", "North West", "North North West"
-                       }, {
-                               "N", "NNE", "NE", "ENE",
-                               "E", "ESE", "SE", "SSE",
-                               "S", "SSW", "SW", "WSW",
-                               "W", "WNW", "NW", "NNW"
-                       }, {
-                               "north", "nor nor east", "north east", "east nor east",
-                               "east", "east sow east", "south east", "sow sow east",
-                               "south", "sow sow west", "south west", "west sow west",
-                               "west", "west nor west", "north west", "nor nor west "
-                       }
-               };
-               return bearing_string[length][(int)((bearing / 90 * 8 + 1) / 2)%16];
+               return AltosConvert.bearing_to_words(length, bearing);
        }
 
        public AltosGreatCircle (double start_lat, double start_lon, double start_alt,
index 9e8e22a..1162e52 100644 (file)
@@ -389,6 +389,10 @@ public class AltosState implements Cloneable {
 
        private AltosGpsAltitude        gps_altitude;
 
+       private AltosValue              gps_ground_speed;
+       private AltosValue              gps_ascent_rate;
+       private AltosValue              gps_course;
+
        public double altitude() {
                double a = altitude.value();
                if (a != AltosLib.MISSING)
@@ -419,6 +423,18 @@ public class AltosState implements Cloneable {
                gps_altitude.set(new_gps_altitude, time);
        }
 
+       public double gps_ground_speed() {
+               return gps_ground_speed.value();
+       }
+
+       public double gps_ascent_rate() {
+               return gps_ascent_rate.value();
+       }
+
+       public double gps_course() {
+               return gps_course.value();
+       }
+
        class AltosPressure extends AltosValue {
                void set(double p, double time) {
                        super.set(p, time);
@@ -695,6 +711,8 @@ public class AltosState implements Cloneable {
 
                gps_altitude = new AltosGpsAltitude();
                gps_ground_altitude = new AltosGpsGroundAltitude();
+               gps_ground_speed = new AltosValue();
+               gps_ascent_rate = new AltosValue();
 
                speak_tick = AltosLib.MISSING;
                speak_altitude = AltosLib.MISSING;
@@ -877,6 +895,12 @@ public class AltosState implements Cloneable {
                                gps_ground_altitude.set(gps.alt, time);
                        }
                        gps_altitude.set(gps.alt, time);
+                       if (gps.climb_rate != AltosLib.MISSING)
+                               gps_ascent_rate.set(gps.climb_rate, time);
+                       if (gps.ground_speed != AltosLib.MISSING)
+                               gps_ground_speed.set(gps.ground_speed, time);
+                       if (gps.course != AltosLib.MISSING)
+                               gps_course.set(gps.course, time);
                }
                if (gps.lat != 0 && gps.lon != 0 &&
                    pad_lat != AltosLib.MISSING &&
diff --git a/altosui/AltosCSVUI.java b/altosui/AltosCSVUI.java
deleted file mode 100644 (file)
index a0fceee..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import java.io.*;
-import org.altusmetrum.altoslib_4.*;
-import org.altusmetrum.altosuilib_2.*;
-
-public class AltosCSVUI
-       extends AltosUIDialog
-       implements ActionListener
-{
-       JFileChooser            csv_chooser;
-       JPanel                  accessory;
-       JComboBox<String>       combo_box;
-       Iterable<AltosState>    states;
-       AltosWriter             writer;
-
-       static String[]         combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" };
-
-       void set_default_file() {
-               File    current = csv_chooser.getSelectedFile();
-               String  current_name = current.getName();
-               String  new_name = null;
-               String  selected = (String) combo_box.getSelectedItem();
-
-               if (selected.contains("CSV"))
-                       new_name = Altos.replace_extension(current_name, ".csv");
-               else if (selected.contains("KML"))
-                       new_name = Altos.replace_extension(current_name, ".kml");
-               if (new_name != null)
-                       csv_chooser.setSelectedFile(new File(new_name));
-       }
-
-       public void actionPerformed(ActionEvent e) {
-               if (e.getActionCommand().equals("comboBoxChanged"))
-                       set_default_file();
-       }
-
-       public AltosCSVUI(JFrame frame, AltosStateIterable states, File source_file) {
-               this.states = states;
-               csv_chooser = new JFileChooser(source_file);
-
-               accessory = new JPanel();
-               accessory.setLayout(new GridBagLayout());
-
-               GridBagConstraints      c = new GridBagConstraints();
-               c.fill = GridBagConstraints.NONE;
-               c.weightx = 1;
-               c.weighty = 0;
-               c.insets = new Insets (4, 4, 4, 4);
-
-               JLabel accessory_label = new JLabel("Export File Type");
-               c.gridx = 0;
-               c.gridy = 0;
-               accessory.add(accessory_label, c);
-
-               combo_box = new JComboBox<String>(combo_box_items);
-               combo_box.addActionListener(this);
-               c.gridx = 0;
-               c.gridy = 1;
-               accessory.add(combo_box, c);
-
-               csv_chooser.setAccessory(accessory);
-               csv_chooser.setSelectedFile(source_file);
-               set_default_file();
-               int ret = csv_chooser.showSaveDialog(frame);
-               if (ret == JFileChooser.APPROVE_OPTION) {
-                       File file = csv_chooser.getSelectedFile();
-                       String type = (String) combo_box.getSelectedItem();
-                       try {
-                               if (type.contains("CSV"))
-                                       writer = new AltosCSV(file);
-                               else
-                                       writer = new AltosKML(file);
-                               writer.write(states);
-                               writer.close();
-                       } catch (FileNotFoundException ee) {
-                               JOptionPane.showMessageDialog(frame,
-                                                             ee.getMessage(),
-                                                             "Cannot open file",
-                                                             JOptionPane.ERROR_MESSAGE);
-                       }
-               }
-       }
-}
diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java
deleted file mode 100644 (file)
index 43726a4..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import javax.swing.*;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import java.io.*;
-import org.altusmetrum.altoslib_4.*;
-import org.altusmetrum.altosuilib_2.*;
-
-public class AltosDataChooser extends JFileChooser {
-       JFrame  frame;
-       String  filename;
-       File    file;
-
-       public String filename() {
-               return filename;
-       }
-
-       public File file() {
-               return file;
-       }
-
-       public AltosStateIterable runDialog() {
-               int     ret;
-
-               ret = showOpenDialog(frame);
-               if (ret == APPROVE_OPTION) {
-                       file = getSelectedFile();
-                       if (file == null)
-                               return null;
-                       filename = file.getName();
-                       try {
-                               if (filename.endsWith("eeprom")) {
-                                       FileInputStream in = new FileInputStream(file);
-                                       return new AltosEepromFile(in);
-                               } else if (filename.endsWith("telem")) {
-                                       FileInputStream in = new FileInputStream(file);
-                                       return new AltosTelemetryFile(in);
-                               } else {
-                                       throw new FileNotFoundException();
-                               }
-                       } catch (FileNotFoundException fe) {
-                               JOptionPane.showMessageDialog(frame,
-                                                             fe.getMessage(),
-                                                             "Cannot open file",
-                                                             JOptionPane.ERROR_MESSAGE);
-                       }
-               }
-               return null;
-       }
-
-       public AltosDataChooser(JFrame in_frame) {
-               frame = in_frame;
-               setDialogTitle("Select Flight Record File");
-               setFileFilter(new FileNameExtensionFilter("TeleMetrum eeprom file",
-                                                         "eeprom"));
-               setFileFilter(new FileNameExtensionFilter("Telemetry file",
-                                                         "telem"));
-               setFileFilter(new FileNameExtensionFilter("TeleMega eeprom file",
-                                                         "mega"));
-               setFileFilter(new FileNameExtensionFilter("EasyMini eeprom file",
-                                                         "mini"));
-               setFileFilter(new FileNameExtensionFilter("Flight data file",
-                                                         "telem", "eeprom", "mega", "mini"));
-               setCurrentDirectory(AltosUIPreferences.logdir());
-       }
-}
diff --git a/altosui/AltosLed.java b/altosui/AltosLed.java
deleted file mode 100644 (file)
index 93064f1..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import javax.swing.*;
-
-public class AltosLed extends JLabel {
-       ImageIcon       on, off;
-
-       ImageIcon create_icon(String path) {
-               java.net.URL imgURL = AltosUI.class.getResource(path);
-               if (imgURL != null)
-                       return new ImageIcon(imgURL);
-               System.err.printf("Cannot find icon \"%s\"\n", path);
-               return null;
-       }
-
-       public void set(boolean set) {
-               if (set)
-                       setIcon(on);
-               else
-                       setIcon(off);
-       }
-
-       public AltosLed(String on_path, String off_path) {
-               on = create_icon(on_path);
-               off = create_icon(off_path);
-               setIcon(off);
-       }
-}
diff --git a/altosui/AltosLights.java b/altosui/AltosLights.java
deleted file mode 100644 (file)
index 7ad22f3..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright © 2010 Keith Packard <keithp@keithp.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
- */
-
-package altosui;
-
-import java.awt.*;
-import javax.swing.*;
-
-public class AltosLights extends JComponent {
-
-       GridBagLayout   gridbag;
-
-       AltosLed        red, green;
-
-       ImageIcon create_icon(String path, String description) {
-               java.net.URL imgURL = AltosUI.class.getResource(path);
-               if (imgURL != null)
-                       return new ImageIcon(imgURL, description);
-               System.err.printf("Cannot find icon \"%s\"\n", path);
-               return null;
-       }
-
-       public void set (boolean on) {
-               if (on) {
-                       red.set(false);
-                       green.set(true);
-               } else {
-                       red.set(true);
-                       green.set(false);
-               }
-       }
-
-       public AltosLights() {
-               GridBagConstraints c;
-               gridbag = new GridBagLayout();
-               setLayout(gridbag);
-
-               c = new GridBagConstraints();
-               red = new AltosLed("/redled.png", "/grayled.png");
-               c.gridx = 0; c.gridy = 0;
-               c.insets = new Insets (0, 5, 0, 5);
-               gridbag.setConstraints(red, c);
-               add(red);
-               red.set(true);
-               green = new AltosLed("/greenled.png", "/grayled.png");
-               c.gridx = 1; c.gridy = 0;
-               gridbag.setConstraints(green, c);
-               add(green);
-               green.set(false);
-       }
-}
index c834646..9eff161 100644 (file)
@@ -20,7 +20,6 @@ altosui_JAVA = \
        AltosConfigureUI.java \
        AltosConfigTD.java \
        AltosConfigTDUI.java \
-       AltosCSVUI.java \
        AltosDescent.java \
        AltosFlashUI.java \
        AltosFlightInfoTableModel.java \
@@ -36,8 +35,6 @@ altosui_JAVA = \
        AltosLaunchUI.java \
        AltosInfoTable.java \
        AltosLanded.java \
-       AltosLed.java \
-       AltosLights.java \
        AltosPad.java \
        AltosUIPreferencesBackend.java \
        AltosRomconfigUI.java \
@@ -45,8 +42,7 @@ altosui_JAVA = \
        AltosGraph.java \
        AltosGraphDataPoint.java \
        AltosGraphDataSet.java \
-       AltosGraphUI.java \
-       AltosDataChooser.java
+       AltosGraphUI.java
 
 JFREECHART_CLASS= \
     jfreechart.jar
@@ -94,20 +90,13 @@ JAVA_ICONS=\
        $(ICONDIR)/altus-metrum-128.png \
        $(ICONDIR)/altus-metrum-256.png
 
-ICONS= $(ICONDIR)/redled.png $(ICONDIR)/redoff.png \
-       $(ICONDIR)/greenled.png $(ICONDIR)/greenoff.png \
-       $(ICONDIR)/grayled.png $(ICONDIR)/grayoff.png
-
 # icon base names for jar
 ICONJAR= -C $(ICONDIR) altus-metrum-16.png \
        -C $(ICONDIR) altus-metrum-32.png \
        -C $(ICONDIR) altus-metrum-48.png \
        -C $(ICONDIR) altus-metrum-64.png \
        -C $(ICONDIR) altus-metrum-128.png \
-       -C $(ICONDIR) altus-metrum-256.png \
-       -C $(ICONDIR) redled.png -C $(ICONDIR) redoff.png \
-       -C $(ICONDIR) greenled.png -C $(ICONDIR) greenoff.png \
-       -C $(ICONDIR) grayon.png -C $(ICONDIR) grayled.png
+       -C $(ICONDIR) altus-metrum-256.png
 
 WINDOWS_ICON=$(ICONDIR)/altus-metrum.ico
 
diff --git a/altosuilib/AltosCSVUI.java b/altosuilib/AltosCSVUI.java
new file mode 100644 (file)
index 0000000..0a5e4fa
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.altosuilib_2;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosCSVUI
+       extends AltosUIDialog
+       implements ActionListener
+{
+       JFileChooser            csv_chooser;
+       JPanel                  accessory;
+       JComboBox<String>       combo_box;
+       Iterable<AltosState>    states;
+       AltosWriter             writer;
+
+       static String[]         combo_box_items = { "Comma Separated Values (.CSV)", "Googleearth Data (.KML)" };
+
+       void set_default_file() {
+               File    current = csv_chooser.getSelectedFile();
+               String  current_name = current.getName();
+               String  new_name = null;
+               String  selected = (String) combo_box.getSelectedItem();
+
+               if (selected.contains("CSV"))
+                       new_name = AltosLib.replace_extension(current_name, ".csv");
+               else if (selected.contains("KML"))
+                       new_name = AltosLib.replace_extension(current_name, ".kml");
+               if (new_name != null)
+                       csv_chooser.setSelectedFile(new File(new_name));
+       }
+
+       public void actionPerformed(ActionEvent e) {
+               if (e.getActionCommand().equals("comboBoxChanged"))
+                       set_default_file();
+       }
+
+       public AltosCSVUI(JFrame frame, AltosStateIterable states, File source_file) {
+               this.states = states;
+               csv_chooser = new JFileChooser(source_file);
+
+               accessory = new JPanel();
+               accessory.setLayout(new GridBagLayout());
+
+               GridBagConstraints      c = new GridBagConstraints();
+               c.fill = GridBagConstraints.NONE;
+               c.weightx = 1;
+               c.weighty = 0;
+               c.insets = new Insets (4, 4, 4, 4);
+
+               JLabel accessory_label = new JLabel("Export File Type");
+               c.gridx = 0;
+               c.gridy = 0;
+               accessory.add(accessory_label, c);
+
+               combo_box = new JComboBox<String>(combo_box_items);
+               combo_box.addActionListener(this);
+               c.gridx = 0;
+               c.gridy = 1;
+               accessory.add(combo_box, c);
+
+               csv_chooser.setAccessory(accessory);
+               csv_chooser.setSelectedFile(source_file);
+               set_default_file();
+               int ret = csv_chooser.showSaveDialog(frame);
+               if (ret == JFileChooser.APPROVE_OPTION) {
+                       File file = csv_chooser.getSelectedFile();
+                       String type = (String) combo_box.getSelectedItem();
+                       try {
+                               if (type.contains("CSV"))
+                                       writer = new AltosCSV(file);
+                               else
+                                       writer = new AltosKML(file);
+                               writer.write(states);
+                               writer.close();
+                       } catch (FileNotFoundException ee) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             ee.getMessage(),
+                                                             "Cannot open file",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+       }
+}
diff --git a/altosuilib/AltosDataChooser.java b/altosuilib/AltosDataChooser.java
new file mode 100644 (file)
index 0000000..14d2811
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.altosuilib_2;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.io.*;
+import org.altusmetrum.altoslib_4.*;
+
+public class AltosDataChooser extends JFileChooser {
+       JFrame  frame;
+       String  filename;
+       File    file;
+
+       public String filename() {
+               return filename;
+       }
+
+       public File file() {
+               return file;
+       }
+
+       public AltosStateIterable runDialog() {
+               int     ret;
+
+               ret = showOpenDialog(frame);
+               if (ret == APPROVE_OPTION) {
+                       file = getSelectedFile();
+                       if (file == null)
+                               return null;
+                       filename = file.getName();
+                       try {
+                               if (filename.endsWith("eeprom")) {
+                                       FileInputStream in = new FileInputStream(file);
+                                       return new AltosEepromFile(in);
+                               } else if (filename.endsWith("telem")) {
+                                       FileInputStream in = new FileInputStream(file);
+                                       return new AltosTelemetryFile(in);
+                               } else {
+                                       throw new FileNotFoundException();
+                               }
+                       } catch (FileNotFoundException fe) {
+                               JOptionPane.showMessageDialog(frame,
+                                                             fe.getMessage(),
+                                                             "Cannot open file",
+                                                             JOptionPane.ERROR_MESSAGE);
+                       }
+               }
+               return null;
+       }
+
+       public AltosDataChooser(JFrame in_frame) {
+               frame = in_frame;
+               setDialogTitle("Select Flight Record File");
+               setFileFilter(new FileNameExtensionFilter("TeleMetrum eeprom file",
+                                                         "eeprom"));
+               setFileFilter(new FileNameExtensionFilter("Telemetry file",
+                                                         "telem"));
+               setFileFilter(new FileNameExtensionFilter("TeleMega eeprom file",
+                                                         "mega"));
+               setFileFilter(new FileNameExtensionFilter("EasyMini eeprom file",
+                                                         "mini"));
+               setFileFilter(new FileNameExtensionFilter("Flight data file",
+                                                         "telem", "eeprom", "mega", "mini"));
+               setCurrentDirectory(AltosUIPreferences.logdir());
+       }
+}
diff --git a/altosuilib/AltosLed.java b/altosuilib/AltosLed.java
new file mode 100644 (file)
index 0000000..2debb62
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.altosuilib_2;
+
+import javax.swing.*;
+
+public class AltosLed extends JLabel {
+       ImageIcon       on, off;
+
+       ImageIcon create_icon(String path) {
+               java.net.URL imgURL = AltosUILib.class.getResource(path);
+               if (imgURL != null)
+                       return new ImageIcon(imgURL);
+               System.err.printf("Cannot find icon \"%s\"\n", path);
+               return null;
+       }
+
+       public void set(boolean set) {
+               if (set)
+                       setIcon(on);
+               else
+                       setIcon(off);
+       }
+
+       public AltosLed(String on_path, String off_path) {
+               on = create_icon(on_path);
+               off = create_icon(off_path);
+               setIcon(off);
+       }
+}
diff --git a/altosuilib/AltosLights.java b/altosuilib/AltosLights.java
new file mode 100644 (file)
index 0000000..c91b70e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.altosuilib_2;
+
+import java.awt.*;
+import javax.swing.*;
+
+public class AltosLights extends JComponent {
+
+       GridBagLayout   gridbag;
+
+       AltosLed        red, green;
+
+       ImageIcon create_icon(String path, String description) {
+               java.net.URL imgURL = AltosUILib.class.getResource(path);
+               if (imgURL != null)
+                       return new ImageIcon(imgURL, description);
+               System.err.printf("Cannot find icon \"%s\"\n", path);
+               return null;
+       }
+
+       public void set (boolean on) {
+               if (on) {
+                       red.set(false);
+                       green.set(true);
+               } else {
+                       red.set(true);
+                       green.set(false);
+               }
+       }
+
+       public AltosLights() {
+               GridBagConstraints c;
+               gridbag = new GridBagLayout();
+               setLayout(gridbag);
+
+               c = new GridBagConstraints();
+               red = new AltosLed("/redled.png", "/grayled.png");
+               c.gridx = 0; c.gridy = 0;
+               c.insets = new Insets (0, 5, 0, 5);
+               gridbag.setConstraints(red, c);
+               add(red);
+               red.set(true);
+               green = new AltosLed("/greenled.png", "/grayled.png");
+               c.gridx = 1; c.gridy = 0;
+               gridbag.setConstraints(green, c);
+               add(green);
+               green.set(false);
+       }
+}
index 4dc4c47..f554fd7 100644 (file)
@@ -50,6 +50,10 @@ altosuilib_JAVA = \
        AltosEepromManage.java \
        AltosEepromMonitorUI.java \
        AltosEepromSelect.java \
+       AltosCSVUI.java \
+       AltosDataChooser.java \
+       AltosLights.java \
+       AltosLed.java \
        AltosBTDevice.java \
        AltosBTDeviceIterator.java \
        AltosBTManage.java \
@@ -58,6 +62,18 @@ altosuilib_JAVA = \
 
 JAR=altosuilib_$(ALTOSUILIB_VERSION).jar
 
+# Icons
+ICONDIR=$(top_srcdir)/icon
+
+ICONS= $(ICONDIR)/redled.png $(ICONDIR)/redoff.png \
+       $(ICONDIR)/greenled.png $(ICONDIR)/greenoff.png \
+       $(ICONDIR)/grayon.png $(ICONDIR)/grayled.png
+
+# icon base names for jar
+ICONJAR= -C $(ICONDIR) redled.png -C $(ICONDIR) redoff.png \
+       -C $(ICONDIR) greenled.png -C $(ICONDIR) greenoff.png \
+       -C $(ICONDIR) grayon.png -C $(ICONDIR) grayled.png
+
 all-local: $(JAR)
 
 clean-local:
@@ -72,5 +88,5 @@ install-altosuilibJAVA: $(JAR)
 $(JAVAROOT):
        mkdir -p $(JAVAROOT)
 
-$(JAR): classaltosuilib.stamp
-       jar cf $@ -C $(JAVAROOT) .
+$(JAR): classaltosuilib.stamp $(ICONS)
+       jar cf $@ $(ICONJAR) -C $(JAVAROOT) .
diff --git a/icon/telegps-128.png b/icon/telegps-128.png
new file mode 100644 (file)
index 0000000..f1343d9
Binary files /dev/null and b/icon/telegps-128.png differ
diff --git a/icon/telegps-16.png b/icon/telegps-16.png
new file mode 100644 (file)
index 0000000..5bd4599
Binary files /dev/null and b/icon/telegps-16.png differ
diff --git a/icon/telegps-256.png b/icon/telegps-256.png
new file mode 100644 (file)
index 0000000..46e1670
Binary files /dev/null and b/icon/telegps-256.png differ
diff --git a/icon/telegps-32.png b/icon/telegps-32.png
new file mode 100644 (file)
index 0000000..c858889
Binary files /dev/null and b/icon/telegps-32.png differ
diff --git a/icon/telegps-48.png b/icon/telegps-48.png
new file mode 100644 (file)
index 0000000..3bee98e
Binary files /dev/null and b/icon/telegps-48.png differ
diff --git a/icon/telegps-512.png b/icon/telegps-512.png
new file mode 100644 (file)
index 0000000..47c4700
Binary files /dev/null and b/icon/telegps-512.png differ
diff --git a/icon/telegps-64.png b/icon/telegps-64.png
new file mode 100644 (file)
index 0000000..0ee086a
Binary files /dev/null and b/icon/telegps-64.png differ
diff --git a/icon/telegps.ico b/icon/telegps.ico
new file mode 100644 (file)
index 0000000..bedf04e
Binary files /dev/null and b/icon/telegps.ico differ
diff --git a/icon/telegps.svg b/icon/telegps.svg
new file mode 100644 (file)
index 0000000..256b8c5
--- /dev/null
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   id="svg2"
+   width="191.28"
+   height="245.28"
+   version="1.0"
+   sodipodi:version="0.32"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="altusmetrum-only.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/keithp/src/cc1111/altus-logo/bottom.png"
+   inkscape:export-xdpi="119.89881"
+   inkscape:export-ydpi="119.89881">
+  <metadata
+     id="metadata14">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs12">
+    <linearGradient
+       id="linearGradient3165">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3167" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3169" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3177">
+      <stop
+         style="stop-color:#da7000;stop-opacity:1;"
+         offset="0"
+         id="stop3179" />
+      <stop
+         id="stop3447"
+         offset="0.24528302"
+         style="stop-color:#a63852;stop-opacity:1;" />
+      <stop
+         style="stop-color:#7200a4;stop-opacity:1;"
+         offset="1"
+         id="stop3181" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3169">
+      <stop
+         style="stop-color:#ff8a00;stop-opacity:1;"
+         offset="0"
+         id="stop3171" />
+      <stop
+         id="stop3445"
+         offset="0.71698111"
+         style="stop-color:#c24573;stop-opacity:0.98039216;" />
+      <stop
+         style="stop-color:#8500e7;stop-opacity:0.96078432;"
+         offset="1"
+         id="stop3173" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 121 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="191 : 121 : 1"
+       inkscape:persp3d-origin="95.5 : 80.666667 : 1"
+       id="perspective16" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3169"
+       id="radialGradient3175"
+       cx="951.68713"
+       cy="2305.2668"
+       fx="951.68713"
+       fy="2305.2668"
+       r="951.68701"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3165"
+       id="radialGradient3171"
+       cx="951.68713"
+       cy="1205.2668"
+       fx="951.68713"
+       fy="1205.2668"
+       r="951.68701"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3169"
+       id="radialGradient3020"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="2305.2668"
+       fx="951.68713"
+       fy="2305.2668"
+       r="951.68701" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3165"
+       id="radialGradient3022"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="1205.2668"
+       fx="951.68713"
+       fy="1205.2668"
+       r="951.68701" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3169"
+       id="radialGradient3024"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="2305.2668"
+       fx="951.68713"
+       fy="2305.2668"
+       r="951.68701" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3165"
+       id="radialGradient3026"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="1205.2668"
+       fx="951.68713"
+       fy="1205.2668"
+       r="951.68701" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3169"
+       id="radialGradient3028"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="2305.2668"
+       fx="951.68713"
+       fy="2305.2668"
+       r="951.68701" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3165"
+       id="radialGradient3030"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,1.2664529,0,-321.14689)"
+       cx="951.68713"
+       cy="1205.2668"
+       fx="951.68713"
+       fy="1205.2668"
+       r="951.68701" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:cy="107.44765"
+     inkscape:cx="270.26251"
+     inkscape:zoom="0.86831672"
+     inkscape:window-height="709"
+     inkscape:window-width="1006"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     guidetolerance="10.0"
+     gridtolerance="10.0"
+     objecttolerance="10.0"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     showgrid="false"
+     inkscape:window-x="266"
+     inkscape:window-y="43"
+     inkscape:current-layer="svg2"
+     inkscape:window-maximized="0" />
+  <g
+     transform="matrix(0.1,0,0,0.1,1.1516425,2.6405446)"
+     id="g3"
+     style="fill:url(#radialGradient3175);fill-opacity:1;stroke:url(#radialGradient3171);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none">
+    <g
+       transform="translate(20.61545,-27.69425)"
+       style="fill:url(#radialGradient3028);fill-opacity:1;fill-rule:evenodd;stroke:url(#radialGradient3030);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none"
+       id="g5">
+      <path
+         d="m 931.07168,1164.597 248.86992,-331.80265 416.1687,1338.32935 286.6484,267.1042 -520.4224,0 -270.2797,-262.2181 0,-1033.0627 -160.98492,106.6818 -160.98492,-106.6818 0,1033.0627 -270.2797,262.2181 -520.4224,0 286.6484,-267.1042 416.1687,-1338.32935 248.86992,331.80265 z"
+         id="path7"
+         style="fill:url(#radialGradient3020);fill-opacity:1;stroke:url(#radialGradient3022);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 931.07168,27.69425 224.03682,720.46517 -63.341,76.00913 L 931.07168,486.3269 770.37586,824.16855 707.03486,748.15942 931.07168,27.69425 z"
+         id="path9"
+         style="fill:url(#radialGradient3024);fill-opacity:1;stroke:url(#radialGradient3026);stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
index 280b1e4..f8e2e63 100644 (file)
@@ -15,6 +15,7 @@ telegps_JAVA= \
        TeleGPS.java \
        TeleGPSStatus.java \
        TeleGPSStatusUpdate.java \
+       TeleGPSInfo.java \
        TeleGPSConfig.java \
        TeleGPSConfigUI.java \
        TeleGPSPreferences.java
index ad46fbd..1bb505e 100644 (file)
@@ -53,6 +53,7 @@ public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFo
        JTabbedPane     pane;
 
        AltosSiteMap    sitemap;
+       TeleGPSInfo     gps_info;
        boolean         has_map;
 
        JMenuBar        menu_bar;
@@ -115,10 +116,12 @@ public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFo
 
        public void reset() {
                sitemap.reset();
+               gps_info.reset();
        }
 
        public void set_font() {
                sitemap.set_font();
+               gps_info.set_font();
        }
 
        public void font_size_changed(int font_size) {
@@ -135,6 +138,7 @@ public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFo
                        state = new AltosState();
 
                sitemap.show(state, listener_state);
+               gps_info.show(state, listener_state);
                telegps_status.show(state, listener_state);
        }
 
@@ -225,6 +229,12 @@ public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFo
        }
 
        void export() {
+               AltosDataChooser chooser;
+               chooser = new AltosDataChooser(this);
+               AltosStateIterable states = chooser.runDialog();
+               if (states == null)
+                       return;
+               new AltosCSVUI(this, states, chooser.file());
        }
 
        void graph() {
@@ -394,6 +404,9 @@ public class TeleGPS extends AltosUIFrame implements AltosFlightDisplay, AltosFo
                sitemap = new AltosSiteMap();
                pane.add("Site Map", sitemap);
 
+               gps_info = new TeleGPSInfo();
+               pane.add("Info", gps_info);
+
                setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
 
                AltosUIPreferences.register_font_listener(this);
diff --git a/telegps/TeleGPSInfo.java b/telegps/TeleGPSInfo.java
new file mode 100644 (file)
index 0000000..0fba77d
--- /dev/null
@@ -0,0 +1,511 @@
+/*
+ * Copyright © 2010 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+package org.altusmetrum.telegps;
+
+import java.awt.*;
+import javax.swing.*;
+import org.altusmetrum.altoslib_4.*;
+import org.altusmetrum.altosuilib_2.*;
+
+public class TeleGPSInfo extends JComponent implements AltosFlightDisplay {
+       GridBagLayout   layout;
+       JLabel                  cur, max;
+
+       public class Info {
+               JLabel          label;
+               JTextField      value;
+               AltosLights     lights;
+
+               void show() {
+                       value.setVisible(true);
+                       lights.setVisible(true);
+                       label.setVisible(true);
+               }
+
+               void hide() {
+                       value.setVisible(false);
+                       lights.setVisible(false);
+                       label.setVisible(false);
+               }
+
+               void show(AltosState state, AltosListenerState listener_state) {}
+
+               void show(String s) {
+                       show();
+                       value.setText(s);
+               }
+
+               void show(AltosUnits units, double v) {
+                       show(units.show(8, v));
+               }
+
+               void show(String format, double v) {
+                       show(String.format(format, v));
+               }
+
+               void reset() {
+                       lights.set(false);
+                       value.setText("");
+               }
+
+               void set_font() {
+                       label.setFont(AltosUILib.label_font);
+                       value.setFont(AltosUILib.value_font);
+               }
+
+               public Info (GridBagLayout layout, int y, String text) {
+                       GridBagConstraints      c = new GridBagConstraints();
+                       c.weighty = 1;
+
+                       lights = new AltosLights();
+                       c.gridx = 0; c.gridy = y;
+                       c.anchor = GridBagConstraints.CENTER;
+                       c.fill = GridBagConstraints.VERTICAL;
+                       c.weightx = 0;
+                       layout.setConstraints(lights, c);
+                       add(lights);
+
+                       label = new JLabel(text);
+                       label.setFont(AltosUILib.label_font);
+                       label.setHorizontalAlignment(SwingConstants.LEFT);
+                       c.gridx = 1; c.gridy = y;
+                       c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.VERTICAL;
+                       c.weightx = 0;
+                       layout.setConstraints(label, c);
+                       add(label);
+
+                       value = new JTextField(AltosUILib.text_width);
+                       value.setFont(AltosUILib.value_font);
+                       value.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = 2; c.gridy = y;
+                       c.gridwidth = 2;
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.weightx = 1;
+                       layout.setConstraints(value, c);
+                       add(value);
+               }
+       }
+
+       public class Value {
+               JLabel          label;
+               JTextField      value;
+               void show(AltosState state, AltosListenerState listener_state) {}
+
+               void reset() {
+                       value.setText("");
+               }
+
+               void show() {
+                       label.setVisible(true);
+                       value.setVisible(true);
+               }
+
+               void show(String s) {
+                       show();
+                       value.setText(s);
+               }
+
+               void show(AltosUnits units, double v) {
+                       show(units.show(8, v));
+               }
+
+               void show(String format, double v) {
+                       show(String.format(format, v));
+               }
+
+               void hide() {
+                       label.setVisible(false);
+                       value.setVisible(false);
+               }
+               void set_font() {
+                       label.setFont(AltosUILib.label_font);
+                       value.setFont(AltosUILib.value_font);
+               }
+
+               public Value (GridBagLayout layout, int y, String text) {
+                       GridBagConstraints      c = new GridBagConstraints();
+                       c.weighty = 1;
+
+                       label = new JLabel(text);
+                       label.setFont(AltosUILib.label_font);
+                       label.setHorizontalAlignment(SwingConstants.LEFT);
+                       c.gridx = 1; c.gridy = y;
+                       c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.VERTICAL;
+                       c.weightx = 0;
+                       layout.setConstraints(label, c);
+                       add(label);
+
+                       value = new JTextField(AltosUILib.text_width);
+                       value.setFont(AltosUILib.value_font);
+                       value.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = 2; c.gridy = y;
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.gridwidth = 2;
+                       c.weightx = 1;
+                       layout.setConstraints(value, c);
+                       add(value);
+               }
+       }
+
+       public abstract class DualValue {
+               JLabel          label;
+               JTextField      value1;
+               JTextField      value2;
+
+               void reset() {
+                       value1.setText("");
+                       value2.setText("");
+               }
+
+               void show() {
+                       label.setVisible(true);
+                       value1.setVisible(true);
+                       value2.setVisible(true);
+               }
+
+               void hide() {
+                       label.setVisible(false);
+                       value1.setVisible(false);
+                       value2.setVisible(false);
+               }
+
+               void set_font() {
+                       label.setFont(AltosUILib.label_font);
+                       value1.setFont(AltosUILib.value_font);
+                       value2.setFont(AltosUILib.value_font);
+               }
+
+               abstract void show(AltosState state, AltosListenerState listener_state);
+
+               void show(String v1, String v2) {
+                       show();
+                       value1.setText(v1);
+                       value2.setText(v2);
+               }
+               void show(String f1, double v1, String f2, double v2) {
+                       show();
+                       value1.setText(String.format(f1, v1));
+                       value2.setText(String.format(f2, v2));
+               }
+
+               public DualValue (GridBagLayout layout, int x, int y, String text) {
+                       GridBagConstraints      c = new GridBagConstraints();
+                       c.weighty = 1;
+
+                       label = new JLabel(text);
+                       label.setFont(AltosUILib.label_font);
+                       label.setHorizontalAlignment(SwingConstants.LEFT);
+                       c.gridx = x + 1; c.gridy = y;
+                       c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.VERTICAL;
+                       c.weightx = 0;
+                       layout.setConstraints(label, c);
+                       add(label);
+
+                       value1 = new JTextField(AltosUILib.text_width);
+                       value1.setFont(AltosUILib.value_font);
+                       value1.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = x + 2; c.gridy = y;
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.weightx = 1;
+                       layout.setConstraints(value1, c);
+                       add(value1);
+
+                       value2 = new JTextField(AltosUILib.text_width);
+                       value2.setFont(AltosUILib.value_font);
+                       value2.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = x + 3; c.gridy = y;
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.weightx = 1;
+                       c.gridwidth = 1;
+                       layout.setConstraints(value2, c);
+                       add(value2);
+               }
+       }
+
+       public class ValueHold {
+               JLabel          label;
+               JTextField      value;
+               JTextField      max_value;
+               double          max;
+
+               void show(AltosState state, AltosListenerState listener_state) {}
+
+               void reset() {
+                       value.setText("");
+                       max_value.setText("");
+                       max = AltosLib.MISSING;
+               }
+
+               void set_font() {
+                       label.setFont(AltosUILib.label_font);
+                       value.setFont(AltosUILib.value_font);
+                       max_value.setFont(AltosUILib.value_font);
+               }
+
+               void show(AltosUnits units, double v) {
+                       if (v == AltosLib.MISSING) {
+                               value.setText("Missing");
+                               max_value.setText("Missing");
+                       } else {
+                               value.setText(units.show(8, v));
+                               if (v > max || max == AltosLib.MISSING) {
+                                       max_value.setText(units.show(8, v));
+                                       max = v;
+                               }
+                       }
+               }
+
+               void hide() {
+                       label.setVisible(false);
+                       value.setVisible(false);
+                       max_value.setVisible(false);
+               }
+
+               public ValueHold (GridBagLayout layout, int y, String text) {
+                       GridBagConstraints      c = new GridBagConstraints();
+                       c.weighty = 1;
+
+                       label = new JLabel(text);
+                       label.setFont(AltosUILib.label_font);
+                       label.setHorizontalAlignment(SwingConstants.LEFT);
+                       c.gridx = 1; c.gridy = y;
+                       c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+                       c.anchor = GridBagConstraints.WEST;
+                       c.fill = GridBagConstraints.VERTICAL;
+                       c.weightx = 0;
+                       layout.setConstraints(label, c);
+                       add(label);
+
+                       value = new JTextField(AltosUILib.text_width);
+                       value.setFont(AltosUILib.value_font);
+                       value.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = 2; c.gridy = y;
+                       c.anchor = GridBagConstraints.EAST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.weightx = 1;
+                       layout.setConstraints(value, c);
+                       add(value);
+
+                       max_value = new JTextField(AltosUILib.text_width);
+                       max_value.setFont(AltosUILib.value_font);
+                       max_value.setHorizontalAlignment(SwingConstants.RIGHT);
+                       c.gridx = 3; c.gridy = y;
+                       c.anchor = GridBagConstraints.EAST;
+                       c.fill = GridBagConstraints.BOTH;
+                       c.weightx = 1;
+                       layout.setConstraints(max_value, c);
+                       add(max_value);
+               }
+       }
+
+
+       class Altitude extends ValueHold {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       show(AltosConvert.height, state.altitude());
+               }
+               public Altitude (GridBagLayout layout, int y) {
+                       super (layout, y, "Altitude");
+               }
+       }
+
+       Altitude        altitude;
+
+       class AscentRate extends ValueHold {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       show(AltosConvert.speed, state.gps_ascent_rate());
+               }
+               public AscentRate (GridBagLayout layout, int y) {
+                       super (layout, y, "Ascent Rate");
+               }
+       }
+
+       AscentRate      ascent_rate;
+
+       class GroundSpeed extends ValueHold {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       show(AltosConvert.speed, state.gps_ground_speed());
+               }
+               public GroundSpeed (GridBagLayout layout, int y) {
+                       super (layout, y, "Ground Speed");
+               }
+       }
+
+       GroundSpeed     ground_speed;
+
+       String pos(double p, String pos, String neg) {
+               String  h = pos;
+               if (p < 0) {
+                       h = neg;
+                       p = -p;
+               }
+               int deg = (int) Math.floor(p);
+               double min = (p - Math.floor(p)) * 60.0;
+               return String.format("%s %4d° %9.6f", h, deg, min);
+       }
+
+       class Course extends DualValue {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       double  course = state.gps_course();
+                       if (course != AltosLib.MISSING)
+                               show( String.format("%3.0f°", course),
+                                     AltosConvert.bearing_to_words(
+                                             AltosConvert.BEARING_LONG,
+                                             course));
+               }
+               public Course (GridBagLayout layout, int y) {
+                       super (layout, 0, y, "Course");
+               }
+       }
+
+       Course          course;
+
+       class Lat extends Value {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       if (state.gps != null && state.gps.connected && state.gps.lat != AltosLib.MISSING)
+                               show(pos(state.gps.lat,"N", "S"));
+                       else
+                               show("???");
+               }
+               public Lat (GridBagLayout layout, int y) {
+                       super (layout, y, "Latitude");
+               }
+       }
+
+       Lat lat;
+
+       class Lon extends Value {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       if (state.gps != null && state.gps.connected && state.gps.lon != AltosLib.MISSING)
+                               show(pos(state.gps.lon,"E", "W"));
+                       else
+                               show("???");
+               }
+               public Lon (GridBagLayout layout, int y) {
+                       super (layout, y, "Longitude");
+               }
+       }
+
+       Lon lon;
+
+       class GPSLocked extends Info {
+               void show (AltosState state, AltosListenerState listener_state) {
+                       if (state == null || state.gps == null)
+                               hide();
+                       else {
+                               show("%4d sats", state.gps.nsat);
+                               lights.set(state.gps.locked && state.gps.nsat >= 4);
+                       }
+               }
+               public GPSLocked (GridBagLayout layout, int y) {
+                       super (layout, y, "GPS Locked");
+               }
+       }
+
+       GPSLocked gps_locked;
+
+       public void reset() {
+               lat.reset();
+               lon.reset();
+               altitude.reset();
+               ground_speed.reset();
+               ascent_rate.reset();
+               course.reset();
+               gps_locked.reset();
+       }
+
+       public void set_font() {
+               cur.setFont(AltosUILib.label_font);
+               max.setFont(AltosUILib.label_font);
+               lat.set_font();
+               lon.set_font();
+               altitude.set_font();
+               ground_speed.set_font();
+               ascent_rate.set_font();
+               course.set_font();
+               gps_locked.set_font();
+       }
+
+       public void show(AltosState state, AltosListenerState listener_state) {
+               if (state.gps != null && state.gps.connected) {
+                       lat.show(state, listener_state);
+                       lon.show(state, listener_state);
+               } else {
+                       lat.hide();
+                       lon.hide();
+               }
+               altitude.show(state, listener_state);
+               ground_speed.show(state, listener_state);
+               ascent_rate.show(state, listener_state);
+               course.show(state, listener_state);
+               gps_locked.show(state, listener_state);
+       }
+
+       public void labels(GridBagLayout layout, int y) {
+               GridBagConstraints      c;
+
+               cur = new JLabel("Current");
+               cur.setFont(AltosUILib.label_font);
+               c = new GridBagConstraints();
+               c.gridx = 2; c.gridy = y;
+               c.insets = new Insets(AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad, AltosUILib.tab_elt_pad);
+               layout.setConstraints(cur, c);
+               add(cur);
+
+               max = new JLabel("Maximum");
+               max.setFont(AltosUILib.label_font);
+               c.gridx = 3; c.gridy = y;
+               layout.setConstraints(max, c);
+               add(max);
+       }
+
+       public String getName() {
+               return "Info";
+       }
+
+       public TeleGPSInfo() {
+               layout = new GridBagLayout();
+
+               setLayout(layout);
+
+               /* Elements in ascent display:
+                *
+                * lat
+                * lon
+                * height
+                */
+               int y = 0;
+               labels(layout, y++);
+               altitude = new Altitude(layout, y++);
+               ground_speed = new GroundSpeed(layout, y++);
+               ascent_rate = new AscentRate(layout, y++);
+               course = new Course(layout, y++);
+               lat = new Lat(layout, y++);
+               lon = new Lon(layout, y++);
+               gps_locked = new GPSLocked(layout, y++);
+       }
+}