Project

General

Profile

Statistics
| Revision:

root / trunk / code / projects / colonet / client / Colonet.java @ 638

History | View | Annotate | Download (32.4 KB)

1
import javax.swing.*;
2
import javax.swing.event.*;
3
import javax.imageio.*;
4
import java.awt.*;
5
import java.awt.image.*;
6
import java.awt.event.*;
7
import java.net.*;
8
import java.io.*;
9
import java.util.*;
10

    
11
/**
12
* The Colonet Graphical User Interface Applet for use locally and over an internet connection.
13
* @author Gregory Tress
14
*
15
* To generate javadoc on this file or other java files, use javadoc *.java -d doc, where doc
16
* is the name of the folder into which the files should be written.
17
*/
18
public class Colonet extends JApplet implements ActionListener, MouseInputListener, KeyListener, Runnable {
19

    
20
        // Used for painting
21
        final int BUFFER = 50;
22
        final int RADIUS = 30;
23

    
24
        // Used for the robot controller
25
        final int VECTOR_CONTROLLER_HEIGHT = 190;
26
        final int VECTOR_CONTROLLER_WIDTH = 320;
27

    
28
        // Connection
29
        JTextField txtHost;
30
        JTextField txtPort;
31
        JButton btnConnect;
32
        JButton btnGetXBeeIDs;
33
        JLabel lblConnectionStatus;
34
        JTextArea txtInfo;
35
        JPanel panelConnect;
36
        JPanel panelServerInterface;
37
        Socket socket;
38
        DataUpdater dataUpdater;
39

    
40
        // Control
41
        JPanel panelControl;
42
        JTabbedPane tabPaneControl;
43
        JPanel panelRobotControl;
44
        JPanel panelRobotDirection;
45
        JPanel panelRobotDirectionButtons;
46
        JPanel panelRobotCommands;
47
        JButton btnF, btnB, btnL, btnR, btnActivate;
48
        JComboBox cmbRobotNum;
49
        JLabel lblBattery;
50
        JLabel lblSelected;
51
        BatteryIcon batteryIcon;
52
        JPanel panelBattery;
53
        VectorController vectorController;
54
        BufferedImage imageVectorControl;
55
        JButton btnAssignID;
56
        boolean setWaypoint;
57
        int setWaypointID;
58
        JButton btnCommand_MoveTo;
59
        JButton btnCommand_MoveAll;
60
        JButton btnCommand_StopTask;
61
        JButton btnCommand_ResumeTask;
62
        JButton btnCommand_ChargeNow;
63
        JButton btnCommand_StopCharging;
64

    
65
        // Task Manager
66
        JPanel panelTaskManager;
67
        JScrollPane spTaskManager;
68
        JPanel panelTaskManagerControls;
69
        JPanel panelTaskManagerControlsPriority;
70
        DefaultListModel taskListModel;
71
        JList taskList;
72
        JButton btnAddTask;
73
        JButton btnRemoveTask;
74
        JButton btnMoveTaskUp;
75
        JButton btnMoveTaskDown;
76
        JButton btnUpdateTasks;
77
        TaskAddWindow taskAddWindow;
78

    
79
        // Webcam
80
        WebcamPanel panelWebcam;
81
        GraphicsConfiguration gc;
82
        JTabbedPane tabPaneMain;
83
        WebcamLoader webcamLoader;
84
        
85
        // Robots
86
        volatile int selectedBot;         //the user has selected this bot graphically
87
        volatile java.util.Map <Integer, RobotIcon> robotIcons;         //contains boundary shapes around bots for click detection
88
        volatile int[] xbeeID;
89

    
90
        Colonet self = this;
91
        volatile ColonetServerInterface csi;
92
        Thread paintThread;
93

    
94
        public void init () {
95
                // Set the default look and feel - choose one
96
                String laf = UIManager.getSystemLookAndFeelClassName();
97
                //String laf = UIManager.getCrossPlatformLookAndFeelClassName();
98
                //String laf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
99
                try {
100
                        UIManager.setLookAndFeel(laf);
101
                } catch (UnsupportedLookAndFeelException exc) {
102
                        System.err.println ("Warning: UnsupportedLookAndFeel: " + laf);
103
                } catch (Exception exc) {
104
                        System.err.println ("Error loading " + laf + ": " + exc);
105
                }
106

    
107
                // We should invoke and wait to avoid browser display difficulties
108
                Runnable r = new Runnable() {
109
                        public void run() {
110
                                createAndShowGUI();
111
                        }
112
                };
113

    
114
                try {
115
                        SwingUtilities.invokeAndWait(r);
116
                } catch (InterruptedException e) {
117
                        //Not really sure why we would be in this situation
118
                        System.out.println("InterruptedException in init: " + e);
119
                } catch (java.lang.reflect.InvocationTargetException e) {
120
                        //This could happen for various reasons if there is a problem in createAndShowGUI
121
                        e.printStackTrace();
122
                }
123
        }
124

    
125
        public void destroy () {
126
                if (csi != null) {
127
                        csi.disconnect();
128
                }
129
        }
130

    
131
        private synchronized void createAndShowGUI () {
132
                paintThread = new Thread(this, "PaintThread");
133
                paintThread.start();
134
                
135
                // Webcam
136
                gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
137
                panelWebcam = new WebcamPanel();
138
                tabPaneMain = new JTabbedPane();
139
                tabPaneMain.setFont(new Font("arial", Font.PLAIN, 16));
140
                tabPaneMain.add(panelWebcam, "Webcam");
141

    
142
                // Robots
143
                selectedBot = -1;
144
                robotIcons = new HashMap <Integer, RobotIcon> ();
145

    
146
                // Connection area
147
                txtInfo = new JTextArea();
148
                txtInfo.setBorder(BorderFactory.createTitledBorder("Info"));
149
                txtHost = new JTextField(this.getDocumentBase().getHost());
150
                txtHost.setBorder(BorderFactory.createTitledBorder("Host"));
151
                txtPort = new JTextField("10123");
152
                txtPort.setBorder(BorderFactory.createTitledBorder("Port"));
153
                btnConnect = new JButton("Connect");
154
                btnConnect.setFont(new Font("arial", Font.BOLD, 16));
155
                btnGetXBeeIDs = new JButton("Get XBee IDs");
156
                getRootPane().setDefaultButton(btnConnect);
157
                lblConnectionStatus = new JLabel("Status: Offline");
158
                panelConnect = new JPanel();
159
                panelConnect.setLayout(new GridLayout(6,1));
160
                panelConnect.add(lblConnectionStatus);
161
                panelConnect.add(txtHost);
162
                panelConnect.add(txtPort);
163
                panelConnect.add(btnConnect);
164
                //panelConnect.add(btnGetXBeeIDs);
165
                panelServerInterface = new JPanel();
166
                panelServerInterface.setLayout(new GridLayout(2,1));
167
                panelServerInterface.add(panelConnect);
168
                panelServerInterface.add(txtInfo);
169

    
170
                // Robot direction panel
171
                panelRobotDirection = new JPanel();
172
                panelRobotDirectionButtons = new JPanel();
173

    
174
                Font f = new Font(null, Font.PLAIN, 18);
175
                btnF = new JButton("\u2191");
176
                btnF.setFont(f);
177
                btnB = new JButton("\u2193");
178
                btnB.setFont(f);
179
                btnL = new JButton("\u2190");
180
                btnL.setFont(f);
181
                btnR = new JButton("\u2192");
182
                btnR.setFont(f);
183
                btnActivate = new JButton("\u25A0");
184
                btnActivate.setFont(new Font(null, Font.BOLD, 36));
185

    
186
                panelRobotDirectionButtons.setLayout(new GridLayout(1,5));
187
                panelRobotDirectionButtons.add(btnActivate);
188
                panelRobotDirectionButtons.add(btnF);
189
                panelRobotDirectionButtons.add(btnB);
190
                panelRobotDirectionButtons.add(btnL);
191
                panelRobotDirectionButtons.add(btnR);
192

    
193
                imageVectorControl = gc.createCompatibleImage(VECTOR_CONTROLLER_WIDTH, VECTOR_CONTROLLER_HEIGHT);
194
                vectorController = new VectorController(imageVectorControl, self);
195
                panelRobotDirection.setLayout(new BorderLayout());
196
                panelRobotDirection.add(vectorController, BorderLayout.CENTER);
197
                panelRobotDirection.add(panelRobotDirectionButtons, BorderLayout.SOUTH);
198

    
199
                // Robot Control and Commands
200
                panelRobotCommands = new JPanel();
201
                panelRobotCommands.setLayout(new GridLayout(5,2));
202
                cmbRobotNum = new JComboBox();
203
                // Battery subset
204
                batteryIcon = new BatteryIcon(0);
205
                lblBattery = new JLabel(batteryIcon);
206
                lblSelected = new JLabel("None");
207
                // Command subset
208
                setWaypoint = false;
209
                setWaypointID = -1;
210
                btnAssignID = new JButton("Assign ID");
211
                btnCommand_MoveTo = new JButton("Move to ...");
212
                btnCommand_MoveAll = new JButton("Move all ...");
213
                btnCommand_StopTask = new JButton("Stop Current Task");
214
                btnCommand_ResumeTask = new JButton("Resume Current Task");
215
                btnCommand_ChargeNow = new JButton("Recharge Now");
216
                btnCommand_StopCharging = new JButton("Stop Recharging");
217
                panelRobotCommands.add(new JLabel("Select Robot to Control: "));
218
                panelRobotCommands.add(cmbRobotNum);
219
                panelRobotCommands.add(new JLabel("Battery Level: "));
220
                panelRobotCommands.add(lblBattery);
221
                panelRobotCommands.add(new JLabel("Selected Icon: "));
222
                panelRobotCommands.add(lblSelected);
223
                panelRobotCommands.add(btnAssignID);
224
                panelRobotCommands.add(new JLabel(""));
225
                panelRobotCommands.add(btnCommand_MoveTo);
226
                panelRobotCommands.add(btnCommand_MoveAll);
227
                //panelRobotCommands.add(btnCommand_StopTask);
228
                //panelRobotCommands.add(btnCommand_ResumeTask);
229
                //panelRobotCommands.add(btnCommand_ChargeNow);
230
                //panelRobotCommands.add(btnCommand_StopCharging);
231
                panelRobotControl = new JPanel();
232
                panelRobotControl.setLayout(new GridLayout(2,1));
233
                panelRobotControl.add(panelRobotDirection);
234
                panelRobotControl.add(panelRobotCommands);
235

    
236
                // Task Manager
237
                panelTaskManager = new JPanel();
238
                panelTaskManager.setLayout(new BorderLayout());
239
                taskListModel = new DefaultListModel();
240
                taskList = new JList(taskListModel);
241
                taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
242
                taskList.setSelectedIndex(0);
243
                spTaskManager = new JScrollPane(taskList);
244
                panelTaskManagerControls = new JPanel();
245
                panelTaskManagerControls.setLayout(new GridLayout(1,4));
246
                panelTaskManagerControlsPriority = new JPanel();
247
                panelTaskManagerControlsPriority.setLayout(new GridLayout(1,2));
248
                btnAddTask = new JButton("Add...");
249
                btnRemoveTask = new JButton("Remove");
250
                btnMoveTaskUp = new JButton("^");
251
                btnMoveTaskDown = new JButton("v");
252
                btnUpdateTasks = new JButton("Update");
253
                panelTaskManagerControlsPriority.add(btnMoveTaskUp);
254
                panelTaskManagerControlsPriority.add(btnMoveTaskDown);
255
                panelTaskManagerControls.add(btnAddTask);
256
                panelTaskManagerControls.add(btnRemoveTask);
257
                panelTaskManagerControls.add(btnUpdateTasks);
258
                panelTaskManagerControls.add(panelTaskManagerControlsPriority);
259
                panelTaskManager.add(spTaskManager, BorderLayout.CENTER);
260
                panelTaskManager.add(panelTaskManagerControls, BorderLayout.SOUTH);
261
                panelTaskManager.add(new JLabel("Current Task Queue"), BorderLayout.NORTH);
262
                taskAddWindow = new TaskAddWindow();
263

    
264
                // Main control mechanism
265
                panelControl = new JPanel();
266
                panelControl.setLayout(new GridLayout(1,1));
267
                tabPaneControl = new JTabbedPane(JTabbedPane.TOP);
268
                tabPaneControl.setFont(new Font("arial", Font.PLAIN, 16));
269
                tabPaneControl.setPreferredSize(new Dimension(VECTOR_CONTROLLER_WIDTH, 0));
270
                tabPaneControl.addTab("Connection", panelServerInterface);
271
                tabPaneControl.addTab("Robots", panelRobotControl);
272
                //tabPaneControl.addTab("Tasks", panelTaskManager);
273
                panelControl.add(tabPaneControl);
274

    
275

    
276
                // Put all elements in the ContentPane
277
                this.getContentPane().setLayout(new BorderLayout());
278
                this.getContentPane().add(tabPaneMain, BorderLayout.CENTER);
279
                this.getContentPane().add(panelControl, BorderLayout.EAST);
280
                this.setVisible(true);
281
                
282
                // Disable components before connecting
283
                btnConnect.setText("Connect");
284
        lblConnectionStatus.setText("Status: Disconnected");
285
        btnF.setEnabled(false);
286
        btnB.setEnabled(false);
287
        btnL.setEnabled(false);
288
        btnR.setEnabled(false);
289
        btnActivate.setEnabled(false);
290
        cmbRobotNum.setEnabled(false);
291
        btnAssignID.setEnabled(false);
292
        btnCommand_MoveTo.setEnabled(false);
293
        btnCommand_MoveAll.setEnabled(false);
294

    
295
                /* Add all listeners here */
296
                // Task Management
297
                btnAddTask.addActionListener(this);
298
                btnRemoveTask.addActionListener(this);
299
                btnMoveTaskUp.addActionListener(this);
300
                btnMoveTaskDown.addActionListener(this);
301
                btnUpdateTasks.addActionListener(this);
302
                // Robot Control
303
                btnF.addActionListener(this);
304
                btnB.addActionListener(this);
305
                btnL.addActionListener(this);
306
                btnR.addActionListener(this);
307
                btnF.addKeyListener(this);
308
                btnB.addKeyListener(this);
309
                btnL.addKeyListener(this);
310
                btnR.addKeyListener(this);
311
                btnActivate.addActionListener(this);
312
                btnActivate.addKeyListener(this);
313
                cmbRobotNum.addKeyListener(this);
314
                btnCommand_MoveTo.addActionListener(this);
315
                btnCommand_MoveAll.addActionListener(this);
316
                btnCommand_StopTask.addActionListener(this);
317
                btnCommand_ResumeTask.addActionListener(this);
318
                btnCommand_ChargeNow.addActionListener(this);
319
                btnCommand_StopCharging.addActionListener(this);
320
                // Other
321
                btnConnect.addActionListener(this);
322
                btnGetXBeeIDs.addActionListener(this);
323
                btnAssignID.addActionListener(this);
324
                panelWebcam.addMouseListener(this);
325
        }
326
        
327
        public ColonetServerInterface getCSI () {
328
            return csi;
329
        }
330

    
331
        public void paint (Graphics g) {
332
                super.paint(g);
333
        }
334

    
335
        public void update (Graphics g) {
336
                paint(g);
337
        }
338
        
339
        public void run () {
340
                final int PAINT_DELAY = 300;
341
                while (true) {
342
                        try {
343
                                Thread.sleep(PAINT_DELAY);
344
                        } catch (Exception e) {
345
                                System.out.println(e);
346
                                return;
347
                        }
348
                        repaint();
349
                }
350
        }
351

    
352
        /**
353
        * Gets the JTextArea used for displaying debugging information. 
354
        *
355
        * @return the JTextArea where debugging information can be displayed.
356
        */
357
        public JTextArea getInfoPanel () {
358
                return txtInfo;
359
        }
360

    
361
        /**
362
        * Parses a String containing BOM matrix information.
363
        * The ColonetServerInterface receives lines of the BOM matrix.        (For encoding
364
        * information, see the ColonetServerInterface documentation.)         The entire matrix is passed
365
        * to the client when requested. This method takes a string of the form
366
        * "[command code] [command code] [number of robots] [data0] [data1] ..."
367
        * with tokens separated by spaces and containing no brackets.
368
        * The [command code]s are predefined values identifying this String as a BOM data
369
        * String, [number of robots] is an integer, and the values that follow are
370
        * the sensor readings of the robots in order, starting with robot 0.        Only [number of robots]^2
371
        * data entries will be read.        The matrix values are saved locally until the next String is parsed.
372
        *
373
        *
374
        * @param line the String containing BOM matrix information.
375
        * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots]^2 data entries in the String
376
        */
377
        public void parseMatrix (String line) {
378
                txtInfo.setText("");
379
                String [] str = line.split(" ");
380
                int num = Integer.parseInt(str[2]);
381
                for (int i = 0; i < num; i++) {
382
                        for (int j = 0; j < num; j++) {
383
                                String next = str[3 + i*num + j];
384
                                if (next.equals("-1")) {
385
                                        txtInfo.append("-");
386
                                } else {
387
                                        txtInfo.append(next);
388
                                }
389

    
390
                                if (j < num - 1) {
391
                                        txtInfo.append(" ");
392
                                }
393
                        }
394

    
395
                        if (i < num - 1) {
396
                                txtInfo.append("\n");
397
                        }
398
                }
399
                repaint();
400
        }
401

    
402
        public void connect () {
403
        if (csi != null)
404
                return;
405
        csi = new ColonetServerInterface(self);
406
        csi.connect(txtHost.getText(), txtPort.getText());
407
        if (!csi.isReady()) {
408
                csi = null;
409
                return;
410
        }
411
        webcamLoader = new WebcamLoader(self);
412
        dataUpdater = new DataUpdater();
413
        dataUpdater.start();
414
        webcamLoader.start();
415
        Runnable r = new Runnable() {
416
                public void run () {
417
                        btnConnect.setText("Disconnect");
418
                        lblConnectionStatus.setText("Status: Connected");
419
                btnF.setEnabled(true);
420
                btnB.setEnabled(true);
421
                btnL.setEnabled(true);
422
                btnR.setEnabled(true);
423
                btnActivate.setEnabled(true);
424
                cmbRobotNum.setEnabled(true);
425
                btnAssignID.setEnabled(true);
426
                btnCommand_MoveTo.setEnabled(true);
427
                btnCommand_MoveAll.setEnabled(true);
428
                    }
429
                };
430
                SwingUtilities.invokeLater(r);
431
        }
432

    
433
        public void disconnect () {
434
        try {
435
            dataUpdater.interrupt();
436
        } catch (Exception e) {
437
        }
438
        csi = null;
439
                Runnable r = new Runnable() {
440
                public void run () {
441
                        btnConnect.setText("Connect");
442
                    lblConnectionStatus.setText("Status: Disconnected");
443
                btnF.setEnabled(false);
444
                btnB.setEnabled(false);
445
                btnL.setEnabled(false);
446
                btnR.setEnabled(false);
447
                btnActivate.setEnabled(false);
448
                cmbRobotNum.setEnabled(false);
449
                btnAssignID.setEnabled(false);
450
                btnCommand_MoveTo.setEnabled(false);
451
                btnCommand_MoveAll.setEnabled(false);
452
                    }
453
                };
454
                SwingUtilities.invokeLater(r);
455
        }
456

    
457
        /**
458
        * Parses a String containing a task queue update.
459
        * Format is currently not specified.
460
        * This method currently does nothing.
461
        *
462
        * @param line the String containing task queue update information.
463
        */
464
        public void parseQueue (String line) {
465

    
466
        }
467

    
468
        /**
469
        * Parses a String containing XBee ID values.
470
        * The ColonetServerInterface receives Strings of XBee information.        (For encoding
471
        * information, see the ColonetServerInterface documentation.)         This method takes
472
        * a string of the form "[command code] [command code] [number of robots] [id0] [id1] ..."
473
        * with tokens separated by spaces and containing no brackets.
474
        * The [command code]s are predefined values identifying this String as an XBee
475
        * ID String, [number of robots] is an integer, and the values that follow are
476
        * the IDs of the robots in order, starting with robot 0.        Only [number of robots]
477
        * will be read.         The ID values are saved locally until the next String is parsed.
478
        * The purpose of having this list is to ensure that robots are properly identified for control purposes.
479
        * This keeps robot identification consistent between sessions and prevents arbitrary assignment.
480
        *
481
        * @param line the String containing XBee ID information.
482
        * @throws ArrayIndexOutOfBoundsException if there are fewer than [number of robots] IDs in the String
483
        * @see ColonetServerInterface#sendXBeeIDRequest()
484
        */
485
        public void parseXBeeIDs (String line) {
486
                String [] str = line.split(" ");
487
                int num = Integer.parseInt(str[2]);
488
                xbeeID = new int[num];
489
                for (int i = 0; i < num; i++) {
490
                        xbeeID[i] = Integer.parseInt(str[i+3]);
491
                }
492

    
493
                //update the list of robots to control
494
                //but save the old value first
495
                Object oldSelection = cmbRobotNum.getSelectedItem();
496
                cmbRobotNum.removeAllItems();
497
                cmbRobotNum.addItem(new String("         All         "));
498
                for (int i = 0; i < num; i++) {
499
                        cmbRobotNum.addItem(new String("" + xbeeID[i]));
500
                }
501
                cmbRobotNum.setSelectedItem(oldSelection);
502
                repaint();
503
        }
504

    
505
        /**
506
        * Parses a String containing battery information.
507
        * The ColonetServerInterface receives Strings of battery information.         (For encoding
508
        * information, see the ColonetServerInterface documentation.)         This method takes
509
        * a string of the form "[command code] [command code] [robot ID] [value]"
510
        * with tokens separated by spaces and containing no brackets.
511
        * The [command code]s are predefined values identifying this String as a battery
512
        * information String, [robot ID] is an integer, and [value] is a battery measurement.
513
        * This updates the batery information for a single robot.
514
        *
515
        *
516
        * @param line the String containing battery information.
517
        * @see ColonetServerInterface#sendBatteryRequest(int)
518
        */
519
        public void parseBattery (String line) {
520
            int botNum, level;
521
            System.out.println("Got battery update:" + line);
522
            try {
523
                    String [] str = line.split(" ");
524
                    botNum = Integer.parseInt(str[2]);
525
                    level = Integer.parseInt(str[3]);
526
                } catch (NumberFormatException e) {
527
                    System.out.println("Could not parse battery update");
528
                    return;
529
                }
530

    
531
                RobotIcon r = robotIcons.get(botNum);
532
                if (r != null) {
533
                    r.battery = batteryIcon.convert(level);  // set contextual battery meter
534
                    batteryIcon.setLevel(level);             // set solo battery meter
535
                }
536
                super.repaint();
537
        }
538

    
539
        /**
540
        * Parses a String containing visual robot position information along with
541
        * canonical ID assignments.
542
        */
543
        public void parsePositions (String line) {
544
                String [] str = line.split(" ");
545
                java.util.Map <Integer, RobotIcon> newMap = new HashMap <Integer, RobotIcon> ();
546

    
547
                for (int i = 2; i < str.length; i+=3) {
548
                        int id = Integer.parseInt(str[i]);
549
                        int x = Integer.parseInt(str[i+1]);
550
                        int y = Integer.parseInt(str[i+2]);
551
                        RobotIcon newIcon = new RobotIcon(id, x, y);
552
                        // Save previous battery value
553
                        if (robotIcons.get(id) != null)
554
                            newIcon.battery = robotIcons.get(id).battery;
555
                        if (newIcon.id >= 0) {
556
                                newIcon.color = Color.GREEN;
557
                        }
558
                        newMap.put(id, newIcon);
559
                }
560
                robotIcons = newMap;
561
                repaint();
562
        }
563

    
564
        //
565
        // MouseListener methods
566
        //
567
        public void mousePressed(MouseEvent e) {
568
                //Start a new Thread to handle the MouseEvent
569
                (new MouseHandler(e)).start();
570
                repaint();
571
        }
572
        public void mouseExited(MouseEvent e) {
573
        }
574
        public void mouseEntered(MouseEvent e) {
575
        }
576
        public void mouseReleased(MouseEvent e) {
577
        }
578
        public void mouseClicked(MouseEvent e) {
579
        }
580
        public void mouseDragged(MouseEvent e) {
581
        }
582
        public void mouseMoved(MouseEvent e) {
583
        }
584

    
585
        //
586
        // KeyListener methods
587
        //
588
        public void keyPressed (KeyEvent e) {
589
                //Start a new Thread to handle the KeyEvent
590
                (new KeyHandler(e)).start();
591
                repaint();
592
        }
593
        public void keyReleased (KeyEvent e) {
594
        }
595
        public void keyTyped (KeyEvent e) {
596
        }
597

    
598
        //
599
        // ActionListener method
600
        //
601
        public void actionPerformed (ActionEvent e) {
602
                // Start a new Thread to handle the ActionEvent
603
                (new ActionHandler(e)).start();
604
                repaint();
605
        }
606

    
607
        class MouseHandler extends Thread {
608
                MouseEvent e;
609

    
610
                public MouseHandler (MouseEvent event) {
611
                        super("MouseHandler");
612
                        this.e = event;
613
                }
614

    
615
                public void run () {
616
                        Point pt = panelWebcam.convertClick(e);
617

    
618
                        // If we are selecting a waypoint (destination) for a specific bot
619
                        if (setWaypoint && setWaypointID >= 0) {
620
                                setWaypoint = false;
621
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
622
                                if (selectedBot < 0) {
623
                                        return;
624
                                }
625
                                
626
                                RobotIcon r = robotIcons.get(selectedBot);
627
                                if (r != null) {
628
                                        r.destx = pt.x;
629
                                        r.desty = pt.y;
630
                                        if (csi != null) {
631
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
632
                                        }
633
                                }
634

    
635
                                return;
636
                        }
637

    
638
                        // Right-click also means we are moving a robot
639
                        if (e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3) {
640
                                if (selectedBot < 0) {
641
                                        return;
642
                                }
643

    
644
                                RobotIcon r = robotIcons.get(selectedBot);
645
                                if (r != null) {
646
                                        r.destx = pt.x;
647
                                        r.desty = pt.y;
648
                                        if (csi != null) {
649
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
650
                                        }
651
                                }
652

    
653
                                return;
654
                        }
655

    
656
                        // If we are setting all waypoints
657
                        if (setWaypoint) {
658
                                setWaypoint = false;
659
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
660
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
661
                                        RobotIcon r = entry.getValue();
662
                                        r.destx = pt.x;
663
                                        r.desty = pt.y;
664
                                }
665
                                return;
666
                        }
667

    
668
                        // Otherwise, we are selecting a bot, or doing nothing
669
                        for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
670
                                RobotIcon r = entry.getValue();
671
                                if (r.contains(pt.x, pt.y)) {
672
                                        selectedBot = r.id;
673
                                        lblSelected.setText("" + r.id);
674
                                        // Try to select the clicked bot, if its XBee ID is detected.
675
                                        for (int j = 1; j < cmbRobotNum.getItemCount(); j++) {
676
                                                if (Integer.parseInt(cmbRobotNum.getItemAt(j).toString()) == selectedBot) {
677
                                                        cmbRobotNum.setSelectedIndex(j);
678
                                                }
679
                                        }
680
                                        return;
681
                                }
682
                        }
683

    
684
                }
685
        }
686

    
687
        class KeyHandler extends Thread {
688
                KeyEvent e;
689

    
690
                public KeyHandler (KeyEvent event) {
691
                        super("KeyHandler");
692
                        this.e = event;
693
                }
694

    
695
                public void run () {
696
                        int code = e.getKeyCode();
697
                        if (code == KeyEvent.VK_UP) {
698
                                vectorController.setMaxForward();
699
                                vectorController.sendToServer();
700
                        } else if (code == KeyEvent.VK_DOWN) {
701
                                vectorController.setMaxReverse();
702
                                vectorController.sendToServer();
703
                        } else if (code == KeyEvent.VK_LEFT) {
704
                                vectorController.setMaxLeft();
705
                                vectorController.sendToServer();
706
                        } else if (code == KeyEvent.VK_RIGHT) {
707
                                vectorController.setMaxRight();
708
                                vectorController.sendToServer();
709
                        } else if (code == KeyEvent.VK_S) {
710
                                vectorController.setZero();
711
                                vectorController.sendToServer();
712
                        }
713
                }
714
        }
715

    
716
        class ActionHandler extends Thread {
717
                ActionEvent e;
718

    
719
                public ActionHandler (ActionEvent event) {
720
                        super("ActionHandler");
721
                        this.e = event;
722
                }
723

    
724
                public void run () {
725
                        Object source = e.getSource();
726

    
727
                        // General Actions
728
                        if (source == btnConnect) {
729
                                if (csi == null) {
730
                                        connect();
731
                                } else {
732
                                        disconnect();
733
                                }
734
                        } else if (source == btnGetXBeeIDs) {
735
                                csi.sendXBeeIDRequest();
736
                        } else if (source == btnAssignID) {
737
                                String message;
738
                                if (selectedBot < 0) {
739
                                        message = "That robot is unidentified. Please specify its ID.";
740
                                } else {
741
                                        message = "That robot has ID " + selectedBot + ". You may reassign it now.";
742
                                }
743
                                String result = JOptionPane.showInputDialog(self, message, "Robot Identification", JOptionPane.QUESTION_MESSAGE);
744
                                if (result == null) {
745
                                        return;
746
                                }
747
                                int newID = -1;
748
                                try {
749
                                        newID = Integer.parseInt(result);
750
                                } catch (Exception ex) {
751
                                        csi.warn("Invalid ID.");
752
                                        return;
753
                                }
754
                                // Assign new ID and update display
755
                                if (csi != null) {
756
                                        csi.sendIDAssignment(selectedBot, newID);
757
                                }
758
                                selectedBot = newID;
759
                                lblSelected.setText("" + newID);
760
                        } else if (source == btnF) { // Robot Movement Controls
761
                                vectorController.setMaxForward();
762
                                vectorController.sendToServer();
763
                        } else if (source == btnB) {
764
                                vectorController.setMaxReverse();
765
                                vectorController.sendToServer();
766
                        } else if (source == btnL) {
767
                                vectorController.setMaxLeft();
768
                                vectorController.sendToServer();
769
                        } else if (source == btnR) {
770
                                vectorController.setMaxRight();
771
                                vectorController.sendToServer();
772
                        } else if (source == btnActivate) {
773
                                vectorController.setZero();
774
                                vectorController.sendToServer();
775
                        } else if (source == btnCommand_MoveTo) { // Robot Commands (non-movement)
776
                                if (selectedBot < 0) {
777
                                        return;
778
                                }
779
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
780
                                setWaypoint = true;
781
                                setWaypointID = selectedBot;
782
                        } else if (source == btnCommand_MoveAll) {
783
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
784
                                setWaypoint = true;
785
                                setWaypointID = -1;
786
                        } else if (source == btnCommand_StopTask) {
787

    
788
                        } else if (source == btnCommand_ResumeTask) {
789

    
790
                        } else if (source == btnCommand_ChargeNow) {
791

    
792
                        } else if (source == btnCommand_StopCharging) {
793

    
794
                        } else if (source == btnAddTask) { // Queue Management
795
                                taskAddWindow.prompt();
796
                        } else if (source == btnRemoveTask) {
797
                                if (taskList.getSelectedIndex() >= 0) {
798
                                        csi.sendQueueRemove(taskList.getSelectedIndex());
799
                                }
800
                                csi.sendQueueUpdate();
801
                        } else if (source == btnMoveTaskUp) {
802
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() - 1);
803
                                csi.sendQueueUpdate();
804
                        } else if (source == btnMoveTaskDown) {
805
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() + 1);
806
                                csi.sendQueueUpdate();
807
                        } else if (source == btnUpdateTasks) {
808
                                csi.sendQueueUpdate();
809
                        }
810

    
811
                }
812
        }
813

    
814
        /*
815
        * DataUpdater thread.
816
        *        The purpose of this thread is to request data from the server at regular intervals.
817
        *        Types of data requested: XBee IDs, robot positions, battery levels.
818
        *
819
        */
820
        class DataUpdater extends Thread {
821
                final int DATAUPDATER_DELAY = 200;
822
                int count;
823
                public DataUpdater () {
824
                        super("Colonet DataUpdater");
825
                        count = 0;
826
                }
827

    
828
                public synchronized void run () {
829
                        while (true) {
830
                                try {
831
                                        if (csi != null && csi.isReady()) {
832
                                                // XBee ID Request
833
                                                if (count % 5 == 0) {
834
                                                        csi.sendXBeeIDRequest();
835
                                                }
836
                                                // Robot Position Request
837
                                                if (count % 1 == 0) {
838
                                                        csi.sendPositionRequest();
839
                                                }
840
                                                // Battery Request
841
                                                if (count % 30 == 0) {
842
                                                        for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
843
                                                                RobotIcon r = entry.getValue();
844
                                                                        int id = r.id;
845
                                                                                if (id >= 0) {
846
                                                                                csi.sendBatteryRequest(id);
847
                                                                                System.out.println("Sent battery request (" + id + ")");
848
                                                                        }
849
                                                        }
850
                                                }
851
                                        }
852
                                        count++;
853
                                        if (count > 1000)
854
                                                count = 0;
855
                                        Thread.sleep(DATAUPDATER_DELAY);
856
                                } catch (InterruptedException e) {
857
                                        return;
858
                                } catch (NullPointerException e) {
859
                                    // This probably indicates that an object was destroyed by shutdown.
860
                                    return;
861
                                }
862
                        }
863
                }
864
        }
865

    
866

    
867
        /*
868
        * WebcamPanel class
869
        * Enables more efficient image handling in a component-controlled environment
870
        */
871
        class WebcamPanel extends JPanel {
872
                final int BORDER = 16;        // this is arbitrary. it makes the image look nice inside a border.
873
                final int BATTERY_WIDTH = 50;
874
                final int BATTERY_HEIGHT = 10;
875
                volatile BufferedImage img;
876
                BufferedImage buffer;
877

    
878
                public WebcamPanel () {
879
                        super(true);
880
                }
881

    
882
                public synchronized void setImage (BufferedImage newimg) {
883
                        if (img != null) {
884
                                img.flush();
885
                        }
886
                        System.gc();
887
                        img = newimg;
888
                        repaint();
889
                }
890

    
891
                public synchronized void paint (Graphics g) {
892
                        if (img == null) {
893
                                return;
894
                        }
895

    
896
                        // Calculate scaling
897
                        int maxWidth = getWidth() - 2*BORDER;
898
                        int maxHeight = getHeight() - 2*BORDER;
899
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
900
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
901
                        double scale = 0;
902
                        int newWidth = 0;
903
                        int newHeight = 0;
904
                        int x = 0;
905
                        int y = 0;
906

    
907
                        if (widthRatio > heightRatio) {         //height is the limiting factor
908
                                scale = heightRatio;
909
                                newHeight = maxHeight;
910
                                newWidth = (int) (img.getWidth() * scale);
911
                                y = BORDER;
912
                                x = (maxWidth - newWidth) / 2 + BORDER;
913
                        } else {        //width is the limiting factor
914
                                scale = widthRatio;
915
                                newWidth = maxWidth;
916
                                newHeight = (int) (img.getHeight() * scale);
917
                                x = BORDER;
918
                                y = (maxHeight - newHeight) / 2 + BORDER;
919
                        }
920

    
921
                        // Draw everything onto the buffer
922
                        buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
923
                        Graphics2D bufferedGraphics = (Graphics2D)buffer.getGraphics();
924
                        bufferedGraphics.setColor(Color.GRAY);
925
                        bufferedGraphics.fillRect(0, 0, this.getWidth(), this.getHeight());
926
                        Image imgScaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST);
927
                        bufferedGraphics.drawImage(imgScaled, x, y, this);
928

    
929

    
930
                        // Draw Identifiers and battery levels
931
                        if (robotIcons != null) {
932
                                bufferedGraphics.setStroke(new BasicStroke(2));
933
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
934
                                        RobotIcon r = entry.getValue();
935
                                        bufferedGraphics.setColor(r.color);
936
                                        // Identifier circle
937
                                        int px = (int) (x + r.x * scale);
938
                                        int py = (int) (y + r.y * scale);
939
                                        bufferedGraphics.drawOval(px-RADIUS, py-RADIUS, 2*r.RADIUS, 2*r.RADIUS);
940
                                        // Battery
941
                                        if (r.battery >= 0) {
942
                                            int pixels = r.battery * BATTERY_WIDTH / 100;  // 160 should be fully charged
943
                                                if (r.battery > 50)
944
                                                    bufferedGraphics.setColor(Color.GREEN);
945
                                                else if (r.battery > 25)
946
                                                    bufferedGraphics.setColor(Color.YELLOW);
947
                                                else
948
                                                    bufferedGraphics.setColor(Color.RED);
949
                                                bufferedGraphics.fillRect(px+20, py+20, pixels, BATTERY_HEIGHT);
950
                                                bufferedGraphics.setColor(Color.BLACK);
951
                                                bufferedGraphics.drawRect(px+20, py+20, BATTERY_WIDTH, BATTERY_HEIGHT);
952
                                        }
953
                                        // If the robot has a destination, draw the vector
954
                                        if (r.destx >= 0) {
955
                                                bufferedGraphics.drawLine(px, py, (int)(x + r.destx * scale), (int)(y + r.desty * scale));
956
                                        }
957
                                }
958
                        }
959

    
960
                        // Identify currently-selected robot
961
                        RobotIcon r = robotIcons.get(selectedBot);
962
                        if (r != null) {
963
                                int px = (int) (x + r.x * scale);
964
                                int py = (int) (y + r.y * scale);
965
                                bufferedGraphics.setColor(Color.BLACK);
966
                                bufferedGraphics.drawOval(px-RADIUS-6, py-RADIUS-6, 2*r.RADIUS+12, 2*r.RADIUS+12);
967
                        }
968

    
969
                        //Display buffered content
970
                        g.drawImage(buffer, 0, 0, this);
971
                }
972

    
973
                /**
974
                * Convert a click on the webcam panel to a coordinate that is consistent with the
975
                * original size of the image that the panel contains.
976
                */
977
                public Point convertClick (MouseEvent e) {
978
                        if (img == null) {
979
                                return new Point(e.getX(), e.getY());
980
                        }
981

    
982
                        // Calculate scaling
983
                        int clickx = e.getX();
984
                        int clicky = e.getY();
985
                        int maxWidth = getWidth() - 2*BORDER;
986
                        int maxHeight = getHeight() - 2*BORDER;
987
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
988
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
989
                        double scale = 0;
990
                        int newWidth = 0;
991
                        int newHeight = 0;
992
                        int px = 0;
993
                        int py = 0;
994

    
995
                        if (widthRatio > heightRatio) {         //height is the limiting factor
996
                                scale = heightRatio;
997
                                newHeight = maxHeight;
998
                                newWidth = (int) (img.getWidth() * scale);
999
                                py = clicky - BORDER;
1000
                                px = clickx - BORDER - (maxWidth - newWidth) / 2;
1001
                        } else {        //width is the limiting factor
1002
                                scale = widthRatio;
1003
                                newWidth = maxWidth;
1004
                                newHeight = (int) (img.getHeight() * scale);
1005
                                px = clickx - BORDER;
1006
                                py = clicky - BORDER - (maxHeight - newHeight) / 2;
1007
                        }
1008
                        py = (int) (py / scale);
1009
                        px = (int) (px / scale);
1010
                        return new Point(px, py);
1011
                }
1012
        }
1013

    
1014
        /*
1015
        * WebcamLoader class
1016
        * Handles the loading of the webcam image.
1017
        */
1018
        class WebcamLoader extends Thread
1019
        {
1020
                final int WEBCAMLOADER_DELAY = 250;
1021
                final String IMAGE_PATH = "http://128.2.99.176/colonet.jpg";
1022

    
1023
                URL imagePath;
1024

    
1025
                MediaTracker mt;
1026
                BufferedImage image;
1027
                Random rand;
1028

    
1029
                public WebcamLoader (JApplet applet)
1030
                {
1031
                        super("ColonetWebcamLoader");
1032
                        mt = new MediaTracker(applet);
1033
                        ImageIO.setUseCache(false);
1034
                        rand = new Random();
1035
                }
1036

    
1037
                public void run ()
1038
                {
1039
                        while (true) {
1040
                                try {
1041
                                        Thread.sleep(WEBCAMLOADER_DELAY);
1042
                                        if (image != null)
1043
                                                image.flush();
1044
                                        System.gc();
1045
                                        try {
1046
                                                imagePath = new URL(IMAGE_PATH + "?rand=" + rand.nextInt(100000));
1047
                                        } catch (MalformedURLException e) {
1048
                                                System.out.println("Malformed URL: could not form URL from: [" + IMAGE_PATH + "]\n");
1049
                                        }
1050
                                        image = ImageIO.read(imagePath);
1051
                                        // The MediaTracker waitForID pauses the thread until the image is loaded.
1052
                                        // We don't want to display a half-downloaded image.
1053
                                        mt.addImage(image, 1);
1054
                                        mt.waitForID(1);
1055
                                        mt.removeImage(image);
1056
                                        // Save
1057
                                        panelWebcam.setImage(image);
1058
                                } catch (InterruptedException e) {
1059
                                        return;
1060
                                } catch (java.security.AccessControlException e) {
1061
                                        csi.warn("Could not load webcam.\n" + e);
1062
                                        return;
1063
                                } catch (IOException e) {
1064
                                        getInfoPanel().append("IOException while trying to load image.");
1065
                                }
1066
                        }
1067
                }
1068
        }
1069

    
1070
}