Project

General

Profile

Statistics
| Revision:

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

History | View | Annotate | Download (34.2 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 btnSetBounds;
59
  RobotBoundary boundary;
60
        JButton btnCommand_MoveTo;
61
        JButton btnCommand_MoveAll;
62
        JButton btnCommand_StopTask;
63
        JButton btnCommand_ResumeTask;
64
        JButton btnCommand_ChargeNow;
65
        JButton btnCommand_StopCharging;
66

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

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

    
92
        Colonet self = this;
93
        volatile ColonetServerInterface csi;
94
        Thread paintThread;
95

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

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

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

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

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

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

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

    
172
                // Robot direction panel
173
                panelRobotDirection = new JPanel();
174
                panelRobotDirectionButtons = new JPanel();
175

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

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

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

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

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

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

    
280

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

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

    
337
        public void paint (Graphics g) {
338
                super.paint(g);
339
        }
340

    
341
        public void update (Graphics g) {
342
                paint(g);
343
        }
344
        
345
        public void run () {
346
                final int PAINT_DELAY = 300;
347
                while (true) {
348
                        try {
349
                                Thread.sleep(PAINT_DELAY);
350
                        } catch (Exception e) {
351
                                System.out.println(e);
352
                                return;
353
                        }
354
                        repaint();
355
                }
356
        }
357

    
358
        /**
359
        * Gets the JTextArea used for displaying debugging information. 
360
        *
361
        * @return the JTextArea where debugging information can be displayed.
362
        */
363
        public JTextArea getInfoPanel () {
364
                return txtInfo;
365
        }
366

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

    
396
                                if (j < num - 1) {
397
                                        txtInfo.append(" ");
398
                                }
399
                        }
400

    
401
                        if (i < num - 1) {
402
                                txtInfo.append("\n");
403
                        }
404
                }
405
                repaint();
406
        }
407

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

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

    
463
        /**
464
        * Parses a String containing a task queue update.
465
        * Format is currently not specified.
466
        * This method currently does nothing.
467
        *
468
        * @param line the String containing task queue update information.
469
        */
470
        public void parseQueue (String line) {
471

    
472
        }
473

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

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

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

    
537
                RobotIcon r = robotIcons.get(botNum);
538
                if (r != null) {
539
                    r.battery = batteryIcon.convert(level);  // set contextual battery meter
540
                    batteryIcon.setLevel(level);             // set solo battery meter
541
                }
542
                super.repaint();
543
        }
544

    
545
        /**
546
        * Parses a String containing visual robot position information along with
547
        * canonical ID assignments.
548
        */
549
        public void parsePositions (String line) {
550
                String [] str = line.split(" ");
551
                java.util.Map <Integer, RobotIcon> newMap = new HashMap <Integer, RobotIcon> ();
552

    
553
                for (int i = 2; i < str.length; i+=3) {
554
                        int id = Integer.parseInt(str[i]);
555
                        int x = Integer.parseInt(str[i+1]);
556
                        int y = Integer.parseInt(str[i+2]);
557
                        RobotIcon newIcon = new RobotIcon(id, x, y);
558
                        // Save previous robot information
559
                        RobotIcon oldIcon = robotIcons.get(id);
560
                        if (oldIcon != null) {
561
                            newIcon.battery = oldIcon.battery;
562
                                newIcon.destx = oldIcon.destx;
563
                                newIcon.desty = oldIcon.desty;
564
                        }
565
                        if (newIcon.id >= 0) {
566
                                newIcon.color = Color.GREEN;
567
                        }
568
                        newMap.put(id, newIcon);
569
                }
570
                robotIcons = newMap;
571
                repaint();
572
        }
573

    
574
        //
575
        // MouseListener methods
576
        //
577
        public void mousePressed(MouseEvent e) {
578
                //Start a new Thread to handle the MouseEvent
579
                (new MouseHandler(e)).start();
580
                repaint();
581
        }
582
        public void mouseExited(MouseEvent e) {
583
        }
584
        public void mouseEntered(MouseEvent e) {
585
        }
586
        public void mouseReleased(MouseEvent e) {
587
    (new MouseHandler(e)).start();
588
                repaint();
589
        }
590
        public void mouseClicked(MouseEvent e) {
591
        }
592
        public void mouseDragged(MouseEvent e) {
593
    (new MouseHandler(e)).start();
594
                repaint();
595
        }
596
        public void mouseMoved(MouseEvent e) {
597
        }
598

    
599
        //
600
        // KeyListener methods
601
        //
602
        public void keyPressed (KeyEvent e) {
603
                //Start a new Thread to handle the KeyEvent
604
                (new KeyHandler(e)).start();
605
                repaint();
606
        }
607
        public void keyReleased (KeyEvent e) {
608
        }
609
        public void keyTyped (KeyEvent e) {
610
        }
611

    
612
        //
613
        // ActionListener method
614
        //
615
        public void actionPerformed (ActionEvent e) {
616
                // Start a new Thread to handle the ActionEvent
617
                (new ActionHandler(e)).start();
618
                repaint();
619
        }
620

    
621
        class MouseHandler extends Thread {
622
                MouseEvent e;
623

    
624
                public MouseHandler (MouseEvent event) {
625
                        super("MouseHandler");
626
                        this.e = event;
627
                }
628

    
629
                public void run () {
630
                        Point pt = panelWebcam.convertClick(e);
631

    
632
                        // If we are selecting a waypoint (destination) for a specific bot
633
                        if (setWaypoint && setWaypointID >= 0 && e.getID() == MouseEvent.MOUSE_PRESSED) {
634
                                setWaypoint = false;
635
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
636
                                if (selectedBot < 0) {
637
                                        return;
638
                                }
639
                                
640
                                RobotIcon r = robotIcons.get(selectedBot);
641
                                if (r != null) {
642
                                        r.destx = pt.x;
643
                                        r.desty = pt.y;
644
                                        if (csi != null) {
645
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
646
                                        }
647
                                }
648
                                return;
649
                        }
650

    
651
                        // Right-click also means we are moving a robot
652
                        if ((e.getButton() == MouseEvent.BUTTON2 || e.getButton() == MouseEvent.BUTTON3)  
653
          && e.getID() == MouseEvent.MOUSE_PRESSED) {
654
                                if (selectedBot < 0) {
655
                                        return;
656
                                }
657

    
658
                                RobotIcon r = robotIcons.get(selectedBot);
659
                                if (r != null) {
660
                                        r.destx = pt.x;
661
                                        r.desty = pt.y;
662
                                        if (csi != null) {
663
                                                csi.sendAbsoluteMove(r.id, r.destx, r.desty);
664
                                        }
665
                                }
666
                                return;
667
                        }
668

    
669
                        // If we are setting all waypoints
670
                        if (setWaypoint  && e.getID() == MouseEvent.MOUSE_PRESSED) {
671
                                setWaypoint = false;
672
                                panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
673
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
674
                                        RobotIcon r = entry.getValue();
675
                                        r.destx = pt.x;
676
                                        r.desty = pt.y;
677
                                }
678
                                return;
679
                        }
680
      
681
      // If we are drawing a boundary rectangle
682
      if (boundary.set) {
683
        // Begin 
684
        if (e.getID() == MouseEvent.MOUSE_PRESSED) {
685
          boundary.p1 = new Point(pt.x, pt.y);
686
          boundary.p2 = new Point(pt.x, pt.y);
687
        }
688
        // Resize
689
        else if (e.getID() == MouseEvent.MOUSE_DRAGGED){
690
          boundary.p2 = new Point(pt.x, pt.y);
691
        }
692
        // End
693
        else if (e.getID() == MouseEvent.MOUSE_RELEASED){
694
          boundary.p2 = new Point(pt.x, pt.y);
695
          panelWebcam.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
696
          boundary.set = false;
697
          boundary.active = true;
698
          boundary.sendToServer();
699
        }
700
        return;
701
      }
702

    
703
                        // Otherwise, we are selecting a bot, or doing nothing
704
                        for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
705
                                RobotIcon r = entry.getValue();
706
                                if (r.contains(pt.x, pt.y)) {
707
                                        selectedBot = r.id;
708
                                        lblSelected.setText("" + r.id);
709
                                        // Try to select the clicked bot, if its XBee ID is detected.
710
                                        for (int j = 1; j < cmbRobotNum.getItemCount(); j++) {
711
                                                if (Integer.parseInt(cmbRobotNum.getItemAt(j).toString()) == selectedBot) {
712
                                                        cmbRobotNum.setSelectedIndex(j);
713
                                                }
714
                                        }
715
                                        return;
716
                                }
717
                        }
718

    
719
                }
720
        }
721

    
722
        class KeyHandler extends Thread {
723
                KeyEvent e;
724

    
725
                public KeyHandler (KeyEvent event) {
726
                        super("KeyHandler");
727
                        this.e = event;
728
                }
729

    
730
                public void run () {
731
                        int code = e.getKeyCode();
732
                        if (code == KeyEvent.VK_UP) {
733
                                vectorController.setMaxForward();
734
                                vectorController.sendToServer();
735
                        } else if (code == KeyEvent.VK_DOWN) {
736
                                vectorController.setMaxReverse();
737
                                vectorController.sendToServer();
738
                        } else if (code == KeyEvent.VK_LEFT) {
739
                                vectorController.setMaxLeft();
740
                                vectorController.sendToServer();
741
                        } else if (code == KeyEvent.VK_RIGHT) {
742
                                vectorController.setMaxRight();
743
                                vectorController.sendToServer();
744
                        } else if (code == KeyEvent.VK_S) {
745
                                vectorController.setZero();
746
                                vectorController.sendToServer();
747
                        }
748
                }
749
        }
750

    
751
        class ActionHandler extends Thread {
752
                ActionEvent e;
753

    
754
                public ActionHandler (ActionEvent event) {
755
                        super("ActionHandler");
756
                        this.e = event;
757
                }
758

    
759
                public void run () {
760
                        Object source = e.getSource();
761

    
762
                        // General Actions
763
                        if (source == btnConnect) {
764
                                if (csi == null) {
765
                                        connect();
766
                                } else {
767
                                        disconnect();
768
                                }
769
                        } else if (source == btnGetXBeeIDs) {
770
                                csi.sendXBeeIDRequest();
771
                        } else if (source == btnAssignID) {
772
                                String message;
773
                                if (selectedBot < 0) {
774
                                        message = "That robot is unidentified. Please specify its ID.";
775
                                } else {
776
                                        message = "That robot has ID " + selectedBot + ". You may reassign it now.";
777
                                }
778
                                String result = JOptionPane.showInputDialog(self, message, "Robot Identification", JOptionPane.QUESTION_MESSAGE);
779
                                if (result == null) {
780
                                        return;
781
                                }
782
                                int newID = -1;
783
                                try {
784
                                        newID = Integer.parseInt(result);
785
                                } catch (Exception ex) {
786
                                        csi.warn("Invalid ID.");
787
                                        return;
788
                                }
789
                                // Assign new ID and update display
790
                                if (csi != null) {
791
                                        csi.sendIDAssignment(selectedBot, newID);
792
                                }
793
                                selectedBot = newID;
794
                                lblSelected.setText("" + newID);
795
                        } else if (source == btnF) { // Robot Movement Controls
796
                                vectorController.setMaxForward();
797
                                vectorController.sendToServer();
798
                        } else if (source == btnB) {
799
                                vectorController.setMaxReverse();
800
                                vectorController.sendToServer();
801
                        } else if (source == btnL) {
802
                                vectorController.setMaxLeft();
803
                                vectorController.sendToServer();
804
                        } else if (source == btnR) {
805
                                vectorController.setMaxRight();
806
                                vectorController.sendToServer();
807
                        } else if (source == btnActivate) {
808
                                vectorController.setZero();
809
                                vectorController.sendToServer();
810
      } else if (source == btnSetBounds) {
811
        boundary.set = true;
812
        panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
813
                        } else if (source == btnCommand_MoveTo) {
814
                                if (selectedBot < 0) {
815
                                        return;
816
                                }
817
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
818
                                setWaypoint = true;
819
                                setWaypointID = selectedBot;
820
                        } else if (source == btnCommand_MoveAll) {
821
                                panelWebcam.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
822
                                setWaypoint = true;
823
                                setWaypointID = -1;
824
                        } else if (source == btnCommand_StopTask) {
825

    
826
                        } else if (source == btnCommand_ResumeTask) {
827

    
828
                        } else if (source == btnCommand_ChargeNow) {
829

    
830
                        } else if (source == btnCommand_StopCharging) {
831

    
832
                        } else if (source == btnAddTask) { // Queue Management
833
                                taskAddWindow.prompt();
834
                        } else if (source == btnRemoveTask) {
835
                                if (taskList.getSelectedIndex() >= 0) {
836
                                        csi.sendQueueRemove(taskList.getSelectedIndex());
837
                                }
838
                                csi.sendQueueUpdate();
839
                        } else if (source == btnMoveTaskUp) {
840
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() - 1);
841
                                csi.sendQueueUpdate();
842
                        } else if (source == btnMoveTaskDown) {
843
                                csi.sendQueueReorder(taskList.getSelectedIndex(), taskList.getSelectedIndex() + 1);
844
                                csi.sendQueueUpdate();
845
                        } else if (source == btnUpdateTasks) {
846
                                csi.sendQueueUpdate();
847
                        }
848

    
849
                }
850
        }
851

    
852
        /*
853
        * DataUpdater thread.
854
        *        The purpose of this thread is to request data from the server at regular intervals.
855
        *        Types of data requested: XBee IDs, robot positions, battery levels.
856
        *
857
        */
858
        class DataUpdater extends Thread {
859
                final int DATAUPDATER_DELAY = 200;
860
                int count;
861
                public DataUpdater () {
862
                        super("Colonet DataUpdater");
863
                        count = 0;
864
                }
865

    
866
                public synchronized void run () {
867
                        while (true) {
868
                                try {
869
                                        if (csi != null && csi.isReady()) {
870
                                                // XBee ID Request
871
                                                if (count % 5 == 0) {
872
                                                        csi.sendXBeeIDRequest();
873
                                                }
874
                                                // Robot Position Request
875
                                                if (count % 1 == 0) {
876
                                                        csi.sendPositionRequest();
877
                                                }
878
                                                // Battery Request
879
                                                if (count % 30 == 0) {
880
                                                        for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
881
                                                                RobotIcon r = entry.getValue();
882
                                                                        int id = r.id;
883
                                                                                if (id >= 0) {
884
                                                                                csi.sendBatteryRequest(id);
885
                                                                                System.out.println("Sent battery request (" + id + ")");
886
                                                                        }
887
                                                        }
888
                                                }
889
                                        }
890
                                        count++;
891
                                        if (count > 1000)
892
                                                count = 0;
893
                                        Thread.sleep(DATAUPDATER_DELAY);
894
                                } catch (InterruptedException e) {
895
                                        return;
896
                                } catch (NullPointerException e) {
897
                                    // This probably indicates that an object was destroyed by shutdown.
898
                                    return;
899
                                }
900
                        }
901
                }
902
        }
903

    
904

    
905
        /*
906
        * WebcamPanel class
907
        * Enables more efficient image handling in a component-controlled environment
908
        */
909
        class WebcamPanel extends JPanel {
910
                final int BORDER = 16;        // this is arbitrary. it makes the image look nice inside a border.
911
                final int BATTERY_WIDTH = 50;
912
                final int BATTERY_HEIGHT = 10;
913
                volatile BufferedImage img;
914
                BufferedImage buffer;
915

    
916
                public WebcamPanel () {
917
                        super(true);
918
                }
919

    
920
                public synchronized void setImage (BufferedImage newimg) {
921
                        if (img != null) {
922
                                img.flush();
923
                        }
924
                        System.gc();
925
                        img = newimg;
926
                        repaint();
927
                }
928

    
929
                public synchronized void paint (Graphics g) {
930
                        if (img == null) {
931
                                return;
932
                        }
933

    
934
                        // Calculate scaling
935
                        int maxWidth = getWidth() - 2*BORDER;
936
                        int maxHeight = getHeight() - 2*BORDER;
937
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
938
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
939
                        double scale = 0;
940
                        int newWidth = 0;
941
                        int newHeight = 0;
942
                        int x = 0;
943
                        int y = 0;
944

    
945
                        if (widthRatio > heightRatio) {         //height is the limiting factor
946
                                scale = heightRatio;
947
                                newHeight = maxHeight;
948
                                newWidth = (int) (img.getWidth() * scale);
949
                                y = BORDER;
950
                                x = (maxWidth - newWidth) / 2 + BORDER;
951
                        } else {        //width is the limiting factor
952
                                scale = widthRatio;
953
                                newWidth = maxWidth;
954
                                newHeight = (int) (img.getHeight() * scale);
955
                                x = BORDER;
956
                                y = (maxHeight - newHeight) / 2 + BORDER;
957
                        }
958

    
959
                        // Draw image onto the buffer
960
                        buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
961
                        Graphics2D bufferedGraphics = (Graphics2D)buffer.getGraphics();
962
                        bufferedGraphics.setColor(Color.GRAY);
963
                        bufferedGraphics.fillRect(0, 0, this.getWidth(), this.getHeight());
964
                        Image imgScaled = img.getScaledInstance(newWidth, newHeight, Image.SCALE_FAST);
965
                        bufferedGraphics.drawImage(imgScaled, x, y, this);
966

    
967
      // Draw boundary
968
      if (boundary.set || boundary.active) {
969
        bufferedGraphics.setColor(Color.BLUE);
970
        bufferedGraphics.drawRect(boundary.p1.x, boundary.p1.y, boundary.p2.x, boundary.p2.y);
971
      }
972

    
973
                        // Draw Identifiers and battery levels
974
                        if (robotIcons != null) {
975
                                bufferedGraphics.setStroke(new BasicStroke(2));
976
                                for (Map.Entry<Integer,RobotIcon> entry : robotIcons.entrySet()) {
977
                                        RobotIcon r = entry.getValue();
978
                                        bufferedGraphics.setColor(r.color);
979
                                        // Identifier circle
980
                                        int px = (int) (x + r.x * scale);
981
                                        int py = (int) (y + r.y * scale);
982
                                        bufferedGraphics.drawOval(px-RADIUS, py-RADIUS, 2*r.RADIUS, 2*r.RADIUS);
983
                                        // Battery
984
                                        if (r.battery >= 0) {
985
                                            int pixels = r.battery * BATTERY_WIDTH / 100;  // 160 should be fully charged
986
                                                if (r.battery > 50)
987
                                                    bufferedGraphics.setColor(Color.GREEN);
988
                                                else if (r.battery > 25)
989
                                                    bufferedGraphics.setColor(Color.YELLOW);
990
                                                else
991
                                                    bufferedGraphics.setColor(Color.RED);
992
                                                bufferedGraphics.fillRect(px+20, py+20, pixels, BATTERY_HEIGHT);
993
                                                bufferedGraphics.setColor(Color.BLACK);
994
                                                bufferedGraphics.drawRect(px+20, py+20, BATTERY_WIDTH, BATTERY_HEIGHT);
995
                                        }
996
                                        // If the robot has a destination, draw the vector
997
                                        if (r.destx >= 0) {
998
                                                bufferedGraphics.drawLine(px, py, (int)(x + r.destx * scale), (int)(y + r.desty * scale));
999
                                        }
1000
                                }
1001
                        }
1002

    
1003
                        // Identify currently-selected robot
1004
                        RobotIcon r = robotIcons.get(selectedBot);
1005
                        if (r != null) {
1006
                                int px = (int) (x + r.x * scale);
1007
                                int py = (int) (y + r.y * scale);
1008
                                bufferedGraphics.setColor(Color.BLACK);
1009
                                bufferedGraphics.drawOval(px-RADIUS-6, py-RADIUS-6, 2*r.RADIUS+12, 2*r.RADIUS+12);
1010
                        }
1011

    
1012
                        //Display buffered content
1013
                        g.drawImage(buffer, 0, 0, this);
1014
                }
1015

    
1016
                /**
1017
                * Convert a click on the webcam panel to a coordinate that is consistent with the
1018
                * original size of the image that the panel contains.
1019
                */
1020
                public Point convertClick (MouseEvent e) {
1021
                        if (img == null) {
1022
                                return new Point(e.getX(), e.getY());
1023
                        }
1024

    
1025
                        // Calculate scaling
1026
                        int clickx = e.getX();
1027
                        int clicky = e.getY();
1028
                        int maxWidth = getWidth() - 2*BORDER;
1029
                        int maxHeight = getHeight() - 2*BORDER;
1030
                        double widthRatio = 1.0 * maxWidth / img.getWidth();
1031
                        double heightRatio = 1.0 * maxHeight / img.getHeight();
1032
                        double scale = 0;
1033
                        int newWidth = 0;
1034
                        int newHeight = 0;
1035
                        int px = 0;
1036
                        int py = 0;
1037

    
1038
                        if (widthRatio > heightRatio) {         //height is the limiting factor
1039
                                scale = heightRatio;
1040
                                newHeight = maxHeight;
1041
                                newWidth = (int) (img.getWidth() * scale);
1042
                                py = clicky - BORDER;
1043
                                px = clickx - BORDER - (maxWidth - newWidth) / 2;
1044
                        } else {        //width is the limiting factor
1045
                                scale = widthRatio;
1046
                                newWidth = maxWidth;
1047
                                newHeight = (int) (img.getHeight() * scale);
1048
                                px = clickx - BORDER;
1049
                                py = clicky - BORDER - (maxHeight - newHeight) / 2;
1050
                        }
1051
                        py = (int) (py / scale);
1052
                        px = (int) (px / scale);
1053
                        return new Point(px, py);
1054
                }
1055
        }
1056

    
1057
        /*
1058
        * WebcamLoader class
1059
        * Handles the loading of the webcam image.
1060
        */
1061
        class WebcamLoader extends Thread
1062
        {
1063
                final int WEBCAMLOADER_DELAY = 250;
1064
                final String IMAGE_PATH = "http://128.2.99.176/colonet.jpg";
1065

    
1066
                URL imagePath;
1067

    
1068
                MediaTracker mt;
1069
                BufferedImage image;
1070
                Random rand;
1071

    
1072
                public WebcamLoader (JApplet applet)
1073
                {
1074
                        super("ColonetWebcamLoader");
1075
                        mt = new MediaTracker(applet);
1076
                        ImageIO.setUseCache(false);
1077
                        rand = new Random();
1078
                }
1079

    
1080
                public void run ()
1081
                {
1082
                        while (true) {
1083
                                try {
1084
                                        Thread.sleep(WEBCAMLOADER_DELAY);
1085
                                        if (image != null)
1086
                                                image.flush();
1087
                                        System.gc();
1088
                                        try {
1089
                                                imagePath = new URL(IMAGE_PATH + "?rand=" + rand.nextInt(100000));
1090
                                        } catch (MalformedURLException e) {
1091
                                                System.out.println("Malformed URL: could not form URL from: [" + IMAGE_PATH + "]\n");
1092
                                        }
1093
                                        image = ImageIO.read(imagePath);
1094
                                        // The MediaTracker waitForID pauses the thread until the image is loaded.
1095
                                        // We don't want to display a half-downloaded image.
1096
                                        mt.addImage(image, 1);
1097
                                        mt.waitForID(1);
1098
                                        mt.removeImage(image);
1099
                                        // Save
1100
                                        panelWebcam.setImage(image);
1101
                                } catch (InterruptedException e) {
1102
                                        return;
1103
                                } catch (java.security.AccessControlException e) {
1104
                                        csi.warn("Could not load webcam.\n" + e);
1105
                                        return;
1106
                                } catch (IOException e) {
1107
                                        getInfoPanel().append("IOException while trying to load image.");
1108
                                }
1109
                        }
1110
                }
1111
        }
1112
  
1113
  class RobotBoundary {
1114
    public boolean active, set;
1115
    public Point p1, p2;
1116
    
1117
    public RobotBoundary () {
1118
      active = false;
1119
      set = false;
1120
      p1 = new Point(-1,-1);
1121
      p2 = new Point(-1,-1);
1122
    }
1123
    
1124
    public void sendToServer () {
1125
      //TODO
1126
    }
1127
    
1128
  }
1129

    
1130
}