first cut at turnon scripts for EasyTimer v2
[fw/altos] / telegps / TeleGPSConfigUI.java
1 /*
2  * Copyright © 2010 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; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17  */
18
19 package org.altusmetrum.telegps;
20
21 import java.text.*;
22 import java.awt.*;
23 import java.awt.event.*;
24 import javax.swing.*;
25 import javax.swing.event.*;
26 import org.altusmetrum.altoslib_14.*;
27 import org.altusmetrum.altosuilib_14.*;
28
29 public class TeleGPSConfigUI
30         extends AltosUIDialog
31         implements ActionListener, ItemListener, DocumentListener, AltosConfigValues, AltosUnitsListener
32 {
33
34         Container               pane;
35         JLabel                  product_label;
36         JLabel                  version_label;
37         JLabel                  serial_label;
38         JLabel                  frequency_label;
39         JLabel                  radio_calibration_label;
40         JLabel                  radio_frequency_label;
41         JLabel                  radio_enable_label;
42         JLabel                  radio_10mw_label;
43         JLabel                  report_feet_label;
44         JLabel                  gps_receiver_label;
45         JLabel                  rate_label;
46         JLabel                  aprs_interval_label;
47         JLabel                  aprs_ssid_label;
48         JLabel                  aprs_format_label;
49         JLabel                  aprs_offset_label;
50         JLabel                  flight_log_max_label;
51         JLabel                  callsign_label;
52         JLabel                  tracker_motion_label;
53         JLabel                  tracker_interval_label;
54
55         public boolean          dirty;
56
57         JFrame                  owner;
58         JLabel                  product_value;
59         JLabel                  version_value;
60         JLabel                  serial_value;
61         AltosUIFreqList         radio_frequency_value;
62         JLabel                  radio_calibration_value;
63         JRadioButton            radio_enable_value;
64         JRadioButton            radio_10mw_value;
65         JComboBox<String>       report_feet_value;
66         JComboBox<String>       gps_receiver_value;
67         AltosUIRateList         rate_value;
68         JComboBox<String>       aprs_interval_value;
69         JComboBox<Integer>      aprs_ssid_value;
70         JComboBox<String>       aprs_format_value;
71         JComboBox<Integer>      aprs_offset_value;
72         JComboBox<String>       flight_log_max_value;
73         JTextField              callsign_value;
74         JComboBox<String>       tracker_motion_value;
75         JComboBox<String>       tracker_interval_value;
76
77         JButton                 save;
78         JButton                 reset;
79         JButton                 reboot;
80         JButton                 close;
81
82         ActionListener          listener;
83
84         static String[]         aprs_interval_values = {
85                 "Disabled",
86                 "2",
87                 "5",
88                 "10"
89         };
90
91         static Integer[]        aprs_ssid_values = {
92                 0, 1, 2 ,3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
93         };
94
95         static Integer[]        aprs_offset_values = {
96                 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
97         };
98
99         static String[]         tracker_motion_values_m = {
100                 "2",
101                 "5",
102                 "10",
103                 "25",
104         };
105
106         static String[]         tracker_motion_values_ft = {
107                 "5",
108                 "20",
109                 "50",
110                 "100"
111         };
112
113         static String[]         tracker_interval_values = {
114                 "1",
115                 "2",
116                 "5",
117                 "10"
118         };
119
120         static String[]         report_feet_values = {
121                 "Meters",
122                 "Feet",
123         };
124
125         /* A window listener to catch closing events and tell the config code */
126         class ConfigListener extends WindowAdapter {
127                 TeleGPSConfigUI ui;
128
129                 public ConfigListener(TeleGPSConfigUI this_ui) {
130                         ui = this_ui;
131                 }
132
133                 public void windowClosing(WindowEvent e) {
134                         ui.actionPerformed(new ActionEvent(e.getSource(),
135                                                            ActionEvent.ACTION_PERFORMED,
136                                                            "Close"));
137                 }
138         }
139
140         public void set_pyros(AltosPyro[] new_pyros) {
141         }
142
143         public AltosPyro[] pyros() {
144                 return null;
145         }
146
147         public void set_pyro_firing_time(double new_pyro_firing_time) {
148         }
149
150         public double pyro_firing_time() {
151                 return AltosLib.MISSING;
152         }
153
154         boolean is_telemetrum() {
155                 String  product = product_value.getText();
156                 return product != null && product.startsWith("TeleGPS");
157         }
158
159         void set_radio_enable_tool_tip() {
160                 if (radio_enable_value.isVisible())
161                         radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions");
162                 else
163                         radio_enable_value.setToolTipText("Firmware version does not support disabling radio");
164         }
165
166         void set_radio_10mw_tool_tip() {
167                 if (radio_10mw_value.isVisible())
168                         radio_10mw_value.setToolTipText("Should transmitter power be limited to 10mW");
169                 else
170                         radio_10mw_value.setToolTipText("Older firmware could not limit radio power");
171         }
172
173         void set_report_feet_tool_tip() {
174                 if (report_feet_value.isVisible())
175                         report_feet_value.setToolTipText("Units used after landing to beep max height");
176                 else
177                         report_feet_value.setToolTipText("Older firmware always beeps max height in meters");
178         }
179
180         public void set_report_feet(int new_report_feet) {
181                 if (new_report_feet != AltosLib.MISSING) {
182                         if (new_report_feet >= report_feet_values.length)
183                                 new_report_feet = 0;
184                         if (new_report_feet < 0) {
185                                 report_feet_value.setEnabled(false);
186                                 new_report_feet = 0;
187                         } else {
188                                 report_feet_value.setEnabled(true);
189                         }
190                         report_feet_value.setSelectedIndex(new_report_feet);
191                 }
192                 report_feet_value.setVisible(new_report_feet != AltosLib.MISSING);
193                 report_feet_label.setVisible(new_report_feet != AltosLib.MISSING);
194
195                 set_report_feet_tool_tip();
196         }
197
198         public int report_feet() {
199                 if (report_feet_value.isVisible())
200                         return report_feet_value.getSelectedIndex();
201                 else
202                         return AltosLib.MISSING;
203         }
204
205         void set_gps_receiver_tool_tip() {
206                 if (gps_receiver_value.isVisible())
207                         gps_receiver_value.setToolTipText("GPS receiver selection");
208                 else
209                         gps_receiver_value.setToolTipText("Only TeleMega with new firmware supports alternate GPS receivers");
210         }
211
212         public void set_gps_receiver(int new_gps_receiver) {
213                 System.out.printf("set_gps_receiver %d\n", new_gps_receiver);
214                 if (new_gps_receiver != AltosLib.MISSING) {
215                         if (new_gps_receiver >= AltosLib.gps_receiver_names.length)
216                                 new_gps_receiver = 0;
217                         if (new_gps_receiver < 0) {
218                                 gps_receiver_value.setEnabled(false);
219                                 new_gps_receiver = 0;
220                         } else {
221                                 gps_receiver_value.setEnabled(true);
222                         }
223                         gps_receiver_value.setSelectedIndex(new_gps_receiver);
224                 }
225                 gps_receiver_value.setVisible(new_gps_receiver != AltosLib.MISSING);
226                 gps_receiver_label.setVisible(new_gps_receiver != AltosLib.MISSING);
227
228                 set_gps_receiver_tool_tip();
229         }
230
231         public int gps_receiver() {
232                 if (gps_receiver_value.isVisible())
233                         return gps_receiver_value.getSelectedIndex();
234                 else
235                         return AltosLib.MISSING;
236         }
237
238         void set_rate_tool_tip() {
239                 if (rate_value.isVisible())
240                         rate_value.setToolTipText("Select telemetry baud rate");
241                 else
242                         rate_value.setToolTipText("Firmware version does not support variable telemetry rates");
243         }
244
245         void set_aprs_interval_tool_tip() {
246                 if (aprs_interval_value.isVisible())
247                         aprs_interval_value.setToolTipText("Enable APRS and set the interval between APRS reports");
248                 else
249                         aprs_interval_value.setToolTipText("Hardware doesn't support APRS");
250         }
251
252         void set_aprs_ssid_tool_tip() {
253                 if (aprs_ssid_value.isVisible())
254                         aprs_ssid_value.setToolTipText("Set the APRS SSID (secondary station identifier)");
255                 else if (aprs_ssid_value.isVisible())
256                         aprs_ssid_value.setToolTipText("Software version doesn't support setting the APRS SSID");
257                 else
258                         aprs_ssid_value.setToolTipText("Hardware doesn't support APRS");
259         }
260
261         void set_aprs_format_tool_tip() {
262                 if (aprs_format_value.isVisible())
263                         aprs_format_value.setToolTipText("Set the APRS format (compressed/uncompressed)");
264                 else if (aprs_format_value.isVisible())
265                         aprs_format_value.setToolTipText("Software version doesn't support setting the APRS format");
266                 else
267                         aprs_format_value.setToolTipText("Hardware doesn't support APRS");
268         }
269
270         void set_aprs_offset_tool_tip() {
271                 if (aprs_offset_value.isVisible())
272                         aprs_offset_value.setToolTipText("Set the APRS offset from top of minute");
273                 else if (aprs_offset_value.isVisible())
274                         aprs_offset_value.setToolTipText("Software version doesn't support setting the APRS offset");
275                 else
276                         aprs_offset_value.setToolTipText("Hardware doesn't support APRS");
277         }
278
279         void set_flight_log_max_tool_tip() {
280                 if (flight_log_max_value.isVisible())
281                         flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)");
282                 else
283                         flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory");
284         }
285
286         public boolean has_radio() { return true; }
287
288         /* Build the UI using a grid bag */
289         public TeleGPSConfigUI(JFrame in_owner) {
290                 super (in_owner, "Configure Device", false);
291
292                 owner = in_owner;
293                 GridBagConstraints c;
294                 int row = 0;
295
296                 Insets il = new Insets(4,4,4,4);
297                 Insets ir = new Insets(4,4,4,4);
298
299                 pane = getScrollablePane();
300                 pane.setLayout(new GridBagLayout());
301
302                 /* Product */
303                 c = new GridBagConstraints();
304                 c.gridx = 0; c.gridy = row;
305                 c.gridwidth = 4;
306                 c.fill = GridBagConstraints.NONE;
307                 c.anchor = GridBagConstraints.LINE_START;
308                 c.insets = il;
309                 product_label = new JLabel("Product:");
310                 pane.add(product_label, c);
311
312                 c = new GridBagConstraints();
313                 c.gridx = 4; c.gridy = row;
314                 c.gridwidth = 4;
315                 c.fill = GridBagConstraints.HORIZONTAL;
316                 c.weightx = 1;
317                 c.anchor = GridBagConstraints.LINE_START;
318                 c.insets = ir;
319                 product_value = new JLabel("");
320                 pane.add(product_value, c);
321                 row++;
322
323                 /* Version */
324                 c = new GridBagConstraints();
325                 c.gridx = 0; c.gridy = row;
326                 c.gridwidth = 4;
327                 c.fill = GridBagConstraints.NONE;
328                 c.anchor = GridBagConstraints.LINE_START;
329                 c.insets = il;
330                 c.ipady = 5;
331                 version_label = new JLabel("Software version:");
332                 pane.add(version_label, c);
333
334                 c = new GridBagConstraints();
335                 c.gridx = 4; c.gridy = row;
336                 c.gridwidth = 4;
337                 c.fill = GridBagConstraints.HORIZONTAL;
338                 c.weightx = 1;
339                 c.anchor = GridBagConstraints.LINE_START;
340                 c.insets = ir;
341                 c.ipady = 5;
342                 version_value = new JLabel("");
343                 pane.add(version_value, c);
344                 row++;
345
346                 /* Serial */
347                 c = new GridBagConstraints();
348                 c.gridx = 0; c.gridy = row;
349                 c.gridwidth = 4;
350                 c.fill = GridBagConstraints.NONE;
351                 c.anchor = GridBagConstraints.LINE_START;
352                 c.insets = il;
353                 c.ipady = 5;
354                 serial_label = new JLabel("Serial:");
355                 pane.add(serial_label, c);
356
357                 c = new GridBagConstraints();
358                 c.gridx = 4; c.gridy = row;
359                 c.gridwidth = 4;
360                 c.fill = GridBagConstraints.HORIZONTAL;
361                 c.weightx = 1;
362                 c.anchor = GridBagConstraints.LINE_START;
363                 c.insets = ir;
364                 c.ipady = 5;
365                 serial_value = new JLabel("");
366                 pane.add(serial_value, c);
367                 row++;
368
369                 /* Frequency */
370                 c = new GridBagConstraints();
371                 c.gridx = 0; c.gridy = row;
372                 c.gridwidth = 4;
373                 c.fill = GridBagConstraints.NONE;
374                 c.anchor = GridBagConstraints.LINE_START;
375                 c.insets = il;
376                 c.ipady = 5;
377                 radio_frequency_label = new JLabel("Frequency:");
378                 pane.add(radio_frequency_label, c);
379
380                 c = new GridBagConstraints();
381                 c.gridx = 4; c.gridy = row;
382                 c.gridwidth = 4;
383                 c.fill = GridBagConstraints.HORIZONTAL;
384                 c.weightx = 1;
385                 c.anchor = GridBagConstraints.LINE_START;
386                 c.insets = ir;
387                 c.ipady = 5;
388                 radio_frequency_value = new AltosUIFreqList();
389                 radio_frequency_value.addItemListener(this);
390                 pane.add(radio_frequency_value, c);
391                 radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency");
392                 row++;
393
394                 /* Radio Calibration */
395                 c = new GridBagConstraints();
396                 c.gridx = 0; c.gridy = row;
397                 c.gridwidth = 4;
398                 c.fill = GridBagConstraints.NONE;
399                 c.anchor = GridBagConstraints.LINE_START;
400                 c.insets = il;
401                 c.ipady = 5;
402                 radio_calibration_label = new JLabel("RF Calibration:");
403                 pane.add(radio_calibration_label, c);
404
405                 c = new GridBagConstraints();
406                 c.gridx = 4; c.gridy = row;
407                 c.gridwidth = 4;
408                 c.fill = GridBagConstraints.HORIZONTAL;
409                 c.weightx = 1;
410                 c.anchor = GridBagConstraints.LINE_START;
411                 c.insets = ir;
412                 c.ipady = 5;
413                 radio_calibration_value = new JLabel(String.format("%d", 1186611));
414                 pane.add(radio_calibration_value, c);
415                 row++;
416
417                 /* Radio Enable */
418                 c = new GridBagConstraints();
419                 c.gridx = 0; c.gridy = row;
420                 c.gridwidth = 4;
421                 c.fill = GridBagConstraints.NONE;
422                 c.anchor = GridBagConstraints.LINE_START;
423                 c.insets = il;
424                 c.ipady = 5;
425                 radio_enable_label = new JLabel("Telemetry/RDF/APRS Enable:");
426                 pane.add(radio_enable_label, c);
427
428                 c = new GridBagConstraints();
429                 c.gridx = 4; c.gridy = row;
430                 c.gridwidth = 4;
431                 c.fill = GridBagConstraints.HORIZONTAL;
432                 c.weightx = 1;
433                 c.anchor = GridBagConstraints.LINE_START;
434                 c.insets = ir;
435                 c.ipady = 5;
436                 radio_enable_value = new JRadioButton("Enabled");
437                 radio_enable_value.addItemListener(this);
438                 pane.add(radio_enable_value, c);
439                 set_radio_enable_tool_tip();
440                 row++;
441
442                 /* Report feet */
443                 c = new GridBagConstraints();
444                 c.gridx = 0; c.gridy = row;
445                 c.gridwidth = 4;
446                 c.fill = GridBagConstraints.NONE;
447                 c.anchor = GridBagConstraints.LINE_START;
448                 c.insets = il;
449                 c.ipady = 5;
450                 report_feet_label = new JLabel("Beep max height in:");
451                 pane.add(report_feet_label, c);
452
453                 c = new GridBagConstraints();
454                 c.gridx = 4; c.gridy = row;
455                 c.gridwidth = 4;
456                 c.fill = GridBagConstraints.HORIZONTAL;
457                 c.weightx = 1;
458                 c.anchor = GridBagConstraints.LINE_START;
459                 c.insets = ir;
460                 c.ipady = 5;
461                 report_feet_value = new JComboBox<String>(report_feet_values);
462                 report_feet_value.setEditable(false);
463                 report_feet_value.addItemListener(this);
464                 pane.add(report_feet_value, c);
465                 set_report_feet_tool_tip();
466                 row++;
467
468                 /* GPS Receiver */
469                 c = new GridBagConstraints();
470                 c.gridx = 0; c.gridy = row;
471                 c.gridwidth = 4;
472                 c.fill = GridBagConstraints.NONE;
473                 c.anchor = GridBagConstraints.LINE_START;
474                 c.insets = il;
475                 c.ipady = 5;
476                 gps_receiver_label = new JLabel("GPS Receiver:");
477                 pane.add(gps_receiver_label, c);
478
479                 c = new GridBagConstraints();
480                 c.gridx = 4; c.gridy = row;
481                 c.gridwidth = 4;
482                 c.fill = GridBagConstraints.HORIZONTAL;
483                 c.weightx = 1;
484                 c.anchor = GridBagConstraints.LINE_START;
485                 c.insets = ir;
486                 c.ipady = 5;
487                 gps_receiver_value = new JComboBox<String>(AltosLib.gps_receiver_names);
488                 gps_receiver_value.setEditable(false);
489                 gps_receiver_value.addItemListener(this);
490                 pane.add(gps_receiver_value, c);
491                 set_gps_receiver_tool_tip();
492                 row++;
493
494                 /* Radio 10mW limit */
495                 c = new GridBagConstraints();
496                 c.gridx = 0; c.gridy = row;
497                 c.gridwidth = 4;
498                 c.fill = GridBagConstraints.NONE;
499                 c.anchor = GridBagConstraints.LINE_START;
500                 c.insets = il;
501                 c.ipady = 5;
502                 radio_10mw_label = new JLabel("Limit transmit to 10mW:");
503                 pane.add(radio_10mw_label, c);
504
505                 c = new GridBagConstraints();
506                 c.gridx = 4; c.gridy = row;
507                 c.gridwidth = 4;
508                 c.fill = GridBagConstraints.HORIZONTAL;
509                 c.weightx = 1;
510                 c.anchor = GridBagConstraints.LINE_START;
511                 c.insets = ir;
512                 c.ipady = 5;
513                 radio_10mw_value = new JRadioButton("Limited");
514                 radio_10mw_value.addItemListener(this);
515                 pane.add(radio_10mw_value, c);
516                 set_radio_10mw_tool_tip();
517                 row++;
518
519                 /* Telemetry Rate */
520                 c = new GridBagConstraints();
521                 c.gridx = 0; c.gridy = row;
522                 c.gridwidth = 4;
523                 c.fill = GridBagConstraints.NONE;
524                 c.anchor = GridBagConstraints.LINE_START;
525                 c.insets = il;
526                 c.ipady = 5;
527                 rate_label = new JLabel("Telemetry baud rate:");
528                 pane.add(rate_label, c);
529
530                 c = new GridBagConstraints();
531                 c.gridx = 4; c.gridy = row;
532                 c.gridwidth = 4;
533                 c.fill = GridBagConstraints.HORIZONTAL;
534                 c.weightx = 1;
535                 c.anchor = GridBagConstraints.LINE_START;
536                 c.insets = ir;
537                 c.ipady = 5;
538                 rate_value = new AltosUIRateList();
539                 rate_value.addItemListener(this);
540                 pane.add(rate_value, c);
541                 set_rate_tool_tip();
542                 row++;
543
544                 /* APRS interval */
545                 c = new GridBagConstraints();
546                 c.gridx = 0; c.gridy = row;
547                 c.gridwidth = 4;
548                 c.fill = GridBagConstraints.NONE;
549                 c.anchor = GridBagConstraints.LINE_START;
550                 c.insets = il;
551                 c.ipady = 5;
552                 aprs_interval_label = new JLabel("APRS Interval(s):");
553                 pane.add(aprs_interval_label, c);
554
555                 c = new GridBagConstraints();
556                 c.gridx = 4; c.gridy = row;
557                 c.gridwidth = 4;
558                 c.fill = GridBagConstraints.HORIZONTAL;
559                 c.weightx = 1;
560                 c.anchor = GridBagConstraints.LINE_START;
561                 c.insets = ir;
562                 c.ipady = 5;
563                 aprs_interval_value = new JComboBox<String>(aprs_interval_values);
564                 aprs_interval_value.setEditable(true);
565                 aprs_interval_value.addItemListener(this);
566                 pane.add(aprs_interval_value, c);
567                 set_aprs_interval_tool_tip();
568                 row++;
569
570                 /* APRS SSID */
571                 c = new GridBagConstraints();
572                 c.gridx = 0; c.gridy = row;
573                 c.gridwidth = 4;
574                 c.fill = GridBagConstraints.NONE;
575                 c.anchor = GridBagConstraints.LINE_START;
576                 c.insets = il;
577                 c.ipady = 5;
578                 aprs_ssid_label = new JLabel("APRS SSID:");
579                 pane.add(aprs_ssid_label, c);
580
581                 c = new GridBagConstraints();
582                 c.gridx = 4; c.gridy = row;
583                 c.gridwidth = 4;
584                 c.fill = GridBagConstraints.HORIZONTAL;
585                 c.weightx = 1;
586                 c.anchor = GridBagConstraints.LINE_START;
587                 c.insets = ir;
588                 c.ipady = 5;
589                 aprs_ssid_value = new JComboBox<Integer>(aprs_ssid_values);
590                 aprs_ssid_value.setEditable(false);
591                 aprs_ssid_value.addItemListener(this);
592                 aprs_ssid_value.setMaximumRowCount(aprs_ssid_values.length);
593                 pane.add(aprs_ssid_value, c);
594                 set_aprs_ssid_tool_tip();
595                 row++;
596
597                 /* APRS format */
598                 c = new GridBagConstraints();
599                 c.gridx = 0; c.gridy = row;
600                 c.gridwidth = 4;
601                 c.fill = GridBagConstraints.NONE;
602                 c.anchor = GridBagConstraints.LINE_START;
603                 c.insets = il;
604                 c.ipady = 5;
605                 aprs_format_label = new JLabel("APRS format:");
606                 pane.add(aprs_format_label, c);
607
608                 c = new GridBagConstraints();
609                 c.gridx = 4; c.gridy = row;
610                 c.gridwidth = 4;
611                 c.fill = GridBagConstraints.HORIZONTAL;
612                 c.weightx = 1;
613                 c.anchor = GridBagConstraints.LINE_START;
614                 c.insets = ir;
615                 c.ipady = 5;
616                 aprs_format_value = new JComboBox<String>(AltosLib.ao_aprs_format_name);
617                 aprs_format_value.setEditable(false);
618                 aprs_format_value.addItemListener(this);
619                 aprs_format_value.setMaximumRowCount(AltosLib.ao_aprs_format_name.length);
620                 pane.add(aprs_format_value, c);
621                 set_aprs_format_tool_tip();
622                 row++;
623
624                 /* APRS offset */
625                 c = new GridBagConstraints();
626                 c.gridx = 0; c.gridy = row;
627                 c.gridwidth = 4;
628                 c.fill = GridBagConstraints.NONE;
629                 c.anchor = GridBagConstraints.LINE_START;
630                 c.insets = il;
631                 c.ipady = 5;
632                 aprs_offset_label = new JLabel("APRS offset:");
633                 pane.add(aprs_offset_label, c);
634
635                 c = new GridBagConstraints();
636                 c.gridx = 4; c.gridy = row;
637                 c.gridwidth = 4;
638                 c.fill = GridBagConstraints.HORIZONTAL;
639                 c.weightx = 1;
640                 c.anchor = GridBagConstraints.LINE_START;
641                 c.insets = ir;
642                 c.ipady = 5;
643                 aprs_offset_value = new JComboBox<Integer>(aprs_offset_values);
644                 aprs_offset_value.setEditable(false);
645                 aprs_offset_value.addItemListener(this);
646                 aprs_offset_value.setMaximumRowCount(aprs_offset_values.length);
647                 pane.add(aprs_offset_value, c);
648                 set_aprs_offset_tool_tip();
649                 row++;
650
651                 /* Callsign */
652                 c = new GridBagConstraints();
653                 c.gridx = 0; c.gridy = row;
654                 c.gridwidth = 4;
655                 c.fill = GridBagConstraints.NONE;
656                 c.anchor = GridBagConstraints.LINE_START;
657                 c.insets = il;
658                 c.ipady = 5;
659                 callsign_label = new JLabel("Callsign:");
660                 pane.add(callsign_label, c);
661
662                 c = new GridBagConstraints();
663                 c.gridx = 4; c.gridy = row;
664                 c.gridwidth = 4;
665                 c.fill = GridBagConstraints.HORIZONTAL;
666                 c.weightx = 1;
667                 c.anchor = GridBagConstraints.LINE_START;
668                 c.insets = ir;
669                 c.ipady = 5;
670                 callsign_value = new JTextField(AltosUIPreferences.callsign());
671                 callsign_value.getDocument().addDocumentListener(this);
672                 pane.add(callsign_value, c);
673                 callsign_value.setToolTipText("Callsign reported in telemetry data");
674                 row++;
675
676                 /* Flight log max */
677                 c = new GridBagConstraints();
678                 c.gridx = 0; c.gridy = row;
679                 c.gridwidth = 4;
680                 c.fill = GridBagConstraints.NONE;
681                 c.anchor = GridBagConstraints.LINE_START;
682                 c.insets = il;
683                 c.ipady = 5;
684                 flight_log_max_label = new JLabel("Maximum Log Size (kB):");
685                 pane.add(flight_log_max_label, c);
686
687                 c = new GridBagConstraints();
688                 c.gridx = 4; c.gridy = row;
689                 c.gridwidth = 4;
690                 c.fill = GridBagConstraints.HORIZONTAL;
691                 c.weightx = 1;
692                 c.anchor = GridBagConstraints.LINE_START;
693                 c.insets = ir;
694                 c.ipady = 5;
695                 flight_log_max_value = new JComboBox<String>();
696                 flight_log_max_value.setEditable(true);
697                 flight_log_max_value.addItemListener(this);
698                 pane.add(flight_log_max_value, c);
699                 set_flight_log_max_tool_tip();
700                 row++;
701
702                 /* Tracker triger horiz distances */
703                 c = new GridBagConstraints();
704                 c.gridx = 0; c.gridy = row;
705                 c.gridwidth = 4;
706                 c.fill = GridBagConstraints.NONE;
707                 c.anchor = GridBagConstraints.LINE_START;
708                 c.insets = il;
709                 c.ipady = 5;
710                 tracker_motion_label = new JLabel(get_tracker_motion_label());
711                 pane.add(tracker_motion_label, c);
712
713                 c = new GridBagConstraints();
714                 c.gridx = 4; c.gridy = row;
715                 c.gridwidth = 4;
716                 c.fill = GridBagConstraints.HORIZONTAL;
717                 c.weightx = 1;
718                 c.anchor = GridBagConstraints.LINE_START;
719                 c.insets = ir;
720                 c.ipady = 5;
721                 tracker_motion_value = new JComboBox<String>(tracker_motion_values());
722                 tracker_motion_value.setEditable(true);
723                 tracker_motion_value.addItemListener(this);
724                 pane.add(tracker_motion_value, c);
725                 row++;
726
727                 /* Tracker triger vert distances */
728                 c = new GridBagConstraints();
729                 c.gridx = 0; c.gridy = row;
730                 c.gridwidth = 4;
731                 c.fill = GridBagConstraints.NONE;
732                 c.anchor = GridBagConstraints.LINE_START;
733                 c.insets = il;
734                 c.ipady = 5;
735                 tracker_interval_label = new JLabel("Position Reporting Interval (s):");
736                 pane.add(tracker_interval_label, c);
737
738                 c = new GridBagConstraints();
739                 c.gridx = 4; c.gridy = row;
740                 c.gridwidth = 4;
741                 c.fill = GridBagConstraints.HORIZONTAL;
742                 c.weightx = 1;
743                 c.anchor = GridBagConstraints.LINE_START;
744                 c.insets = ir;
745                 c.ipady = 5;
746                 tracker_interval_value = new JComboBox<String>(tracker_interval_values);
747                 tracker_interval_value.setEditable(true);
748                 tracker_interval_value.addItemListener(this);
749                 pane.add(tracker_interval_value, c);
750                 set_tracker_tool_tip();
751                 row++;
752
753                 /* Buttons */
754                 c = new GridBagConstraints();
755                 c.gridx = 0; c.gridy = row;
756                 c.gridwidth = 2;
757                 c.fill = GridBagConstraints.NONE;
758                 c.anchor = GridBagConstraints.LINE_START;
759                 c.insets = il;
760                 save = new JButton("Save");
761                 pane.add(save, c);
762                 save.addActionListener(this);
763                 save.setActionCommand("Save");
764
765                 c = new GridBagConstraints();
766                 c.gridx = 2; c.gridy = row;
767                 c.gridwidth = 2;
768                 c.fill = GridBagConstraints.NONE;
769                 c.anchor = GridBagConstraints.CENTER;
770                 c.insets = il;
771                 reset = new JButton("Reset");
772                 pane.add(reset, c);
773                 reset.addActionListener(this);
774                 reset.setActionCommand("Reset");
775
776                 c = new GridBagConstraints();
777                 c.gridx = 4; c.gridy = row;
778                 c.gridwidth = 2;
779                 c.fill = GridBagConstraints.NONE;
780                 c.anchor = GridBagConstraints.CENTER;
781                 c.insets = il;
782                 reboot = new JButton("Reboot");
783                 pane.add(reboot, c);
784                 reboot.addActionListener(this);
785                 reboot.setActionCommand("Reboot");
786
787                 c = new GridBagConstraints();
788                 c.gridx = 6; c.gridy = row;
789                 c.gridwidth = 2;
790                 c.fill = GridBagConstraints.NONE;
791                 c.anchor = GridBagConstraints.LINE_END;
792                 c.insets = il;
793                 close = new JButton("Close");
794                 pane.add(close, c);
795                 close.addActionListener(this);
796                 close.setActionCommand("Close");
797
798                 addWindowListener(new ConfigListener(this));
799                 AltosPreferences.register_units_listener(this);
800         }
801
802         /* Once the initial values are set, the config code will show the dialog */
803         public void make_visible() {
804                 pack();
805                 setLocationRelativeTo(owner);
806                 setVisible(true);
807         }
808
809         /* If any values have been changed, confirm before closing */
810         public boolean check_dirty(String operation) {
811                 if (dirty) {
812                         Object[] options = { String.format("%s anyway", operation), "Keep editing" };
813                         int i;
814                         i = JOptionPane.showOptionDialog(this,
815                                                          String.format("Configuration modified. %s anyway?", operation),
816                                                          "Configuration Modified",
817                                                          JOptionPane.DEFAULT_OPTION,
818                                                          JOptionPane.WARNING_MESSAGE,
819                                                          null, options, options[1]);
820                         if (i != 0)
821                                 return false;
822                 }
823                 return true;
824         }
825
826         public void set_dirty() {
827                 dirty = true;
828                 save.setEnabled(true);
829         }
830
831         public void set_clean() {
832                 dirty = false;
833                 save.setEnabled(false);
834         }
835
836         public void dispose() {
837                 AltosPreferences.unregister_units_listener(this);
838                 super.dispose();
839         }
840
841         public int accel_cal_plus() {
842                 return AltosLib.MISSING;
843         }
844
845         public int accel_cal_minus() {
846                 return AltosLib.MISSING;
847         }
848
849         public void set_accel_cal(int accel_plus, int accel_minus) {
850         }
851
852         /* Listen for events from our buttons */
853         public void actionPerformed(ActionEvent e) {
854                 String  cmd = e.getActionCommand();
855
856                 if (cmd.equals("Close") || cmd.equals("Reboot"))
857                         if (!check_dirty(cmd))
858                                 return;
859                 listener.actionPerformed(e);
860                 if (cmd.equals("Close") || cmd.equals("Reboot")) {
861                         setVisible(false);
862                         dispose();
863                 }
864                 set_clean();
865         }
866
867         /* ItemListener interface method */
868         public void itemStateChanged(ItemEvent e) {
869                 set_dirty();
870         }
871
872         /* DocumentListener interface methods */
873         public void changedUpdate(DocumentEvent e) {
874                 set_dirty();
875         }
876
877         public void insertUpdate(DocumentEvent e) {
878                 set_dirty();
879         }
880
881         public void removeUpdate(DocumentEvent e) {
882                 set_dirty();
883         }
884
885         /* Let the config code hook on a listener */
886         public void addActionListener(ActionListener l) {
887                 listener = l;
888         }
889
890         public void units_changed(boolean imperial_units) {
891                 boolean was_dirty = dirty;
892
893                 if (tracker_motion_value.isVisible()) {
894                         String motion = tracker_motion_value.getSelectedItem().toString();
895                         tracker_motion_label.setText(get_tracker_motion_label());
896                         set_tracker_motion_values();
897                         try {
898                                 int m = (int) (AltosConvert.height.parse_locale(motion, !imperial_units) + 0.5);
899                                 set_tracker_motion(m);
900                         } catch (ParseException pe) {
901                         }
902                 }
903                 if (!was_dirty)
904                         set_clean();
905         }
906
907         /* set and get all of the dialog values */
908         public void set_product(String product) {
909                 radio_frequency_value.set_product(product);
910                 product_value.setText(product);
911                 set_flight_log_max_tool_tip();
912         }
913
914         public void set_version(String version) {
915                 version_value.setText(version);
916         }
917
918         public void set_serial(int serial) {
919                 radio_frequency_value.set_serial(serial);
920                 serial_value.setText(String.format("%d", serial));
921         }
922
923         public void set_altitude_32(int altitude_32) {
924         }
925
926         public void set_main_deploy(int new_main_deploy) {
927         }
928
929         public int main_deploy() {
930                 return AltosLib.MISSING;
931         }
932
933         public void set_apogee_delay(int new_apogee_delay) { }
934
935         public int apogee_delay() {
936                 return AltosLib.MISSING;
937         }
938
939         public void set_apogee_lockout(int new_apogee_lockout) { }
940
941         public int apogee_lockout() { return AltosLib.MISSING; }
942
943         public void set_radio_frequency(double new_radio_frequency) {
944                 if (new_radio_frequency != AltosLib.MISSING)
945                         radio_frequency_value.set_frequency(new_radio_frequency);
946                 radio_frequency_label.setVisible(new_radio_frequency != AltosLib.MISSING);
947                 radio_frequency_value.setVisible(new_radio_frequency != AltosLib.MISSING);
948         }
949
950         public double radio_frequency() {
951                 return radio_frequency_value.frequency();
952         }
953
954         public void set_radio_calibration(int new_radio_calibration) {
955                 if (new_radio_calibration != AltosLib.MISSING)
956                         radio_calibration_value.setText(String.format("%d", new_radio_calibration));
957                 radio_calibration_value.setVisible(new_radio_calibration == AltosLib.MISSING);
958                 radio_calibration_label.setVisible(new_radio_calibration == AltosLib.MISSING);
959         }
960
961         public void set_radio_enable(int new_radio_enable) {
962                 if (new_radio_enable != AltosLib.MISSING)
963                         radio_enable_value.setSelected(new_radio_enable != 0);
964                 radio_enable_label.setVisible(new_radio_enable != AltosLib.MISSING);
965                 radio_enable_value.setVisible(new_radio_enable != AltosLib.MISSING);
966                 set_radio_enable_tool_tip();
967         }
968
969         public int radio_enable() {
970                 if (radio_enable_value.isVisible())
971                         return radio_enable_value.isSelected() ? 1 : 0;
972                 else
973                         return AltosLib.MISSING;
974         }
975
976         public void set_radio_10mw(int new_radio_10mw) {
977                 if (new_radio_10mw != AltosLib.MISSING) {
978                         radio_10mw_value.setSelected(new_radio_10mw != 0);
979                 }
980                 radio_10mw_value.setVisible(new_radio_10mw != AltosLib.MISSING);
981                 radio_10mw_label.setVisible(new_radio_10mw != AltosLib.MISSING);
982                 set_radio_10mw_tool_tip();
983         }
984
985         public int radio_10mw() {
986                 if (radio_10mw_value.isVisible())
987                         return radio_10mw_value.isSelected() ? 1 : 0;
988                 else
989                         return AltosLib.MISSING;
990         }
991
992         public void set_telemetry_rate(int new_rate) {
993                 if (new_rate != AltosLib.MISSING)
994                         rate_value.set_rate(new_rate);
995                 rate_label.setVisible(new_rate != AltosLib.MISSING);
996                 rate_value.setVisible(new_rate != AltosLib.MISSING);
997         }
998
999         public int telemetry_rate() {
1000                 return rate_value.rate();
1001         }
1002
1003         public void set_callsign(String new_callsign) {
1004                 if (new_callsign != null)
1005                         callsign_value.setText(new_callsign);
1006                 callsign_value.setVisible(new_callsign != null);
1007                 callsign_label.setVisible(new_callsign != null);
1008         }
1009
1010         public String callsign() {
1011                 if (callsign_value.isVisible())
1012                         return callsign_value.getText();
1013                 return null;
1014         }
1015
1016         private int parse_int(String name, String s, boolean split) throws AltosConfigDataException {
1017                 String v = s;
1018                 if (split)
1019                         v = s.split("\\s+")[0];
1020                 try {
1021                         return Integer.parseInt(v);
1022                 } catch (NumberFormatException ne) {
1023                         throw new AltosConfigDataException("Invalid %s \"%s\"", name, s);
1024                 }
1025         }
1026
1027         int     flight_log_max_limit;
1028         int     flight_log_max;
1029
1030         public String flight_log_max_label(int flight_log_max) {
1031                 if (flight_log_max_limit != 0) {
1032                         int     nflight = flight_log_max_limit / flight_log_max;
1033                         String  plural = nflight > 1 ? "s" : "";
1034
1035                         return String.format("%d (%d flight%s)", flight_log_max, nflight, plural);
1036                 }
1037                 return String.format("%d", flight_log_max);
1038         }
1039
1040         public void set_flight_log_max(int new_flight_log_max) {
1041                 flight_log_max_value.setSelectedItem(flight_log_max_label(new_flight_log_max));
1042                 flight_log_max = new_flight_log_max;
1043                 set_flight_log_max_tool_tip();
1044         }
1045
1046         public void set_flight_log_max_enabled(boolean enable) {
1047                 flight_log_max_value.setEnabled(enable);
1048                 set_flight_log_max_tool_tip();
1049         }
1050
1051         public int flight_log_max() throws AltosConfigDataException {
1052                 return parse_int("flight log max", flight_log_max_value.getSelectedItem().toString(), true);
1053         }
1054
1055         public void set_flight_log_max_limit(int new_flight_log_max_limit, int new_storage_erase_unit) {
1056                 flight_log_max_limit = new_flight_log_max_limit;
1057                 if (new_flight_log_max_limit != AltosLib.MISSING) {
1058                         flight_log_max_value.removeAllItems();
1059                         for (int i = 8; i >= 1; i--) {
1060                                 int     size = flight_log_max_limit / i;
1061                                 if (new_storage_erase_unit != 0)
1062                                         size &= ~(new_storage_erase_unit - 1);
1063                                 flight_log_max_value.addItem(String.format("%d (%d flights)", size, i));
1064                         }
1065                 }
1066                 if (flight_log_max != 0 && flight_log_max != AltosLib.MISSING)
1067                         set_flight_log_max(flight_log_max);
1068         }
1069
1070         public void set_ignite_mode(int new_ignite_mode) { }
1071         public int ignite_mode() { return AltosLib.MISSING; }
1072
1073
1074         public void set_pad_orientation(int new_pad_orientation) { }
1075         public int pad_orientation() { return AltosLib.MISSING; }
1076
1077         public void set_beep(int new_beep) { }
1078
1079         public int beep() { return AltosLib.MISSING; }
1080
1081         String[] tracker_motion_values() {
1082                 if (AltosConvert.imperial_units)
1083                         return tracker_motion_values_ft;
1084                 else
1085                         return tracker_motion_values_m;
1086         }
1087
1088         void set_tracker_motion_values() {
1089                 String[]        v = tracker_motion_values();
1090                 while (tracker_motion_value.getItemCount() > 0)
1091                         tracker_motion_value.removeItemAt(0);
1092                 for (int i = 0; i < v.length; i++)
1093                         tracker_motion_value.addItem(v[i]);
1094                 tracker_motion_value.setMaximumRowCount(v.length);
1095         }
1096
1097         String get_tracker_motion_label() {
1098                 return String.format("Logging Trigger Motion (%s):", AltosConvert.height.parse_units());
1099         }
1100
1101         void set_tracker_tool_tip() {
1102                 if (tracker_motion_value.isVisible())
1103                         tracker_motion_value.setToolTipText("How far the device must move before logging");
1104                 else
1105                         tracker_motion_value.setToolTipText("This device doesn't disable logging when stationary");
1106                 if (tracker_interval_value.isVisible())
1107                         tracker_interval_value.setToolTipText("How often to report GPS position");
1108                 else
1109                         tracker_interval_value.setToolTipText("This device can't configure interval");
1110         }
1111
1112         public void set_tracker_motion(int tracker_motion) {
1113                 if (tracker_motion != AltosLib.MISSING)
1114                         tracker_motion_value.setSelectedItem(AltosConvert.height.say(tracker_motion));
1115                 tracker_motion_label.setVisible(tracker_motion != AltosLib.MISSING);
1116                 tracker_motion_value.setVisible(tracker_motion != AltosLib.MISSING);
1117         }
1118
1119         public int tracker_motion() throws AltosConfigDataException {
1120                 if (tracker_motion_value.isVisible()) {
1121                         String str = tracker_motion_value.getSelectedItem().toString();
1122                         try {
1123                                 return (int) (AltosConvert.height.parse_locale(str) + 0.5);
1124                         } catch (ParseException pe) {
1125                                 throw new AltosConfigDataException("invalid tracker motion %s", str);
1126                         }
1127                 }
1128                 return AltosLib.MISSING;
1129         }
1130
1131         public void set_tracker_interval(int tracker_interval) {
1132                 if (tracker_interval != AltosLib.MISSING)
1133                         tracker_interval_value.setSelectedItem(String.format("%d", tracker_interval));
1134                 tracker_interval_label.setVisible(tracker_interval != AltosLib.MISSING);
1135                 tracker_interval_value.setVisible(tracker_interval != AltosLib.MISSING);
1136         }
1137
1138         public int tracker_interval() throws AltosConfigDataException {
1139                 if (tracker_interval_value.isVisible())
1140                         return parse_int ("tracker interval", tracker_interval_value.getSelectedItem().toString(), false);
1141                 return AltosLib.MISSING;
1142         }
1143
1144         public void set_aprs_interval(int new_aprs_interval) {
1145                 if (new_aprs_interval != AltosLib.MISSING)
1146                         aprs_interval_value.setSelectedItem(Integer.toString(new_aprs_interval));
1147                 aprs_interval_value.setVisible(new_aprs_interval != AltosLib.MISSING);
1148                 aprs_interval_label.setVisible(new_aprs_interval != AltosLib.MISSING);
1149                 set_aprs_interval_tool_tip();
1150         }
1151
1152         public int aprs_interval() throws AltosConfigDataException {
1153                 if (aprs_interval_value.isVisible()) {
1154                         String  s = aprs_interval_value.getSelectedItem().toString();
1155
1156                         return parse_int("aprs interval", s, false);
1157                 }
1158                 return AltosLib.MISSING;
1159         }
1160
1161         public void set_aprs_ssid(int new_aprs_ssid) {
1162                 if (new_aprs_ssid != AltosLib.MISSING)
1163                         aprs_ssid_value.setSelectedItem(new_aprs_ssid);
1164                 aprs_ssid_value.setVisible(new_aprs_ssid != AltosLib.MISSING);
1165                 aprs_ssid_label.setVisible(new_aprs_ssid != AltosLib.MISSING);
1166                 set_aprs_ssid_tool_tip();
1167         }
1168
1169         public int aprs_ssid() throws AltosConfigDataException {
1170                 if (aprs_ssid_value.isVisible()) {
1171                         Integer i = (Integer) aprs_ssid_value.getSelectedItem();
1172                         return i;
1173                 }
1174                 return AltosLib.MISSING;
1175         }
1176
1177         public void set_aprs_format(int new_aprs_format) {
1178                 if (new_aprs_format != AltosLib.MISSING)
1179                         aprs_format_value.setSelectedIndex(new_aprs_format);
1180                 aprs_format_value.setVisible(new_aprs_format != AltosLib.MISSING);
1181                 aprs_format_label.setVisible(new_aprs_format != AltosLib.MISSING);
1182                 set_aprs_format_tool_tip();
1183         }
1184
1185         public int aprs_format() throws AltosConfigDataException {
1186                 if (aprs_format_value.isVisible())
1187                         return aprs_format_value.getSelectedIndex();
1188                 return AltosLib.MISSING;
1189         }
1190         public void set_aprs_offset(int new_aprs_offset) {
1191                 if (new_aprs_offset != AltosLib.MISSING)
1192                         aprs_offset_value.setSelectedItem(new_aprs_offset);
1193                 aprs_offset_value.setVisible(new_aprs_offset != AltosLib.MISSING);
1194                 aprs_offset_label.setVisible(new_aprs_offset != AltosLib.MISSING);
1195                 set_aprs_offset_tool_tip();
1196         }
1197
1198         public int aprs_offset() throws AltosConfigDataException {
1199                 if (aprs_offset_value.isVisible()) {
1200                         Integer i = (Integer) aprs_offset_value.getSelectedItem();
1201                         return i;
1202                 }
1203                 return AltosLib.MISSING;
1204         }
1205 }