2 * Copyright © 2011 Keith Packard <keithp@keithp.com>
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.
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.
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.
21 import java.awt.event.*;
23 import javax.swing.filechooser.FileNameExtensionFilter;
24 import javax.swing.table.*;
25 import javax.swing.event.*;
29 import java.util.prefs.*;
30 import java.util.concurrent.*;
32 class AltosScanResult {
38 boolean interrupted = false;
40 public String toString() {
41 return String.format("%-9.9s %4d %4d %2d %2d",
42 callsign, serial, flight, channel, telemetry);
45 public String toShortString() {
46 return String.format("%s %d %d %d %d",
47 callsign, serial, flight, channel, telemetry);
50 public AltosScanResult(String in_callsign, int in_serial,
51 int in_flight, int in_channel, int in_telemetry) {
52 callsign = in_callsign;
56 telemetry = in_telemetry;
59 public boolean equals(AltosScanResult other) {
60 return (callsign.equals(other.callsign) &&
61 serial == other.serial &&
62 flight == other.flight &&
63 channel == other.channel &&
64 telemetry == other.telemetry);
68 class AltosScanResults extends LinkedList<AltosScanResult> implements ListModel {
70 LinkedList<ListDataListener> listeners = new LinkedList<ListDataListener>();
72 public boolean add(AltosScanResult r) {
73 for (AltosScanResult old : this)
78 ListDataEvent de = new ListDataEvent(this,
79 ListDataEvent.INTERVAL_ADDED,
80 this.size() - 2, this.size() - 1);
81 for (ListDataListener l : listeners)
82 l.contentsChanged(de);
86 public void addListDataListener(ListDataListener l) {
90 public void removeListDataListener(ListDataListener l) {
94 public AltosScanResult getElementAt(int i) {
98 public int getSize() {
103 public class AltosScanUI
105 implements ActionListener
109 AltosTelemetryReader reader;
111 private JLabel channel_label;
112 private JLabel monitor_label;
113 private JButton ok_button;
114 javax.swing.Timer timer;
115 AltosScanResults results = new AltosScanResults();
117 static final int[] monitors = { Altos.ao_telemetry_split_len,
118 Altos.ao_telemetry_legacy_len };
122 final static int timeout = 5 * 1000;
123 TelemetryHandler handler;
126 void scan_exception(Exception e) {
127 if (e instanceof FileNotFoundException) {
128 JOptionPane.showMessageDialog(owner,
129 String.format("Cannot open device \"%s\"",
130 device.toShortString()),
131 "Cannot open target device",
132 JOptionPane.ERROR_MESSAGE);
133 } else if (e instanceof AltosSerialInUseException) {
134 JOptionPane.showMessageDialog(owner,
135 String.format("Device \"%s\" already in use",
136 device.toShortString()),
138 JOptionPane.ERROR_MESSAGE);
139 } else if (e instanceof IOException) {
140 IOException ee = (IOException) e;
141 JOptionPane.showMessageDialog(owner,
142 device.toShortString(),
143 ee.getLocalizedMessage(),
144 JOptionPane.ERROR_MESSAGE);
146 JOptionPane.showMessageDialog(owner,
147 String.format("Connection to \"%s\" failed",
148 device.toShortString()),
150 JOptionPane.ERROR_MESSAGE);
155 class TelemetryHandler implements Runnable {
159 boolean interrupted = false;
164 AltosRecord record = reader.read();
167 if ((record.seen & AltosRecord.seen_flight) != 0) {
168 AltosScanResult result = new AltosScanResult(record.callsign,
175 } catch (ParseException pp) {
176 System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage());
177 } catch (AltosCRCException ce) {
180 } catch (InterruptedException ee) {
182 } catch (IOException ie) {
184 reader.close(interrupted);
190 reader.serial.set_channel(channel);
194 reader.serial.set_telemetry(monitors[monitor]);
202 if (monitor == monitors.length)
211 if (thread != null && thread.isAlive()) {
215 } catch (InterruptedException ie) {}
228 public void actionPerformed(ActionEvent e) {
229 String cmd = e.getActionCommand();
231 if (cmd.equals("close")) {
236 /* A window listener to catch closing events and tell the config code */
237 class ConfigListener extends WindowAdapter {
240 public ConfigListener(AltosScanUI this_ui) {
244 public void windowClosing(WindowEvent e) {
245 ui.actionPerformed(new ActionEvent(e.getSource(),
246 ActionEvent.ACTION_PERFORMED,
251 private boolean open() {
252 device = AltosDeviceDialog.show(owner, Altos.product_any);
253 if (device != null) {
255 reader = new AltosTelemetryReader(device);
258 handler = new TelemetryHandler();
259 thread = new Thread(handler);
262 } catch (Exception e) {
269 public AltosScanUI(JFrame in_owner) {
276 Container pane = getContentPane();
277 GridBagConstraints c = new GridBagConstraints();
278 Insets i = new Insets(4,4,4,4);
280 timer = new javax.swing.Timer(timeout, this);
281 timer.setActionCommand("tick");
286 pane.setLayout(new GridBagLayout());
288 c.fill = GridBagConstraints.NONE;
289 c.anchor = GridBagConstraints.CENTER;
297 c.anchor = GridBagConstraints.CENTER;
299 list = new JList(results) {
300 //Subclass JList to workaround bug 4832765, which can cause the
301 //scroll pane to not let the user easily scroll up to the beginning
302 //of the list. An alternative would be to set the unitIncrement
303 //of the JScrollBar to a fixed value. You wouldn't get the nice
304 //aligned scrolling, but it should work.
305 public int getScrollableUnitIncrement(Rectangle visibleRect,
309 if (orientation == SwingConstants.VERTICAL &&
310 direction < 0 && (row = getFirstVisibleIndex()) != -1) {
311 Rectangle r = getCellBounds(row, row);
312 if ((r.y == visibleRect.y) && (row != 0)) {
313 Point loc = r.getLocation();
315 int prevIndex = locationToIndex(loc);
316 Rectangle prevR = getCellBounds(prevIndex, prevIndex);
318 if (prevR == null || prevR.y >= r.y) {
324 return super.getScrollableUnitIncrement(
325 visibleRect, orientation, direction);
329 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
330 list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
331 list.setVisibleRowCount(-1);
333 // list.addMouseListener(new MouseAdapter() {
334 // public void mouseClicked(MouseEvent e) {
335 // if (e.getClickCount() == 2) {
336 // select_button.doClick(); //emulate button click
340 JScrollPane listScroller = new JScrollPane(list);
341 listScroller.setPreferredSize(new Dimension(400, 80));
342 listScroller.setAlignmentX(LEFT_ALIGNMENT);
344 //Create a container so that we can add a title around
345 //the scroll pane. Can't add a title directly to the
346 //scroll pane because its background would be white.
347 //Lay out the label and scroll pane from top to bottom.
348 JPanel listPane = new JPanel();
349 listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
351 JLabel label = new JLabel("Select Device");
352 label.setLabelFor(list);
354 listPane.add(Box.createRigidArea(new Dimension(0,5)));
355 listPane.add(listScroller);
356 listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
358 pane.add(listPane, c);
361 setLocationRelativeTo(owner);
363 addWindowListener(new ConfigListener(this));