altoslib, altosui, telegps: Add configuration support for APRS offset
[fw/altos] / altosui / AltosConfigFCUI.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 altosui;
20
21 import java.awt.*;
22 import java.awt.event.*;
23 import javax.swing.*;
24 import javax.swing.event.*;
25 import java.text.*;
26 import org.altusmetrum.altoslib_14.*;
27 import org.altusmetrum.altosuilib_14.*;
28
29 public class AltosConfigFCUI
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                  main_deploy_label;
39         JLabel                  apogee_delay_label;
40         JLabel                  apogee_lockout_label;
41         JLabel                  frequency_label;
42         JLabel                  radio_calibration_label;
43         JLabel                  radio_frequency_label;
44         JLabel                  radio_enable_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                  ignite_mode_label;
52         JLabel                  pad_orientation_label;
53         JLabel                  accel_plus_label;
54         JLabel                  accel_minus_label;
55         JLabel                  callsign_label;
56         JLabel                  beep_label;
57         JLabel                  tracker_motion_label;
58         JLabel                  tracker_interval_label;
59
60         public boolean          dirty;
61
62         JFrame                  owner;
63         JLabel                  product_value;
64         JLabel                  version_value;
65         JLabel                  serial_value;
66         JComboBox<String>       main_deploy_value;
67         JComboBox<String>       apogee_delay_value;
68         JComboBox<String>       apogee_lockout_value;
69         AltosUIFreqList         radio_frequency_value;
70         JLabel                  radio_calibration_value;
71         JRadioButton            radio_enable_value;
72         AltosUIRateList         rate_value;
73         JComboBox<String>       aprs_interval_value;
74         JComboBox<Integer>      aprs_ssid_value;
75         JComboBox<String>       aprs_format_value;
76         JComboBox<Integer>      aprs_offset_value;
77         JComboBox<String>       flight_log_max_value;
78         JComboBox<String>       ignite_mode_value;
79         JComboBox<String>       pad_orientation_value;
80         JTextField              accel_plus_value;
81         JTextField              accel_minus_value;
82         JTextField              callsign_value;
83         JComboBox<String>       beep_value;
84         JComboBox<String>       tracker_motion_value;
85         JComboBox<String>       tracker_interval_value;
86
87         JButton                 pyro;
88         JButton                 accel_cal;
89
90         JButton                 save;
91         JButton                 reset;
92         JButton                 reboot;
93         JButton                 close;
94
95         AltosPyro[]             pyros;
96         double                  pyro_firing_time;
97
98         ActionListener          listener;
99
100         static String[]         main_deploy_values_m = {
101                 "100", "150", "200", "250", "300", "350",
102                 "400", "450", "500"
103         };
104
105         static String[]         main_deploy_values_ft = {
106                 "250", "500", "750", "1000", "1250", "1500",
107                 "1750", "2000"
108         };
109
110         static String[]         apogee_delay_values = {
111                 "0", "1", "2", "3", "4", "5"
112         };
113
114         static String[]         apogee_lockout_values = {
115                 "0", "5", "10", "15", "20"
116         };
117
118         static String[]         ignite_mode_values = {
119                 "Dual Deploy",
120                 "Redundant Apogee",
121                 "Redundant Main",
122         };
123
124         static String[]         aprs_interval_values = {
125                 "Disabled",
126                 "2",
127                 "5",
128                 "10"
129         };
130
131         static Integer[]        aprs_ssid_values = {
132                 0, 1, 2 ,3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
133         };
134
135         static Integer[]        aprs_offset_values = {
136                 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
137         };
138
139         static String[]         beep_values = {
140                 "3750",
141                 "4000",
142                 "4250",
143         };
144
145         static String[]         pad_orientation_values = {
146                 "Antenna Up",
147                 "Antenna Down",
148         };
149
150         static String[]         tracker_motion_values_m = {
151                 "2",
152                 "5",
153                 "10",
154                 "25",
155         };
156
157         static String[]         tracker_motion_values_ft = {
158                 "5",
159                 "20",
160                 "50",
161                 "100"
162         };
163
164         static String[]         tracker_interval_values = {
165                 "1",
166                 "2",
167                 "5",
168                 "10"
169         };
170
171         /* A window listener to catch closing events and tell the config code */
172         class ConfigListener extends WindowAdapter {
173                 AltosConfigFCUI ui;
174
175                 public ConfigListener(AltosConfigFCUI this_ui) {
176                         ui = this_ui;
177                 }
178
179                 public void windowClosing(WindowEvent e) {
180                         ui.actionPerformed(new ActionEvent(e.getSource(),
181                                                            ActionEvent.ACTION_PERFORMED,
182                                                            "Close"));
183                 }
184         }
185
186         boolean is_telemini_v1() {
187                 String  product = product_value.getText();
188                 return product != null && product.startsWith("TeleMini-v1");
189         }
190
191         boolean is_telemini() {
192                 String  product = product_value.getText();
193                 return product != null && product.startsWith("TeleMini");
194         }
195
196         boolean is_easymini() {
197                 String  product = product_value.getText();
198                 return product != null && product.startsWith("EasyMini");
199         }
200
201         boolean is_telemetrum() {
202                 String  product = product_value.getText();
203                 return product != null && product.startsWith("TeleMetrum");
204         }
205
206         void set_radio_enable_tool_tip() {
207                 if (radio_enable_value.isVisible())
208                         radio_enable_value.setToolTipText("Enable/Disable telemetry and RDF transmissions");
209                 else
210                         radio_enable_value.setToolTipText("Firmware version does not support disabling radio");
211         }
212
213         void set_rate_tool_tip() {
214                 if (rate_value.isVisible())
215                         rate_value.setToolTipText("Select telemetry baud rate");
216                 else
217                         rate_value.setToolTipText("Firmware version does not support variable telemetry rates");
218         }
219
220         void set_aprs_interval_tool_tip() {
221                 if (aprs_interval_value.isVisible())
222                         aprs_interval_value.setToolTipText("Enable APRS and set the interval between APRS reports");
223                 else
224                         aprs_interval_value.setToolTipText("Hardware doesn't support APRS");
225         }
226
227         void set_aprs_ssid_tool_tip() {
228                 if (aprs_ssid_value.isVisible())
229                         aprs_ssid_value.setToolTipText("Set the APRS SSID (secondary station identifier)");
230                 else if (aprs_ssid_value.isVisible())
231                         aprs_ssid_value.setToolTipText("Software version doesn't support setting the APRS SSID");
232                 else
233                         aprs_ssid_value.setToolTipText("Hardware doesn't support APRS");
234         }
235
236         void set_aprs_format_tool_tip() {
237                 if (aprs_format_value.isVisible())
238                         aprs_format_value.setToolTipText("Set the APRS format (compressed/uncompressed)");
239                 else if (aprs_format_value.isVisible())
240                         aprs_format_value.setToolTipText("Software version doesn't support setting the APRS format");
241                 else
242                         aprs_format_value.setToolTipText("Hardware doesn't support APRS");
243         }
244
245         void set_aprs_offset_tool_tip() {
246                 if (aprs_offset_value.isVisible())
247                         aprs_offset_value.setToolTipText("Set the APRS offset from top of minute");
248                 else if (aprs_offset_value.isVisible())
249                         aprs_offset_value.setToolTipText("Software version doesn't support setting the APRS offset");
250                 else
251                         aprs_offset_value.setToolTipText("Hardware doesn't support APRS");
252         }
253
254         void set_flight_log_max_tool_tip() {
255                 if (flight_log_max_value.isVisible())
256                         flight_log_max_value.setToolTipText("Size reserved for each flight log (in kB)");
257                 else {
258                         if (is_telemini_v1())
259                                 flight_log_max_value.setToolTipText("TeleMini-v1 stores only one flight");
260                         else
261                                 flight_log_max_value.setToolTipText("Cannot set max value with flight logs in memory");
262                 }
263         }
264
265         void set_ignite_mode_tool_tip() {
266                 if (ignite_mode_value.isVisible())
267                         ignite_mode_value.setToolTipText("Select when igniters will be fired");
268                 else
269                         ignite_mode_value.setToolTipText("Older firmware could not select ignite mode");
270         }
271
272         void set_pad_orientation_tool_tip() {
273                 if (pad_orientation_value.isVisible()) {
274                         pad_orientation_value.setToolTipText("How will the computer be mounted in the airframe");
275                 } else {
276                         if (is_telemetrum())
277                                 pad_orientation_value.setToolTipText("Older TeleMetrum firmware must fly antenna forward");
278                         else if (is_telemini() || is_easymini())
279                                 pad_orientation_value.setToolTipText("TeleMini and EasyMini don't care how they are mounted");
280                         else
281                                 pad_orientation_value.setToolTipText("Can't select orientation");
282                 }
283         }
284
285         void set_accel_tool_tips() {
286                 if (accel_plus_value.isVisible()) {
287                         accel_plus_value.setToolTipText("Pad acceleration value in flight orientation");
288                         accel_minus_value.setToolTipText("Upside-down acceleration value");
289                 } else {
290                         accel_plus_value.setToolTipText("No accelerometer");
291                         accel_minus_value.setToolTipText("No accelerometer");
292                 }
293         }
294
295         void set_beep_tool_tip() {
296                 if (beep_value.isVisible())
297                         beep_value.setToolTipText("What frequency the beeper will sound at");
298                 else
299                         beep_value.setToolTipText("Older firmware could not select beeper frequency");
300         }
301
302         /* Build the UI using a grid bag */
303         public AltosConfigFCUI(JFrame in_owner, boolean remote) {
304                 super (in_owner, "Configure Flight Computer", false);
305
306                 owner = in_owner;
307                 GridBagConstraints c;
308                 int row = 0;
309
310                 Insets il = new Insets(4,4,4,4);
311                 Insets ir = new Insets(4,4,4,4);
312
313                 pane = getContentPane();
314                 pane.setLayout(new GridBagLayout());
315
316                 /* Product */
317                 c = new GridBagConstraints();
318                 c.gridx = 0; c.gridy = row;
319                 c.gridwidth = 4;
320                 c.fill = GridBagConstraints.NONE;
321                 c.anchor = GridBagConstraints.LINE_START;
322                 c.insets = il;
323                 product_label = new JLabel("Product:");
324                 pane.add(product_label, c);
325
326                 c = new GridBagConstraints();
327                 c.gridx = 4; c.gridy = row;
328                 c.gridwidth = 4;
329                 c.fill = GridBagConstraints.HORIZONTAL;
330                 c.weightx = 1;
331                 c.anchor = GridBagConstraints.LINE_START;
332                 c.insets = ir;
333                 product_value = new JLabel("");
334                 pane.add(product_value, c);
335                 row++;
336
337                 /* Version */
338                 c = new GridBagConstraints();
339                 c.gridx = 0; c.gridy = row;
340                 c.gridwidth = 4;
341                 c.fill = GridBagConstraints.NONE;
342                 c.anchor = GridBagConstraints.LINE_START;
343                 c.insets = il;
344                 c.ipady = 5;
345                 version_label = new JLabel("Software version:");
346                 pane.add(version_label, c);
347
348                 c = new GridBagConstraints();
349                 c.gridx = 4; c.gridy = row;
350                 c.gridwidth = 4;
351                 c.fill = GridBagConstraints.HORIZONTAL;
352                 c.weightx = 1;
353                 c.anchor = GridBagConstraints.LINE_START;
354                 c.insets = ir;
355                 c.ipady = 5;
356                 version_value = new JLabel("");
357                 pane.add(version_value, c);
358                 row++;
359
360                 /* Serial */
361                 c = new GridBagConstraints();
362                 c.gridx = 0; c.gridy = row;
363                 c.gridwidth = 4;
364                 c.fill = GridBagConstraints.NONE;
365                 c.anchor = GridBagConstraints.LINE_START;
366                 c.insets = il;
367                 c.ipady = 5;
368                 serial_label = new JLabel("Serial:");
369                 pane.add(serial_label, c);
370
371                 c = new GridBagConstraints();
372                 c.gridx = 4; c.gridy = row;
373                 c.gridwidth = 4;
374                 c.fill = GridBagConstraints.HORIZONTAL;
375                 c.weightx = 1;
376                 c.anchor = GridBagConstraints.LINE_START;
377                 c.insets = ir;
378                 c.ipady = 5;
379                 serial_value = new JLabel("");
380                 pane.add(serial_value, c);
381                 row++;
382
383                 /* Main deploy */
384                 c = new GridBagConstraints();
385                 c.gridx = 0; c.gridy = row;
386                 c.gridwidth = 4;
387                 c.fill = GridBagConstraints.NONE;
388                 c.anchor = GridBagConstraints.LINE_START;
389                 c.insets = il;
390                 c.ipady = 5;
391                 main_deploy_label = new JLabel(get_main_deploy_label());
392                 pane.add(main_deploy_label, c);
393
394                 c = new GridBagConstraints();
395                 c.gridx = 4; c.gridy = row;
396                 c.gridwidth = 4;
397                 c.fill = GridBagConstraints.HORIZONTAL;
398                 c.weightx = 1;
399                 c.anchor = GridBagConstraints.LINE_START;
400                 c.insets = ir;
401                 c.ipady = 5;
402                 main_deploy_value = new JComboBox<String>(main_deploy_values());
403                 main_deploy_value.setEditable(true);
404                 main_deploy_value.addItemListener(this);
405                 pane.add(main_deploy_value, c);
406                 main_deploy_value.setToolTipText("Height above pad altitude to fire main charge");
407                 row++;
408
409                 /* Apogee delay */
410                 c = new GridBagConstraints();
411                 c.gridx = 0; c.gridy = row;
412                 c.gridwidth = 4;
413                 c.fill = GridBagConstraints.NONE;
414                 c.anchor = GridBagConstraints.LINE_START;
415                 c.insets = il;
416                 c.ipady = 5;
417                 apogee_delay_label = new JLabel("Apogee Delay(s):");
418                 pane.add(apogee_delay_label, c);
419
420                 c = new GridBagConstraints();
421                 c.gridx = 4; c.gridy = row;
422                 c.gridwidth = 4;
423                 c.fill = GridBagConstraints.HORIZONTAL;
424                 c.weightx = 1;
425                 c.anchor = GridBagConstraints.LINE_START;
426                 c.insets = ir;
427                 c.ipady = 5;
428                 apogee_delay_value = new JComboBox<String>(apogee_delay_values);
429                 apogee_delay_value.setEditable(true);
430                 apogee_delay_value.addItemListener(this);
431                 pane.add(apogee_delay_value, c);
432                 apogee_delay_value.setToolTipText("Delay after apogee before charge fires");
433                 row++;
434
435                 /* Apogee lockout */
436                 c = new GridBagConstraints();
437                 c.gridx = 0; c.gridy = row;
438                 c.gridwidth = 4;
439                 c.fill = GridBagConstraints.NONE;
440                 c.anchor = GridBagConstraints.LINE_START;
441                 c.insets = il;
442                 c.ipady = 5;
443                 apogee_lockout_label = new JLabel("Apogee Lockout(s):");
444                 pane.add(apogee_lockout_label, c);
445
446                 c = new GridBagConstraints();
447                 c.gridx = 4; c.gridy = row;
448                 c.gridwidth = 4;
449                 c.fill = GridBagConstraints.HORIZONTAL;
450                 c.weightx = 1;
451                 c.anchor = GridBagConstraints.LINE_START;
452                 c.insets = ir;
453                 c.ipady = 5;
454                 apogee_lockout_value = new JComboBox<String>(apogee_lockout_values);
455                 apogee_lockout_value.setEditable(true);
456                 apogee_lockout_value.addItemListener(this);
457                 pane.add(apogee_lockout_value, c);
458                 apogee_lockout_value.setToolTipText("Time after launch while apogee detection is locked out");
459                 row++;
460
461                 /* Frequency */
462                 c = new GridBagConstraints();
463                 c.gridx = 0; c.gridy = row;
464                 c.gridwidth = 4;
465                 c.fill = GridBagConstraints.NONE;
466                 c.anchor = GridBagConstraints.LINE_START;
467                 c.insets = il;
468                 c.ipady = 5;
469                 radio_frequency_label = new JLabel("Frequency:");
470                 pane.add(radio_frequency_label, c);
471
472                 c = new GridBagConstraints();
473                 c.gridx = 4; c.gridy = row;
474                 c.gridwidth = 4;
475                 c.fill = GridBagConstraints.HORIZONTAL;
476                 c.weightx = 1;
477                 c.anchor = GridBagConstraints.LINE_START;
478                 c.insets = ir;
479                 c.ipady = 5;
480                 radio_frequency_value = new AltosUIFreqList();
481                 radio_frequency_value.addItemListener(this);
482                 pane.add(radio_frequency_value, c);
483                 radio_frequency_value.setToolTipText("Telemetry, RDF and packet frequency");
484                 row++;
485
486                 /* Radio Calibration */
487                 c = new GridBagConstraints();
488                 c.gridx = 0; c.gridy = row;
489                 c.gridwidth = 4;
490                 c.fill = GridBagConstraints.NONE;
491                 c.anchor = GridBagConstraints.LINE_START;
492                 c.insets = il;
493                 c.ipady = 5;
494                 radio_calibration_label = new JLabel("RF Calibration:");
495                 pane.add(radio_calibration_label, c);
496
497                 c = new GridBagConstraints();
498                 c.gridx = 4; c.gridy = row;
499                 c.gridwidth = 4;
500                 c.fill = GridBagConstraints.HORIZONTAL;
501                 c.weightx = 1;
502                 c.anchor = GridBagConstraints.LINE_START;
503                 c.insets = ir;
504                 c.ipady = 5;
505                 radio_calibration_value = new JLabel(String.format("%d", 1186611));
506                 pane.add(radio_calibration_value, c);
507                 row++;
508
509                 /* Radio Enable */
510                 c = new GridBagConstraints();
511                 c.gridx = 0; c.gridy = row;
512                 c.gridwidth = 4;
513                 c.fill = GridBagConstraints.NONE;
514                 c.anchor = GridBagConstraints.LINE_START;
515                 c.insets = il;
516                 c.ipady = 5;
517                 radio_enable_label = new JLabel("Telemetry/RDF/APRS Enable:");
518                 pane.add(radio_enable_label, c);
519
520                 c = new GridBagConstraints();
521                 c.gridx = 4; c.gridy = row;
522                 c.gridwidth = 4;
523                 c.fill = GridBagConstraints.HORIZONTAL;
524                 c.weightx = 1;
525                 c.anchor = GridBagConstraints.LINE_START;
526                 c.insets = ir;
527                 c.ipady = 5;
528                 radio_enable_value = new JRadioButton("Enabled");
529                 radio_enable_value.addItemListener(this);
530                 pane.add(radio_enable_value, c);
531                 set_radio_enable_tool_tip();
532                 row++;
533
534                 /* Telemetry Rate */
535                 c = new GridBagConstraints();
536                 c.gridx = 0; c.gridy = row;
537                 c.gridwidth = 4;
538                 c.fill = GridBagConstraints.NONE;
539                 c.anchor = GridBagConstraints.LINE_START;
540                 c.insets = il;
541                 c.ipady = 5;
542                 rate_label = new JLabel("Telemetry baud rate:");
543                 pane.add(rate_label, c);
544
545                 c = new GridBagConstraints();
546                 c.gridx = 4; c.gridy = row;
547                 c.gridwidth = 4;
548                 c.fill = GridBagConstraints.HORIZONTAL;
549                 c.weightx = 1;
550                 c.anchor = GridBagConstraints.LINE_START;
551                 c.insets = ir;
552                 c.ipady = 5;
553                 rate_value = new AltosUIRateList();
554                 rate_value.addItemListener(this);
555                 pane.add(rate_value, c);
556                 set_rate_tool_tip();
557                 row++;
558
559                 /* APRS interval */
560                 c = new GridBagConstraints();
561                 c.gridx = 0; c.gridy = row;
562                 c.gridwidth = 4;
563                 c.fill = GridBagConstraints.NONE;
564                 c.anchor = GridBagConstraints.LINE_START;
565                 c.insets = il;
566                 c.ipady = 5;
567                 aprs_interval_label = new JLabel("APRS Interval(s):");
568                 pane.add(aprs_interval_label, c);
569
570                 c = new GridBagConstraints();
571                 c.gridx = 4; c.gridy = row;
572                 c.gridwidth = 4;
573                 c.fill = GridBagConstraints.HORIZONTAL;
574                 c.weightx = 1;
575                 c.anchor = GridBagConstraints.LINE_START;
576                 c.insets = ir;
577                 c.ipady = 5;
578                 aprs_interval_value = new JComboBox<String>(aprs_interval_values);
579                 aprs_interval_value.setEditable(true);
580                 aprs_interval_value.addItemListener(this);
581                 pane.add(aprs_interval_value, c);
582                 set_aprs_interval_tool_tip();
583                 row++;
584
585                 /* APRS SSID */
586                 c = new GridBagConstraints();
587                 c.gridx = 0; c.gridy = row;
588                 c.gridwidth = 4;
589                 c.fill = GridBagConstraints.NONE;
590                 c.anchor = GridBagConstraints.LINE_START;
591                 c.insets = il;
592                 c.ipady = 5;
593                 aprs_ssid_label = new JLabel("APRS SSID:");
594                 pane.add(aprs_ssid_label, c);
595
596                 c = new GridBagConstraints();
597                 c.gridx = 4; c.gridy = row;
598                 c.gridwidth = 4;
599                 c.fill = GridBagConstraints.HORIZONTAL;
600                 c.weightx = 1;
601                 c.anchor = GridBagConstraints.LINE_START;
602                 c.insets = ir;
603                 c.ipady = 5;
604                 aprs_ssid_value = new JComboBox<Integer>(aprs_ssid_values);
605                 aprs_ssid_value.setEditable(false);
606                 aprs_ssid_value.addItemListener(this);
607                 aprs_ssid_value.setMaximumRowCount(aprs_ssid_values.length);
608                 pane.add(aprs_ssid_value, c);
609                 set_aprs_ssid_tool_tip();
610                 row++;
611
612                 /* APRS format */
613                 c = new GridBagConstraints();
614                 c.gridx = 0; c.gridy = row;
615                 c.gridwidth = 4;
616                 c.fill = GridBagConstraints.NONE;
617                 c.anchor = GridBagConstraints.LINE_START;
618                 c.insets = il;
619                 c.ipady = 5;
620                 aprs_format_label = new JLabel("APRS format:");
621                 pane.add(aprs_format_label, c);
622
623                 c = new GridBagConstraints();
624                 c.gridx = 4; c.gridy = row;
625                 c.gridwidth = 4;
626                 c.fill = GridBagConstraints.HORIZONTAL;
627                 c.weightx = 1;
628                 c.anchor = GridBagConstraints.LINE_START;
629                 c.insets = ir;
630                 c.ipady = 5;
631                 aprs_format_value = new JComboBox<String>(AltosLib.ao_aprs_format_name);
632                 aprs_format_value.setEditable(false);
633                 aprs_format_value.addItemListener(this);
634                 aprs_format_value.setMaximumRowCount(AltosLib.ao_aprs_format_name.length);
635                 pane.add(aprs_format_value, c);
636                 set_aprs_format_tool_tip();
637                 row++;
638
639                 /* APRS offset */
640                 c = new GridBagConstraints();
641                 c.gridx = 0; c.gridy = row;
642                 c.gridwidth = 4;
643                 c.fill = GridBagConstraints.NONE;
644                 c.anchor = GridBagConstraints.LINE_START;
645                 c.insets = il;
646                 c.ipady = 5;
647                 aprs_offset_label = new JLabel("APRS offset:");
648                 pane.add(aprs_offset_label, c);
649
650                 c = new GridBagConstraints();
651                 c.gridx = 4; c.gridy = row;
652                 c.gridwidth = 4;
653                 c.fill = GridBagConstraints.HORIZONTAL;
654                 c.weightx = 1;
655                 c.anchor = GridBagConstraints.LINE_START;
656                 c.insets = ir;
657                 c.ipady = 5;
658                 aprs_offset_value = new JComboBox<Integer>(aprs_offset_values);
659                 aprs_offset_value.setEditable(false);
660                 aprs_offset_value.addItemListener(this);
661                 aprs_offset_value.setMaximumRowCount(aprs_offset_values.length);
662                 pane.add(aprs_offset_value, c);
663                 set_aprs_offset_tool_tip();
664                 row++;
665
666                 /* Callsign */
667                 c = new GridBagConstraints();
668                 c.gridx = 0; c.gridy = row;
669                 c.gridwidth = 4;
670                 c.fill = GridBagConstraints.NONE;
671                 c.anchor = GridBagConstraints.LINE_START;
672                 c.insets = il;
673                 c.ipady = 5;
674                 callsign_label = new JLabel("Callsign:");
675                 pane.add(callsign_label, c);
676
677                 c = new GridBagConstraints();
678                 c.gridx = 4; c.gridy = row;
679                 c.gridwidth = 4;
680                 c.fill = GridBagConstraints.HORIZONTAL;
681                 c.weightx = 1;
682                 c.anchor = GridBagConstraints.LINE_START;
683                 c.insets = ir;
684                 c.ipady = 5;
685                 callsign_value = new JTextField(AltosUIPreferences.callsign());
686                 callsign_value.getDocument().addDocumentListener(this);
687                 pane.add(callsign_value, c);
688                 callsign_value.setToolTipText("Callsign reported in telemetry data");
689                 row++;
690
691                 /* Flight log max */
692                 c = new GridBagConstraints();
693                 c.gridx = 0; c.gridy = row;
694                 c.gridwidth = 4;
695                 c.fill = GridBagConstraints.NONE;
696                 c.anchor = GridBagConstraints.LINE_START;
697                 c.insets = il;
698                 c.ipady = 5;
699                 flight_log_max_label = new JLabel("Maximum Flight Log Size (kB):");
700                 pane.add(flight_log_max_label, c);
701
702                 c = new GridBagConstraints();
703                 c.gridx = 4; c.gridy = row;
704                 c.gridwidth = 4;
705                 c.fill = GridBagConstraints.HORIZONTAL;
706                 c.weightx = 1;
707                 c.anchor = GridBagConstraints.LINE_START;
708                 c.insets = ir;
709                 c.ipady = 5;
710                 flight_log_max_value = new JComboBox<String>();
711                 flight_log_max_value.setEditable(true);
712                 flight_log_max_value.addItemListener(this);
713                 pane.add(flight_log_max_value, c);
714                 set_flight_log_max_tool_tip();
715                 row++;
716
717                 /* Ignite mode */
718                 c = new GridBagConstraints();
719                 c.gridx = 0; c.gridy = row;
720                 c.gridwidth = 4;
721                 c.fill = GridBagConstraints.NONE;
722                 c.anchor = GridBagConstraints.LINE_START;
723                 c.insets = il;
724                 c.ipady = 5;
725                 ignite_mode_label = new JLabel("Igniter Firing Mode:");
726                 pane.add(ignite_mode_label, c);
727
728                 c = new GridBagConstraints();
729                 c.gridx = 4; c.gridy = row;
730                 c.gridwidth = 4;
731                 c.fill = GridBagConstraints.HORIZONTAL;
732                 c.weightx = 1;
733                 c.anchor = GridBagConstraints.LINE_START;
734                 c.insets = ir;
735                 c.ipady = 5;
736                 ignite_mode_value = new JComboBox<String>(ignite_mode_values);
737                 ignite_mode_value.setEditable(false);
738                 ignite_mode_value.addItemListener(this);
739                 pane.add(ignite_mode_value, c);
740                 set_ignite_mode_tool_tip();
741                 row++;
742
743                 /* Pad orientation */
744                 c = new GridBagConstraints();
745                 c.gridx = 0; c.gridy = row;
746                 c.gridwidth = 4;
747                 c.fill = GridBagConstraints.NONE;
748                 c.anchor = GridBagConstraints.LINE_START;
749                 c.insets = il;
750                 c.ipady = 5;
751                 pad_orientation_label = new JLabel("Pad Orientation:");
752                 pane.add(pad_orientation_label, c);
753
754                 c = new GridBagConstraints();
755                 c.gridx = 4; c.gridy = row;
756                 c.gridwidth = 4;
757                 c.fill = GridBagConstraints.HORIZONTAL;
758                 c.weightx = 1;
759                 c.anchor = GridBagConstraints.LINE_START;
760                 c.insets = ir;
761                 c.ipady = 5;
762                 pad_orientation_value = new JComboBox<String>(pad_orientation_values);
763                 pad_orientation_value.setEditable(false);
764                 pad_orientation_value.addItemListener(this);
765                 pane.add(pad_orientation_value, c);
766                 set_pad_orientation_tool_tip();
767                 row++;
768
769                 /* Accel plus */
770                 c = new GridBagConstraints();
771                 c.gridx = 0; c.gridy = row;
772                 c.gridwidth = 4;
773                 c.fill = GridBagConstraints.NONE;
774                 c.anchor = GridBagConstraints.LINE_START;
775                 c.insets = il;
776                 c.ipady = 5;
777                 accel_plus_label = new JLabel("Accel Plus:");
778                 pane.add(accel_plus_label, c);
779
780                 c = new GridBagConstraints();
781                 c.gridx = 4; c.gridy = row;
782                 c.gridwidth = 4;
783                 c.fill = GridBagConstraints.HORIZONTAL;
784                 c.weightx = 1;
785                 c.anchor = GridBagConstraints.LINE_START;
786                 c.insets = ir;
787                 c.ipady = 5;
788                 accel_plus_value = new JTextField(10);
789                 accel_plus_value.setEditable(true);
790                 accel_plus_value.getDocument().addDocumentListener(this);
791                 pane.add(accel_plus_value, c);
792                 row++;
793
794                 /* Accel minus */
795                 c = new GridBagConstraints();
796                 c.gridx = 0; c.gridy = row;
797                 c.gridwidth = 4;
798                 c.fill = GridBagConstraints.NONE;
799                 c.anchor = GridBagConstraints.LINE_START;
800                 c.insets = il;
801                 c.ipady = 5;
802                 accel_minus_label = new JLabel("Accel Minus:");
803                 pane.add(accel_minus_label, c);
804
805                 c = new GridBagConstraints();
806                 c.gridx = 4; c.gridy = row;
807                 c.gridwidth = 4;
808                 c.fill = GridBagConstraints.HORIZONTAL;
809                 c.weightx = 1;
810                 c.anchor = GridBagConstraints.LINE_START;
811                 c.insets = ir;
812                 c.ipady = 5;
813                 accel_minus_value = new JTextField(10);
814                 accel_minus_value.setEditable(true);
815                 accel_minus_value.getDocument().addDocumentListener(this);
816                 pane.add(accel_minus_value, c);
817                 row++;
818                 set_accel_tool_tips();
819
820                 /* Beeper */
821                 c = new GridBagConstraints();
822                 c.gridx = 0; c.gridy = row;
823                 c.gridwidth = 4;
824                 c.fill = GridBagConstraints.NONE;
825                 c.anchor = GridBagConstraints.LINE_START;
826                 c.insets = il;
827                 c.ipady = 5;
828                 beep_label = new JLabel("Beeper Frequency:");
829                 pane.add(beep_label, c);
830
831                 c = new GridBagConstraints();
832                 c.gridx = 4; c.gridy = row;
833                 c.gridwidth = 4;
834                 c.fill = GridBagConstraints.HORIZONTAL;
835                 c.weightx = 1;
836                 c.anchor = GridBagConstraints.LINE_START;
837                 c.insets = ir;
838                 c.ipady = 5;
839                 beep_value = new JComboBox<String>(beep_values);
840                 beep_value.setEditable(true);
841                 beep_value.addItemListener(this);
842                 pane.add(beep_value, c);
843                 set_beep_tool_tip();
844                 row++;
845
846                 /* Tracker triger horiz distances */
847                 c = new GridBagConstraints();
848                 c.gridx = 0; c.gridy = row;
849                 c.gridwidth = 4;
850                 c.fill = GridBagConstraints.NONE;
851                 c.anchor = GridBagConstraints.LINE_START;
852                 c.insets = il;
853                 c.ipady = 5;
854                 tracker_motion_label = new JLabel(get_tracker_motion_label());
855                 pane.add(tracker_motion_label, c);
856
857                 c = new GridBagConstraints();
858                 c.gridx = 4; c.gridy = row;
859                 c.gridwidth = 4;
860                 c.fill = GridBagConstraints.HORIZONTAL;
861                 c.weightx = 1;
862                 c.anchor = GridBagConstraints.LINE_START;
863                 c.insets = ir;
864                 c.ipady = 5;
865                 tracker_motion_value = new JComboBox<String>(tracker_motion_values());
866                 tracker_motion_value.setEditable(true);
867                 tracker_motion_value.addItemListener(this);
868                 pane.add(tracker_motion_value, c);
869                 row++;
870
871                 /* Tracker triger vert distances */
872                 c = new GridBagConstraints();
873                 c.gridx = 0; c.gridy = row;
874                 c.gridwidth = 4;
875                 c.fill = GridBagConstraints.NONE;
876                 c.anchor = GridBagConstraints.LINE_START;
877                 c.insets = il;
878                 c.ipady = 5;
879                 tracker_interval_label = new JLabel("Position Reporting Interval(s):");
880                 pane.add(tracker_interval_label, c);
881
882                 c = new GridBagConstraints();
883                 c.gridx = 4; c.gridy = row;
884                 c.gridwidth = 4;
885                 c.fill = GridBagConstraints.HORIZONTAL;
886                 c.weightx = 1;
887                 c.anchor = GridBagConstraints.LINE_START;
888                 c.insets = ir;
889                 c.ipady = 5;
890                 tracker_interval_value = new JComboBox<String>(tracker_interval_values);
891                 tracker_interval_value.setEditable(true);
892                 tracker_interval_value.addItemListener(this);
893                 pane.add(tracker_interval_value, c);
894                 set_tracker_tool_tip();
895                 row++;
896
897                 /* Pyro channels */
898                 c = new GridBagConstraints();
899                 c.gridx = 4; c.gridy = row;
900                 c.gridwidth = 4;
901                 c.fill = GridBagConstraints.HORIZONTAL;
902                 c.anchor = GridBagConstraints.LINE_START;
903                 c.insets = il;
904                 c.ipady = 5;
905                 pyro = new JButton("Configure Pyro Channels");
906                 pane.add(pyro, c);
907                 pyro.addActionListener(this);
908                 pyro.setActionCommand("Pyro");
909                 row++;
910
911                 /* Accel cal */
912                 c = new GridBagConstraints();
913                 c.gridx = 5; c.gridy = row;
914                 c.gridwidth = 5;
915                 c.fill = GridBagConstraints.HORIZONTAL;
916                 c.anchor = GridBagConstraints.LINE_START;
917                 c.insets = il;
918                 c.ipady = 5;
919                 accel_cal = new JButton("Calibrate Accelerometer");
920                 pane.add(accel_cal, c);
921                 accel_cal.addActionListener(this);
922                 accel_cal.setActionCommand("Accel");
923                 row++;
924
925                 /* Buttons */
926                 c = new GridBagConstraints();
927                 c.gridx = 0; c.gridy = row;
928                 c.gridwidth = 2;
929                 c.fill = GridBagConstraints.NONE;
930                 c.anchor = GridBagConstraints.LINE_START;
931                 c.insets = il;
932                 save = new JButton("Save");
933                 pane.add(save, c);
934                 save.addActionListener(this);
935                 save.setActionCommand("Save");
936
937                 c = new GridBagConstraints();
938                 c.gridx = 2; c.gridy = row;
939                 c.gridwidth = 2;
940                 c.fill = GridBagConstraints.NONE;
941                 c.anchor = GridBagConstraints.CENTER;
942                 c.insets = il;
943                 reset = new JButton("Reset");
944                 pane.add(reset, c);
945                 reset.addActionListener(this);
946                 reset.setActionCommand("Reset");
947
948                 c = new GridBagConstraints();
949                 c.gridx = 4; c.gridy = row;
950                 c.gridwidth = 2;
951                 c.fill = GridBagConstraints.NONE;
952                 c.anchor = GridBagConstraints.CENTER;
953                 c.insets = il;
954                 reboot = new JButton("Reboot");
955                 pane.add(reboot, c);
956                 reboot.addActionListener(this);
957                 reboot.setActionCommand("Reboot");
958
959                 c = new GridBagConstraints();
960                 c.gridx = 6; c.gridy = row;
961                 c.gridwidth = 2;
962                 c.fill = GridBagConstraints.NONE;
963                 c.anchor = GridBagConstraints.LINE_END;
964                 c.insets = il;
965                 close = new JButton("Close");
966                 pane.add(close, c);
967                 close.addActionListener(this);
968                 close.setActionCommand("Close");
969
970                 addWindowListener(new ConfigListener(this));
971                 AltosPreferences.register_units_listener(this);
972         }
973
974         /* Once the initial values are set, the config code will show the dialog */
975         public void make_visible() {
976                 pack();
977                 setLocationRelativeTo(owner);
978                 setVisible(true);
979         }
980
981         /* If any values have been changed, confirm before closing */
982         public boolean check_dirty(String operation) {
983                 if (dirty) {
984                         Object[] options = { String.format("%s anyway", operation), "Keep editing" };
985                         int i;
986                         i = JOptionPane.showOptionDialog(this,
987                                                          String.format("Configuration modified. %s anyway?", operation),
988                                                          "Configuration Modified",
989                                                          JOptionPane.DEFAULT_OPTION,
990                                                          JOptionPane.WARNING_MESSAGE,
991                                                          null, options, options[1]);
992                         if (i != 0)
993                                 return false;
994                 }
995                 return true;
996         }
997
998         public void set_dirty() {
999                 dirty = true;
1000                 save.setEnabled(true);
1001         }
1002
1003         public void set_clean() {
1004                 dirty = false;
1005                 save.setEnabled(false);
1006         }
1007
1008         AltosConfigPyroUI       pyro_ui;
1009
1010         public void dispose() {
1011                 if (pyro_ui != null)
1012                         pyro_ui.dispose();
1013                 AltosPreferences.unregister_units_listener(this);
1014                 super.dispose();
1015         }
1016
1017         /* Listen for events from our buttons */
1018         public void actionPerformed(ActionEvent e) {
1019                 String  cmd = e.getActionCommand();
1020
1021                 if (cmd.equals("Pyro")) {
1022                         if (pyro_ui == null && pyros != null)
1023                                 pyro_ui = new AltosConfigPyroUI(this, pyros, pyro_firing_time);
1024                         if (pyro_ui != null)
1025                                 pyro_ui.make_visible();
1026                         return;
1027                 }
1028
1029                 if (cmd.equals("Close") || cmd.equals("Reboot"))
1030                         if (!check_dirty(cmd))
1031                                 return;
1032                 listener.actionPerformed(e);
1033                 if (cmd.equals("Close") || cmd.equals("Reboot")) {
1034                         setVisible(false);
1035                         dispose();
1036                 }
1037                 if (cmd.equals("Save") || cmd.equals("Reset"))
1038                         set_clean();
1039         }
1040
1041         /* ItemListener interface method */
1042         public void itemStateChanged(ItemEvent e) {
1043                 set_dirty();
1044         }
1045
1046         /* DocumentListener interface methods */
1047         public void changedUpdate(DocumentEvent e) {
1048                 set_dirty();
1049         }
1050
1051         public void insertUpdate(DocumentEvent e) {
1052                 set_dirty();
1053         }
1054
1055         public void removeUpdate(DocumentEvent e) {
1056                 set_dirty();
1057         }
1058
1059         /* Let the config code hook on a listener */
1060         public void addActionListener(ActionListener l) {
1061                 listener = l;
1062         }
1063
1064         /* set and get all of the dialog values */
1065         public void set_product(String product) {
1066                 radio_frequency_value.set_product(product);
1067                 product_value.setText(product);
1068                 set_pad_orientation_tool_tip();
1069                 set_accel_tool_tips();
1070                 set_flight_log_max_tool_tip();
1071         }
1072
1073         public void set_version(String version) {
1074                 version_value.setText(version);
1075         }
1076
1077         public void set_serial(int serial) {
1078                 radio_frequency_value.set_serial(serial);
1079                 serial_value.setText(String.format("%d", serial));
1080         }
1081
1082         public void set_altitude_32(int altitude_32) {
1083         }
1084
1085         public void set_main_deploy(int new_main_deploy) {
1086                 if (new_main_deploy != AltosLib.MISSING)
1087                         main_deploy_value.setSelectedItem(AltosConvert.height.say(new_main_deploy));
1088                 main_deploy_value.setVisible(new_main_deploy != AltosLib.MISSING);
1089                 main_deploy_label.setVisible(new_main_deploy != AltosLib.MISSING);
1090         }
1091
1092         public int main_deploy() throws AltosConfigDataException {
1093                 String  str = main_deploy_value.getSelectedItem().toString();
1094                 try {
1095                         return (int) (AltosConvert.height.parse_locale(str) + 0.5);
1096                 } catch (ParseException pe) {
1097                         throw new AltosConfigDataException("invalid main deploy height %s", str);
1098                 }
1099         }
1100
1101         String get_main_deploy_label() {
1102                 return String.format("Main Deploy Altitude(%s):", AltosConvert.height.parse_units());
1103         }
1104
1105         String[] main_deploy_values() {
1106                 if (AltosConvert.imperial_units)
1107                         return main_deploy_values_ft;
1108                 else
1109                         return main_deploy_values_m;
1110         }
1111
1112         void set_main_deploy_values() {
1113                 String[]        v = main_deploy_values();
1114                 while (main_deploy_value.getItemCount() > 0)
1115                         main_deploy_value.removeItemAt(0);
1116                 for (int i = 0; i < v.length; i++)
1117                         main_deploy_value.addItem(v[i]);
1118                 main_deploy_value.setMaximumRowCount(v.length);
1119         }
1120
1121         public void units_changed(boolean imperial_units) {
1122                 boolean was_dirty = dirty;
1123
1124                 String v = main_deploy_value.getSelectedItem().toString();
1125                 main_deploy_label.setText(get_main_deploy_label());
1126                 set_main_deploy_values();
1127                 try {
1128                         int m = (int) (AltosConvert.height.parse_locale(v, !imperial_units) + 0.5);
1129                         set_main_deploy(m);
1130                 } catch (ParseException pe) {
1131                 }
1132
1133                 if (tracker_motion_value.isVisible()) {
1134                         String motion = tracker_motion_value.getSelectedItem().toString();
1135                         tracker_motion_label.setText(get_tracker_motion_label());
1136                         set_tracker_motion_values();
1137                         try {
1138                                 int m = (int) (AltosConvert.height.parse_locale(motion, !imperial_units) + 0.5);
1139                                 set_tracker_motion(m);
1140                         } catch (ParseException pe) {
1141                         }
1142                 }
1143
1144                 if (!was_dirty)
1145                         set_clean();
1146         }
1147
1148         public void set_apogee_delay(int new_apogee_delay) {
1149                 if (new_apogee_delay != AltosLib.MISSING)
1150                         apogee_delay_value.setSelectedItem(Integer.toString(new_apogee_delay));
1151                 apogee_delay_value.setVisible(new_apogee_delay != AltosLib.MISSING);
1152                 apogee_delay_label.setVisible(new_apogee_delay != AltosLib.MISSING);
1153         }
1154
1155         private int parse_int(String name, String s, boolean split) throws AltosConfigDataException {
1156                 String v = s;
1157                 if (split)
1158                         v = s.split("\\s+")[0];
1159                 try {
1160                         return Integer.parseInt(v);
1161                 } catch (NumberFormatException ne) {
1162                         throw new AltosConfigDataException("Invalid %s \"%s\"", name, s);
1163                 }
1164         }
1165
1166         public int apogee_delay() throws AltosConfigDataException {
1167                 return parse_int("apogee delay", apogee_delay_value.getSelectedItem().toString(), false);
1168         }
1169
1170         public void set_apogee_lockout(int new_apogee_lockout) {
1171                 if (new_apogee_lockout != AltosLib.MISSING)
1172                         apogee_lockout_value.setSelectedItem(Integer.toString(new_apogee_lockout));
1173
1174                 apogee_lockout_value.setVisible(new_apogee_lockout != AltosLib.MISSING);
1175                 apogee_lockout_label.setVisible(new_apogee_lockout != AltosLib.MISSING);
1176         }
1177
1178         public int apogee_lockout() throws AltosConfigDataException {
1179                 return parse_int("apogee lockout", apogee_lockout_value.getSelectedItem().toString(), false);
1180         }
1181
1182         public void set_radio_frequency(double new_radio_frequency) {
1183                 if (new_radio_frequency != AltosLib.MISSING)
1184                         radio_frequency_value.set_frequency(new_radio_frequency);
1185                 radio_frequency_label.setVisible(new_radio_frequency != AltosLib.MISSING);
1186                 radio_frequency_value.setVisible(new_radio_frequency != AltosLib.MISSING);
1187         }
1188
1189         public double radio_frequency() {
1190                 return radio_frequency_value.frequency();
1191         }
1192
1193         public void set_radio_calibration(int new_radio_calibration) {
1194                 if (new_radio_calibration != AltosLib.MISSING)
1195                         radio_calibration_value.setText(String.format("%d", new_radio_calibration));
1196                 radio_calibration_value.setVisible(new_radio_calibration != AltosLib.MISSING);
1197                 radio_calibration_label.setVisible(new_radio_calibration != AltosLib.MISSING);
1198         }
1199
1200         public void set_radio_enable(int new_radio_enable) {
1201                 if (new_radio_enable != AltosLib.MISSING)
1202                         radio_enable_value.setSelected(new_radio_enable != 0);
1203                 radio_enable_label.setVisible(new_radio_enable != AltosLib.MISSING);
1204                 radio_enable_value.setVisible(new_radio_enable != AltosLib.MISSING);
1205                 set_radio_enable_tool_tip();
1206         }
1207
1208         public int radio_enable() {
1209                 if (radio_enable_value.isVisible())
1210                         return radio_enable_value.isSelected() ? 1 : 0;
1211                 else
1212                         return AltosLib.MISSING;
1213         }
1214
1215         public void set_telemetry_rate(int new_rate) {
1216                 if (new_rate != AltosLib.MISSING)
1217                         rate_value.set_rate(new_rate);
1218                 rate_label.setVisible(new_rate != AltosLib.MISSING);
1219                 rate_value.setVisible(new_rate != AltosLib.MISSING);
1220                 set_rate_tool_tip();
1221         }
1222
1223         public int telemetry_rate() {
1224                 return rate_value.rate();
1225         }
1226
1227         public void set_callsign(String new_callsign) {
1228                 if (new_callsign != null)
1229                         callsign_value.setText(new_callsign);
1230                 callsign_value.setVisible(new_callsign != null);
1231                 callsign_label.setVisible(new_callsign != null);
1232         }
1233
1234         public String callsign() {
1235                 if (callsign_value.isVisible())
1236                         return callsign_value.getText();
1237                 return null;
1238         }
1239
1240         int     flight_log_max_limit;
1241         int     flight_log_max;
1242
1243         public String flight_log_max_label(int flight_log_max) {
1244                 if (flight_log_max_limit != 0) {
1245                         int     nflight = flight_log_max_limit / flight_log_max;
1246                         String  plural = nflight > 1 ? "s" : "";
1247
1248                         return String.format("%d (%d flight%s)", flight_log_max, nflight, plural);
1249                 }
1250                 return String.format("%d", flight_log_max);
1251         }
1252
1253         public void set_flight_log_max(int new_flight_log_max) {
1254                 if (new_flight_log_max != AltosLib.MISSING) {
1255                         flight_log_max_value.setSelectedItem(flight_log_max_label(new_flight_log_max));
1256                         flight_log_max = new_flight_log_max;
1257                 }
1258                 flight_log_max_value.setVisible(new_flight_log_max != AltosLib.MISSING);
1259                 flight_log_max_label.setVisible(new_flight_log_max != AltosLib.MISSING);
1260                 set_flight_log_max_tool_tip();
1261         }
1262
1263         public void set_flight_log_max_enabled(boolean enable) {
1264                 flight_log_max_value.setEnabled(enable);
1265                 set_flight_log_max_tool_tip();
1266         }
1267
1268         public int flight_log_max() throws AltosConfigDataException {
1269                 if (flight_log_max_value.isVisible())
1270                         return parse_int("flight log max", flight_log_max_value.getSelectedItem().toString(), true);
1271                 return AltosLib.MISSING;
1272         }
1273
1274         public void set_flight_log_max_limit(int new_flight_log_max_limit) {
1275                 flight_log_max_limit = new_flight_log_max_limit;
1276                 if (new_flight_log_max_limit != AltosLib.MISSING) {
1277                         flight_log_max_value.removeAllItems();
1278                         for (int i = 8; i >= 1; i--) {
1279                                 int     size = flight_log_max_limit / i;
1280                                 flight_log_max_value.addItem(String.format("%d (%d flights)", size, i));
1281                         }
1282                 }
1283                 if (flight_log_max != 0)
1284                         set_flight_log_max(flight_log_max);
1285         }
1286
1287         public void set_ignite_mode(int new_ignite_mode) {
1288                 if (new_ignite_mode != AltosLib.MISSING) {
1289                         if (new_ignite_mode >= ignite_mode_values.length)
1290                                 new_ignite_mode = 0;
1291                         if (new_ignite_mode < 0) {
1292                                 ignite_mode_value.setEnabled(false);
1293                                 new_ignite_mode = 0;
1294                         } else {
1295                                 ignite_mode_value.setEnabled(true);
1296                         }
1297                         ignite_mode_value.setSelectedIndex(new_ignite_mode);
1298                 }
1299                 ignite_mode_value.setVisible(new_ignite_mode != AltosLib.MISSING);
1300                 ignite_mode_label.setVisible(new_ignite_mode != AltosLib.MISSING);
1301
1302                 set_ignite_mode_tool_tip();
1303         }
1304
1305         public int ignite_mode() {
1306                 if (ignite_mode_value.isVisible())
1307                         return ignite_mode_value.getSelectedIndex();
1308                 else
1309                         return AltosLib.MISSING;
1310         }
1311
1312
1313         public void set_pad_orientation(int new_pad_orientation) {
1314                 if (new_pad_orientation != AltosLib.MISSING) {
1315                         if (new_pad_orientation >= pad_orientation_values.length)
1316                                 new_pad_orientation = 0;
1317                         if (new_pad_orientation < 0)
1318                                 new_pad_orientation = 0;
1319                         pad_orientation_value.setSelectedIndex(new_pad_orientation);
1320                 }
1321                 pad_orientation_value.setVisible(new_pad_orientation != AltosLib.MISSING);
1322                 pad_orientation_label.setVisible(new_pad_orientation != AltosLib.MISSING);
1323                 accel_cal.setVisible(new_pad_orientation != AltosLib.MISSING);
1324
1325                 set_pad_orientation_tool_tip();
1326         }
1327
1328         public int pad_orientation() {
1329                 if (pad_orientation_value.isVisible())
1330                         return pad_orientation_value.getSelectedIndex();
1331                 else
1332                         return AltosLib.MISSING;
1333         }
1334
1335         public void set_accel_cal(int accel_plus, int accel_minus) {
1336                 if (accel_plus != AltosLib.MISSING) {
1337                         accel_plus_value.setText(String.format("%d", accel_plus));
1338                         accel_minus_value.setText(String.format("%d", accel_minus));
1339                 }
1340                 accel_plus_value.setVisible(accel_plus != AltosLib.MISSING);
1341                 accel_plus_label.setVisible(accel_plus != AltosLib.MISSING);
1342                 accel_minus_value.setVisible(accel_minus != AltosLib.MISSING);
1343                 accel_minus_label.setVisible(accel_minus != AltosLib.MISSING);
1344
1345                 set_accel_tool_tips();
1346         }
1347
1348         public int accel_cal_plus() {
1349                 if (accel_plus_value.isVisible())
1350                         return Integer.parseInt(accel_plus_value.getText());
1351                 return AltosLib.MISSING;
1352         }
1353
1354         public int accel_cal_minus() {
1355                 if (accel_minus_value.isVisible())
1356                         return Integer.parseInt(accel_minus_value.getText());
1357                 return AltosLib.MISSING;
1358         }
1359
1360         public void set_beep(int new_beep) {
1361                 if (new_beep != AltosLib.MISSING) {
1362                         int new_freq = (int) Math.floor (AltosConvert.beep_value_to_freq(new_beep) + 0.5);
1363                         for (int i = 0; i < beep_values.length; i++)
1364                                 if (new_beep == AltosConvert.beep_freq_to_value(Integer.parseInt(beep_values[i]))) {
1365                                         beep_value.setSelectedIndex(i);
1366                                         set_beep_tool_tip();
1367                                         return;
1368                                 }
1369                         beep_value.setSelectedItem(String.format("%d", new_freq));
1370                 }
1371                 beep_value.setVisible(new_beep != AltosLib.MISSING);
1372                 beep_label.setVisible(new_beep != AltosLib.MISSING);
1373                 set_beep_tool_tip();
1374         }
1375
1376         public int beep() {
1377                 if (beep_value.isVisible())
1378                         return AltosConvert.beep_freq_to_value(Integer.parseInt(beep_value.getSelectedItem().toString()));
1379                 else
1380                         return AltosLib.MISSING;
1381         }
1382
1383         String[] tracker_motion_values() {
1384                 if (AltosConvert.imperial_units)
1385                         return tracker_motion_values_ft;
1386                 else
1387                         return tracker_motion_values_m;
1388         }
1389
1390         void set_tracker_motion_values() {
1391                 String[]        v = tracker_motion_values();
1392                 while (tracker_motion_value.getItemCount() > 0)
1393                         tracker_motion_value.removeItemAt(0);
1394                 for (int i = 0; i < v.length; i++)
1395                         tracker_motion_value.addItem(v[i]);
1396                 tracker_motion_value.setMaximumRowCount(v.length);
1397         }
1398
1399         String get_tracker_motion_label() {
1400                 return String.format("Logging Trigger Motion (%s):", AltosConvert.height.parse_units());
1401         }
1402
1403         void set_tracker_tool_tip() {
1404                 if (tracker_motion_value.isVisible())
1405                         tracker_motion_value.setToolTipText("How far the device must move before logging");
1406                 else
1407                         tracker_motion_value.setToolTipText("This device doesn't disable logging when stationary");
1408                 if (tracker_interval_value.isVisible())
1409                         tracker_interval_value.setToolTipText("How often to report GPS position");
1410                 else
1411                         tracker_interval_value.setToolTipText("This device can't configure interval");
1412         }
1413
1414         public void set_tracker_motion(int tracker_motion) {
1415                 if (tracker_motion != AltosLib.MISSING)
1416                         tracker_motion_value.setSelectedItem(AltosConvert.height.say(tracker_motion));
1417                 tracker_motion_label.setVisible(tracker_motion != AltosLib.MISSING);
1418                 tracker_motion_value.setVisible(tracker_motion != AltosLib.MISSING);
1419         }
1420
1421         public int tracker_motion() throws AltosConfigDataException {
1422                 if (tracker_motion_value.isVisible()) {
1423                         String str = tracker_motion_value.getSelectedItem().toString();
1424                         try {
1425                                 return (int) (AltosConvert.height.parse_locale(str) + 0.5);
1426                         } catch (ParseException pe) {
1427                                 throw new AltosConfigDataException("invalid tracker motion %s", str);
1428                         }
1429                 }
1430                 return AltosLib.MISSING;
1431         }
1432
1433         public void set_tracker_interval(int tracker_interval) {
1434                 if (tracker_interval != AltosLib.MISSING)
1435                         tracker_interval_value.setSelectedItem(String.format("%d", tracker_interval));
1436                 tracker_interval_label.setVisible(tracker_interval != AltosLib.MISSING);
1437                 tracker_interval_value.setVisible(tracker_interval != AltosLib.MISSING);
1438         }
1439
1440         public int tracker_interval() throws AltosConfigDataException {
1441                 if (tracker_interval_value.isVisible())
1442                         return parse_int ("tracker interval", tracker_interval_value.getSelectedItem().toString(), false);
1443                 return AltosLib.MISSING;
1444         }
1445
1446         public void set_pyros(AltosPyro[] new_pyros) {
1447                 pyros = new_pyros;
1448                 if (pyros != null && pyro_ui != null)
1449                         pyro_ui.set_pyros(pyros);
1450                 pyro.setVisible(pyros != null);
1451         }
1452
1453         public AltosPyro[] pyros() throws AltosConfigDataException {
1454                 if (pyro_ui != null)
1455                         pyros = pyro_ui.get_pyros();
1456                 return pyros;
1457         }
1458
1459         public void set_pyro_firing_time(double new_pyro_firing_time) {
1460                 pyro_firing_time = new_pyro_firing_time;
1461                 if (pyro_firing_time != AltosLib.MISSING && pyro_ui != null)
1462                         pyro_ui.set_pyro_firing_time(pyro_firing_time);
1463                 pyro.setVisible(pyro_firing_time != AltosLib.MISSING);
1464         }
1465
1466         public double pyro_firing_time() throws AltosConfigDataException {
1467                 if (pyro_ui != null)
1468                         pyro_firing_time = pyro_ui.get_pyro_firing_time();
1469                 return pyro_firing_time;
1470         }
1471
1472         private String aprs_interval_string(int interval) {
1473                 if (interval == 0)
1474                         return "Disabled";
1475                 return Integer.toString(interval);
1476         }
1477
1478         private int aprs_interval_value(String interval) throws AltosConfigDataException {
1479                 if (interval.equalsIgnoreCase("Disabled"))
1480                         return 0;
1481                 return parse_int("aprs interval", interval, false);
1482         }
1483
1484         public void set_aprs_interval(int new_aprs_interval) {
1485                 if (new_aprs_interval != AltosLib.MISSING)
1486                         aprs_interval_value.setSelectedItem(aprs_interval_string(new_aprs_interval));
1487                 aprs_interval_value.setVisible(new_aprs_interval != AltosLib.MISSING);
1488                 aprs_interval_label.setVisible(new_aprs_interval != AltosLib.MISSING);
1489                 set_aprs_interval_tool_tip();
1490         }
1491
1492         public int aprs_interval() throws AltosConfigDataException {
1493                 if (aprs_interval_value.isVisible())
1494                         return aprs_interval_value(aprs_interval_value.getSelectedItem().toString());
1495                 return AltosLib.MISSING;
1496         }
1497
1498         public void set_aprs_ssid(int new_aprs_ssid) {
1499                 if (new_aprs_ssid != AltosLib.MISSING)
1500                         aprs_ssid_value.setSelectedItem(new_aprs_ssid);
1501                 aprs_ssid_value.setVisible(new_aprs_ssid != AltosLib.MISSING);
1502                 aprs_ssid_label.setVisible(new_aprs_ssid != AltosLib.MISSING);
1503                 set_aprs_ssid_tool_tip();
1504         }
1505
1506         public int aprs_ssid() throws AltosConfigDataException {
1507                 if (aprs_ssid_value.isVisible()) {
1508                         Integer i = (Integer) aprs_ssid_value.getSelectedItem();
1509                         return i;
1510                 }
1511                 return AltosLib.MISSING;
1512         }
1513
1514         public void set_aprs_format(int new_aprs_format) {
1515                 if (new_aprs_format != AltosLib.MISSING)
1516                         aprs_format_value.setSelectedIndex(new_aprs_format);
1517                 aprs_format_value.setVisible(new_aprs_format != AltosLib.MISSING);
1518                 aprs_format_label.setVisible(new_aprs_format != AltosLib.MISSING);
1519                 set_aprs_format_tool_tip();
1520         }
1521
1522         public int aprs_format() throws AltosConfigDataException {
1523                 if (aprs_format_value.isVisible())
1524                         return aprs_format_value.getSelectedIndex();
1525                 return AltosLib.MISSING;
1526         }
1527
1528         public void set_aprs_offset(int new_aprs_offset) {
1529                 if (new_aprs_offset != AltosLib.MISSING)
1530                         aprs_offset_value.setSelectedItem(new_aprs_offset);
1531                 aprs_offset_value.setVisible(new_aprs_offset != AltosLib.MISSING);
1532                 aprs_offset_label.setVisible(new_aprs_offset != AltosLib.MISSING);
1533                 set_aprs_offset_tool_tip();
1534         }
1535
1536         public int aprs_offset() throws AltosConfigDataException {
1537                 if (aprs_offset_value.isVisible()) {
1538                         Integer i = (Integer) aprs_offset_value.getSelectedItem();
1539                         return i;
1540                 }
1541                 return AltosLib.MISSING;
1542         }
1543 }