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