altosui: Move AltosIgniteUI device open out of Swing thread
[fw/altos] / altosui / AltosIgniteUI.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.filechooser.FileNameExtensionFilter;
24 import javax.swing.table.*;
25 import javax.swing.event.*;
26 import java.io.*;
27 import java.util.*;
28 import java.text.*;
29 import java.util.prefs.*;
30 import java.util.concurrent.*;
31
32 public class AltosIgniteUI
33         extends JDialog
34         implements ActionListener
35 {
36         AltosDevice     device;
37         JFrame          owner;
38         JLabel          label;
39         JRadioButton    apogee;
40         JLabel          apogee_status_label;
41         JRadioButton    main;
42         JLabel          main_status_label;
43         JToggleButton   arm;
44         JButton         fire;
45         javax.swing.Timer       timer;
46
47         int             apogee_status;
48         int             main_status;
49
50         final static int        timeout = 1 * 1000;
51
52         int             time_remaining;
53         boolean         timer_running;
54
55         LinkedBlockingQueue<String>     command_queue;
56
57         class IgniteHandler implements Runnable {
58                 AltosIgnite     ignite;
59                 JFrame          owner;
60
61                 void send_exception(Exception e) {
62                         final Exception f_e = e;
63                         Runnable r = new Runnable() {
64                                         public void run() {
65                                                 ignite_exception(f_e);
66                                         }
67                                 };
68                         SwingUtilities.invokeLater(r);
69                 }
70
71                 public void run () {
72                         try {
73                                 ignite = new AltosIgnite(device);
74                         } catch (Exception e) {
75                                 send_exception(e);
76                                 return;
77                         }
78                         ignite.set_frame(owner);
79
80                         for (;;) {
81                                 Runnable        r;
82
83                                 try {
84                                         String          command = command_queue.take();
85                                         String          reply = null;
86
87                                         if (command.equals("get_status")) {
88                                                 apogee_status = ignite.status(AltosIgnite.Apogee);
89                                                 main_status = ignite.status(AltosIgnite.Main);
90                                                 reply = "status";
91                                         } else if (command.equals("main")) {
92                                                 ignite.fire(AltosIgnite.Main);
93                                                 reply = "fired";
94                                         } else if (command.equals("apogee")) {
95                                                 ignite.fire(AltosIgnite.Apogee);
96                                                 reply = "fired";
97                                         } else if (command.equals("quit")) {
98                                                 ignite.close();
99                                                 break;
100                                         } else {
101                                                 throw new ParseException(String.format("invalid command %s", command), 0);
102                                         }
103                                         final String f_reply = reply;
104                                         r = new Runnable() {
105                                                         public void run() {
106                                                                 ignite_reply(f_reply);
107                                                         }
108                                                 };
109                                         SwingUtilities.invokeLater(r);
110                                 } catch (Exception e) {
111                                         send_exception(e);
112                                 }
113                         }
114                 }
115
116                 public IgniteHandler(JFrame in_owner) {
117                         owner = in_owner;
118                 }
119         }
120
121         void ignite_exception(Exception e) {
122                 if (e instanceof FileNotFoundException) {
123                         JOptionPane.showMessageDialog(owner,
124                                                       String.format("Cannot open device \"%s\"",
125                                                                     device.toShortString()),
126                                                       "Cannot open target device",
127                                                       JOptionPane.ERROR_MESSAGE);
128                 } else if (e instanceof AltosSerialInUseException) {
129                         JOptionPane.showMessageDialog(owner,
130                                                       String.format("Device \"%s\" already in use",
131                                                                     device.toShortString()),
132                                                       "Device in use",
133                                                       JOptionPane.ERROR_MESSAGE);
134                 } else if (e instanceof IOException) {
135                         IOException ee = (IOException) e;
136                         JOptionPane.showMessageDialog(owner,
137                                                       device.toShortString(),
138                                                       ee.getLocalizedMessage(),
139                                                       JOptionPane.ERROR_MESSAGE);
140                 } else {
141                         JOptionPane.showMessageDialog(owner,
142                                                       String.format("Connection to \"%s\" failed",
143                                                                     device.toShortString()),
144                                                       "Connection Failed",
145                                                       JOptionPane.ERROR_MESSAGE);
146                 }
147                 close();
148         }
149
150         void ignite_reply(String reply) {
151                 if (reply.equals("status")) {
152                         set_ignite_status();
153                 } else if (reply.equals("fired")) {
154                         fired();
155                 }
156         }
157
158         void set_arm_text() {
159                 if (arm.isSelected())
160                         arm.setText(String.format("%d", time_remaining));
161                 else
162                         arm.setText("Arm");
163         }
164
165         void start_timer() {
166                 time_remaining = 10;
167                 set_arm_text();
168                 timer_running = true;
169         }
170
171         void stop_timer() {
172                 time_remaining = 0;
173                 arm.setSelected(false);
174                 arm.setEnabled(false);
175                 fire.setEnabled(false);
176                 timer_running = false;
177                 set_arm_text();
178         }
179
180         void cancel () {
181                 apogee.setSelected(false);
182                 main.setSelected(false);
183                 fire.setEnabled(false);
184                 stop_timer();
185         }
186
187         void send_command(String command) {
188                 try {
189                         command_queue.put(command);
190                 } catch (Exception ex) {
191                         ignite_exception(ex);
192                 }
193         }
194
195         boolean getting_status = false;
196
197         boolean visible = false;
198         void set_ignite_status() {
199                 getting_status = false;
200                 apogee_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(apogee_status)));
201                 main_status_label.setText(String.format("\"%s\"", AltosIgnite.status_string(main_status)));
202                 if (!visible) {
203                         visible = true;
204                         setVisible(true);
205                 }
206         }
207
208         void poll_ignite_status() {
209                 if (!getting_status) {
210                         getting_status = true;
211                         send_command("get_status");
212                 }
213         }
214
215         boolean firing = false;
216
217         void start_fire(String which) {
218                 if (!firing) {
219                         firing = true;
220                         send_command(which);
221                 }
222         }
223
224         void fired() {
225                 firing = false;
226                 cancel();
227         }
228
229         void close() {
230                 send_command("quit");
231                 timer.stop();
232                 setVisible(false);
233                 dispose();
234         }
235
236         void tick_timer() {
237                 if (timer_running) {
238                         --time_remaining;
239                         if (time_remaining <= 0)
240                                 cancel();
241                         else
242                                 set_arm_text();
243                 }
244                 poll_ignite_status();
245         }
246
247         void fire() {
248                 if (arm.isEnabled() && arm.isSelected() && time_remaining > 0) {
249                         String  igniter = "none";
250                         if (apogee.isSelected() && !main.isSelected())
251                                 igniter = "apogee";
252                         else if (main.isSelected() && !apogee.isSelected())
253                                 igniter = "main";
254                         send_command(igniter);
255                         cancel();
256                 }
257         }
258
259         public void actionPerformed(ActionEvent e) {
260                 String cmd = e.getActionCommand();
261                 if (cmd.equals("apogee") || cmd.equals("main")) {
262                         stop_timer();
263                 }
264
265                 if (cmd.equals("apogee") && apogee.isSelected()) {
266                         main.setSelected(false);
267                         arm.setEnabled(true);
268                 }
269                 if (cmd.equals("main") && main.isSelected()) {
270                         apogee.setSelected(false);
271                         arm.setEnabled(true);
272                 }
273
274                 if (cmd.equals("arm")) {
275                         if (arm.isSelected()) {
276                                 fire.setEnabled(true);
277                                 start_timer();
278                         } else
279                                 cancel();
280                 }
281                 if (cmd.equals("fire"))
282                         fire();
283                 if (cmd.equals("tick"))
284                         tick_timer();
285                 if (cmd.equals("close")) {
286                         close();
287                 }
288         }
289
290         /* A window listener to catch closing events and tell the config code */
291         class ConfigListener extends WindowAdapter {
292                 AltosIgniteUI   ui;
293
294                 public ConfigListener(AltosIgniteUI this_ui) {
295                         ui = this_ui;
296                 }
297
298                 public void windowClosing(WindowEvent e) {
299                         ui.actionPerformed(new ActionEvent(e.getSource(),
300                                                            ActionEvent.ACTION_PERFORMED,
301                                                            "close"));
302                 }
303         }
304
305         private boolean open() {
306                 command_queue = new LinkedBlockingQueue<String>();
307
308                 device = AltosDeviceDialog.show(owner, Altos.product_any);
309                 if (device != null) {
310                                 IgniteHandler   handler = new IgniteHandler(owner);
311                                 Thread          t = new Thread(handler);
312                                 t.start();
313                                 return true;
314                 }
315                 return false;
316         }
317
318         public AltosIgniteUI(JFrame in_owner) {
319
320                 owner = in_owner;
321                 apogee_status = AltosIgnite.Unknown;
322                 main_status = AltosIgnite.Unknown;
323
324                 if (!open())
325                         return;
326
327                 Container               pane = getContentPane();
328                 GridBagConstraints      c = new GridBagConstraints();
329                 Insets                  i = new Insets(4,4,4,4);
330
331                 timer = new javax.swing.Timer(timeout, this);
332                 timer.setActionCommand("tick");
333                 timer_running = false;
334                 timer.restart();
335
336                 owner = in_owner;
337
338                 pane.setLayout(new GridBagLayout());
339
340                 c.fill = GridBagConstraints.NONE;
341                 c.anchor = GridBagConstraints.CENTER;
342                 c.insets = i;
343                 c.weightx = 1;
344                 c.weighty = 1;
345
346                 c.gridx = 0;
347                 c.gridy = 0;
348                 c.gridwidth = 2;
349                 c.anchor = GridBagConstraints.CENTER;
350                 label = new JLabel ("Fire Igniter");
351                 pane.add(label, c);
352
353                 c.gridx = 0;
354                 c.gridy = 1;
355                 c.gridwidth = 1;
356                 c.anchor = GridBagConstraints.WEST;
357                 apogee = new JRadioButton ("Apogee");
358                 pane.add(apogee, c);
359                 apogee.addActionListener(this);
360                 apogee.setActionCommand("apogee");
361
362                 c.gridx = 1;
363                 c.gridy = 1;
364                 c.gridwidth = 1;
365                 c.anchor = GridBagConstraints.WEST;
366                 apogee_status_label = new JLabel();
367                 pane.add(apogee_status_label, c);
368
369                 c.gridx = 0;
370                 c.gridy = 2;
371                 c.gridwidth = 1;
372                 c.anchor = GridBagConstraints.WEST;
373                 main = new JRadioButton ("Main");
374                 pane.add(main, c);
375                 main.addActionListener(this);
376                 main.setActionCommand("main");
377
378                 c.gridx = 1;
379                 c.gridy = 2;
380                 c.gridwidth = 1;
381                 c.anchor = GridBagConstraints.WEST;
382                 main_status_label = new JLabel();
383                 pane.add(main_status_label, c);
384
385                 c.gridx = 0;
386                 c.gridy = 3;
387                 c.gridwidth = 1;
388                 c.anchor = GridBagConstraints.CENTER;
389                 arm = new JToggleButton ("Arm");
390                 pane.add(arm, c);
391                 arm.addActionListener(this);
392                 arm.setActionCommand("arm");
393                 arm.setEnabled(false);
394
395                 c.gridx = 1;
396                 c.gridy = 3;
397                 c.gridwidth = 1;
398                 c.anchor = GridBagConstraints.CENTER;
399                 fire = new JButton ("Fire");
400                 fire.setEnabled(false);
401                 pane.add(fire, c);
402                 fire.addActionListener(this);
403                 fire.setActionCommand("fire");
404
405                 pack();
406                 setLocationRelativeTo(owner);
407
408                 addWindowListener(new ConfigListener(this));
409         }
410 }