444815442de739d5a40b45f909599df9d07c8a82
[fw/altos] / altosui / AltosLaunchUI.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 import org.altusmetrum.AltosLib.*;
32
33 class FireButton extends JButton {
34         protected void processMouseEvent(MouseEvent e) {
35                 super.processMouseEvent(e);
36                 switch (e.getID()) {
37                 case MouseEvent.MOUSE_PRESSED:
38                         if (actionListener != null)
39                                 actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_down"));
40                         break;
41                 case MouseEvent.MOUSE_RELEASED:
42                         if (actionListener != null)
43                                 actionListener.actionPerformed(new ActionEvent(this, e.getID(), "fire_up"));
44                         break;
45                 }
46         }
47
48         public FireButton(String s) {
49                 super(s);
50         }
51 }
52
53 public class AltosLaunchUI
54         extends AltosDialog
55         implements ActionListener
56 {
57         AltosDevice     device;
58         JFrame          owner;
59         JLabel          label;
60
61         int             radio_channel;
62         JLabel          radio_channel_label;
63         JTextField      radio_channel_text;
64
65         int             launcher_serial;
66         JLabel          launcher_serial_label;
67         JTextField      launcher_serial_text;
68
69         int             launcher_channel;
70         JLabel          launcher_channel_label;
71         JTextField      launcher_channel_text;
72
73         JLabel          armed_label;
74         JLabel          armed_status_label;
75         JLabel          igniter;
76         JLabel          igniter_status_label;
77         JToggleButton   arm;
78         FireButton      fire;
79         javax.swing.Timer       arm_timer;
80         javax.swing.Timer       fire_timer;
81
82         boolean         firing;
83         boolean         armed;
84         int             armed_status;
85         int             igniter_status;
86         int             rssi;
87
88         final static int        arm_timeout = 1 * 1000;
89         final static int        fire_timeout = 250;
90
91         int             armed_count;
92
93         LinkedBlockingQueue<String>     command_queue;
94
95         class LaunchHandler implements Runnable {
96                 AltosLaunch     launch;
97                 JFrame          owner;
98
99                 void send_exception(Exception e) {
100                         final Exception f_e = e;
101                         Runnable r = new Runnable() {
102                                         public void run() {
103                                                 launch_exception(f_e);
104                                         }
105                                 };
106                         SwingUtilities.invokeLater(r);
107                 }
108
109                 public void run () {
110                         try {
111                                 launch = new AltosLaunch(device);
112                         } catch (Exception e) {
113                                 send_exception(e);
114                                 return;
115                         }
116                         launch.set_frame(owner);
117                         launch.set_remote(launcher_serial, launcher_channel);
118
119                         for (;;) {
120                                 Runnable        r;
121
122                                 try {
123                                         String          command = command_queue.take();
124                                         String          reply = null;
125
126                                         if (command.equals("get_status")) {
127                                                 launch.status();
128                                                 reply = "status";
129                                                 armed_status = launch.armed;
130                                                 igniter_status = launch.igniter;
131                                                 rssi = launch.rssi;
132                                         } else if (command.equals("set_remote")) {
133                                                 launch.set_remote(launcher_serial, launcher_channel);
134                                                 reply = "remote set";
135                                         } else if (command.equals("arm")) {
136                                                 launch.arm();
137                                                 reply = "armed";
138                                         } else if (command.equals("fire")) {
139                                                 launch.fire();
140                                                 reply = "fired";
141                                         } else if (command.equals("quit")) {
142                                                 launch.close();
143                                                 break;
144                                         } else {
145                                                 throw new ParseException(String.format("invalid command %s", command), 0);
146                                         }
147                                         final String f_reply = reply;
148                                         r = new Runnable() {
149                                                         public void run() {
150                                                                 launch_reply(f_reply);
151                                                         }
152                                                 };
153                                         SwingUtilities.invokeLater(r);
154                                 } catch (Exception e) {
155                                         send_exception(e);
156                                 }
157                         }
158                 }
159
160                 public LaunchHandler(JFrame in_owner) {
161                         owner = in_owner;
162                 }
163         }
164
165         void launch_exception(Exception e) {
166                 if (e instanceof FileNotFoundException) {
167                         JOptionPane.showMessageDialog(owner,
168                                                       ((FileNotFoundException) e).getMessage(),
169                                                       "Cannot open target device",
170                                                       JOptionPane.ERROR_MESSAGE);
171                 } else if (e instanceof AltosSerialInUseException) {
172                         JOptionPane.showMessageDialog(owner,
173                                                       String.format("Device \"%s\" already in use",
174                                                                     device.toShortString()),
175                                                       "Device in use",
176                                                       JOptionPane.ERROR_MESSAGE);
177                 } else if (e instanceof IOException) {
178                         IOException ee = (IOException) e;
179                         JOptionPane.showMessageDialog(owner,
180                                                       device.toShortString(),
181                                                       ee.getLocalizedMessage(),
182                                                       JOptionPane.ERROR_MESSAGE);
183                 } else {
184                         JOptionPane.showMessageDialog(owner,
185                                                       String.format("Connection to \"%s\" failed",
186                                                                     device.toShortString()),
187                                                       "Connection Failed",
188                                                       JOptionPane.ERROR_MESSAGE);
189                 }
190                 close();
191         }
192
193         void launch_reply(String reply) {
194                 if (reply == null)
195                         return;
196                 if (reply.equals("remote set"))
197                         poll_launch_status();
198                 if (reply.equals("status")) {
199                         set_launch_status();
200                 }
201         }
202
203         void set_arm_text() {
204                 if (arm.isSelected())
205                         arm.setText(String.format("%d", armed_count));
206                 else
207                         arm.setText("Arm");
208         }
209
210         void start_arm_timer() {
211                 armed_count = 30;
212                 set_arm_text();
213         }
214
215         void stop_arm_timer() {
216                 armed_count = 0;
217                 armed = false;
218                 arm.setSelected(false);
219                 fire.setEnabled(false);
220                 set_arm_text();
221         }
222
223         void cancel () {
224                 fire.setEnabled(false);
225                 firing = false;
226                 stop_arm_timer();
227         }
228
229         void send_command(String command) {
230                 try {
231                         command_queue.put(command);
232                 } catch (Exception ex) {
233                         launch_exception(ex);
234                 }
235         }
236
237         boolean getting_status = false;
238
239         void set_launch_status() {
240                 getting_status = false;
241                 armed_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(armed_status)));
242                 igniter_status_label.setText(String.format("\"%s\"", AltosLaunch.status_string(igniter_status)));
243         }
244
245         void poll_launch_status() {
246                 if (!getting_status && !firing && !armed) {
247                         getting_status = true;
248                         send_command("get_status");
249                 }
250         }
251
252         void fired() {
253                 firing = false;
254                 cancel();
255         }
256
257         void close() {
258                 send_command("quit");
259                 arm_timer.stop();
260                 setVisible(false);
261                 dispose();
262         }
263
264         void tick_arm_timer() {
265                 if (armed_count > 0) {
266                         --armed_count;
267                         if (armed_count <= 0) {
268                                 armed_count = 0;
269                                 cancel();
270                         } else {
271                                 if (!firing) {
272                                         send_command("arm");
273                                         set_arm_text();
274                                 }
275                         }
276                 }
277                 poll_launch_status();
278         }
279
280         void arm() {
281                 if (arm.isSelected()) {
282                         fire.setEnabled(true);
283                         start_arm_timer();
284                         if (!firing)
285                                 send_command("arm");
286                         armed = true;
287                 } else
288                         cancel();
289         }
290
291         void fire_more() {
292                 if (firing)
293                         send_command("fire");
294         }
295
296         void fire_down() {
297                 if (arm.isEnabled() && arm.isSelected() && armed_count > 0) {
298                         firing = true;
299                         fire_more();
300                         fire_timer.restart();
301                 }
302         }
303
304         void fire_up() {
305                 firing = false;
306                 fire_timer.stop();
307         }
308
309         void set_radio() {
310                 try {
311                         radio_channel = Integer.parseInt(radio_channel_text.getText());
312                 } catch (NumberFormatException ne) {
313                         radio_channel_text.setText(String.format("%d", radio_channel));
314                 }
315         }
316
317         void set_serial() {
318                 try {
319                         launcher_serial = Integer.parseInt(launcher_serial_text.getText());
320                         AltosUIPreferences.set_launcher_serial(launcher_serial);
321                         send_command("set_remote");
322                 } catch (NumberFormatException ne) {
323                         launcher_serial_text.setText(String.format("%d", launcher_serial));
324                 }
325         }
326
327         void set_channel() {
328                 try {
329                         launcher_channel = Integer.parseInt(launcher_channel_text.getText());
330                         AltosUIPreferences.set_launcher_serial(launcher_channel);
331                         send_command("set_remote");
332                 } catch (NumberFormatException ne) {
333                         launcher_channel_text.setText(String.format("%d", launcher_channel));
334                 }
335         }
336
337         public void actionPerformed(ActionEvent e) {
338                 String cmd = e.getActionCommand();
339                 if (cmd.equals("armed") || cmd.equals("igniter")) {
340                         stop_arm_timer();
341                 }
342
343                 if (cmd.equals("arm"))
344                         arm();
345                 if (cmd.equals("tick_arm"))
346                         tick_arm_timer();
347                 if (cmd.equals("close"))
348                         close();
349                 if (cmd.equals("fire_down"))
350                         fire_down();
351                 if (cmd.equals("fire_up"))
352                         fire_up();
353                 if (cmd.equals("tick_fire"))
354                         fire_more();
355                 if (cmd.equals("new_serial"))
356                         set_serial();
357                 if (cmd.equals("new_channel"))
358                         set_channel();
359         }
360
361         /* A window listener to catch closing events and tell the config code */
362         class ConfigListener extends WindowAdapter {
363                 AltosLaunchUI   ui;
364
365                 public ConfigListener(AltosLaunchUI this_ui) {
366                         ui = this_ui;
367                 }
368
369                 public void windowClosing(WindowEvent e) {
370                         ui.actionPerformed(new ActionEvent(e.getSource(),
371                                                            ActionEvent.ACTION_PERFORMED,
372                                                            "close"));
373                 }
374         }
375
376         private boolean open() {
377                 command_queue = new LinkedBlockingQueue<String>();
378
379                 device = AltosDeviceDialog.show(owner, Altos.product_any);
380                 if (device != null) {
381                                 LaunchHandler   handler = new LaunchHandler(owner);
382                                 Thread          t = new Thread(handler);
383                                 t.start();
384                                 return true;
385                 }
386                 return false;
387         }
388
389         public AltosLaunchUI(JFrame in_owner) {
390
391                 launcher_channel = AltosUIPreferences.launcher_channel();
392                 launcher_serial = AltosUIPreferences.launcher_serial();
393                 owner = in_owner;
394                 armed_status = AltosLaunch.Unknown;
395                 igniter_status = AltosLaunch.Unknown;
396
397                 if (!open())
398                         return;
399
400                 Container               pane = getContentPane();
401                 GridBagConstraints      c = new GridBagConstraints();
402                 Insets                  i = new Insets(4,4,4,4);
403
404                 arm_timer = new javax.swing.Timer(arm_timeout, this);
405                 arm_timer.setActionCommand("tick_arm");
406                 arm_timer.restart();
407
408                 fire_timer = new javax.swing.Timer(fire_timeout, this);
409                 fire_timer.setActionCommand("tick_fire");
410
411                 owner = in_owner;
412
413                 pane.setLayout(new GridBagLayout());
414
415                 c.fill = GridBagConstraints.NONE;
416                 c.anchor = GridBagConstraints.CENTER;
417                 c.insets = i;
418                 c.weightx = 1;
419                 c.weighty = 1;
420
421                 c.gridx = 0;
422                 c.gridy = 0;
423                 c.gridwidth = 2;
424                 c.anchor = GridBagConstraints.CENTER;
425                 label = new JLabel ("Launch Controller");
426                 pane.add(label, c);
427
428                 c.gridx = 0;
429                 c.gridy = 1;
430                 c.gridwidth = 1;
431                 c.anchor = GridBagConstraints.WEST;
432                 launcher_serial_label = new JLabel("Launcher Serial");
433                 pane.add(launcher_serial_label, c);
434
435                 c.gridx = 1;
436                 c.gridy = 1;
437                 c.gridwidth = 1;
438                 c.anchor = GridBagConstraints.WEST;
439                 launcher_serial_text = new JTextField(7);
440                 launcher_serial_text.setText(String.format("%d", launcher_serial));
441                 launcher_serial_text.setActionCommand("new_serial");
442                 launcher_serial_text.addActionListener(this);
443                 pane.add(launcher_serial_text, c);
444
445                 c.gridx = 0;
446                 c.gridy = 2;
447                 c.gridwidth = 1;
448                 c.anchor = GridBagConstraints.WEST;
449                 launcher_channel_label = new JLabel("Launcher Channel");
450                 pane.add(launcher_channel_label, c);
451
452                 c.gridx = 1;
453                 c.gridy = 2;
454                 c.gridwidth = 1;
455                 c.anchor = GridBagConstraints.WEST;
456                 launcher_channel_text = new JTextField(7);
457                 launcher_channel_text.setText(String.format("%d", launcher_channel));
458                 launcher_channel_text.setActionCommand("new_channel");
459                 launcher_channel_text.addActionListener(this);
460                 pane.add(launcher_channel_text, c);
461
462                 c.gridx = 0;
463                 c.gridy = 3;
464                 c.gridwidth = 1;
465                 c.anchor = GridBagConstraints.WEST;
466                 armed_label = new JLabel ("Armed");
467                 pane.add(armed_label, c);
468
469                 c.gridx = 1;
470                 c.gridy = 3;
471                 c.gridwidth = 1;
472                 c.anchor = GridBagConstraints.WEST;
473                 armed_status_label = new JLabel();
474                 pane.add(armed_status_label, c);
475
476                 c.gridx = 0;
477                 c.gridy = 4;
478                 c.gridwidth = 1;
479                 c.anchor = GridBagConstraints.WEST;
480                 igniter = new JLabel ("Igniter");
481                 pane.add(igniter, c);
482
483                 c.gridx = 1;
484                 c.gridy = 4;
485                 c.gridwidth = 1;
486                 c.anchor = GridBagConstraints.WEST;
487                 igniter_status_label = new JLabel();
488                 pane.add(igniter_status_label, c);
489
490                 c.gridx = 0;
491                 c.gridy = 5;
492                 c.gridwidth = 1;
493                 c.anchor = GridBagConstraints.CENTER;
494                 arm = new JToggleButton ("Arm");
495                 pane.add(arm, c);
496                 arm.addActionListener(this);
497                 arm.setActionCommand("arm");
498                 arm.setEnabled(true);
499
500                 c.gridx = 1;
501                 c.gridy = 5;
502                 c.gridwidth = 1;
503                 c.anchor = GridBagConstraints.CENTER;
504                 fire = new FireButton ("Fire");
505                 fire.setEnabled(false);
506                 pane.add(fire, c);
507                 fire.addActionListener(this);
508                 fire.setActionCommand("fire");
509
510                 pack();
511                 setLocationRelativeTo(owner);
512
513                 addWindowListener(new ConfigListener(this));
514
515                 setVisible(true);
516         }
517 }