001    //  edu.isi.gamebots.clients.BotRunnerApp
002    //  Copyright 2000, University of Southern California,
003    //                  Information Science Institute
004    //
005    //  Personal and Educational use is hereby granted.
006    //  Permission required for commercial use and redistribution.
007    
008    
009    package edu.isi.gamebots.client;
010    
011    import java.awt.BorderLayout;
012    import java.awt.Color;
013    import java.awt.Component;
014    import java.awt.Container;
015    import java.awt.Cursor;
016    import java.awt.Dimension;
017    import java.awt.FlowLayout;
018    import java.awt.Font;
019    import java.awt.GridBagLayout;
020    import java.awt.GridBagConstraints;
021    import java.awt.Insets;
022    //import java.awt.*;
023    import java.awt.event.*;
024    import java.io.*;
025    import java.lang.*;
026    import java.net.*;
027    import java.util.*;
028    
029    import javax.swing.*;
030    import javax.swing.border.*;
031    import javax.swing.event.*;
032    
033    import us.ca.la.anm.gui.*;
034    import us.ca.la.anm.util.io.*;
035    
036    import edu.isi.gamebots.client.*;
037    import edu.isi.gamebots.examples.*;
038    
039    
040    /**
041     *  This application is a test platform for any Bot or VizTool implementation.
042     *  It provides a simple user interface for loading bot implementations, setting
043     *  the server address and port, and connecting and disconnecting the clients.
044     *
045     *  <h3>Future Goals:</h3>
046     *  <ul>
047     *    <li>Finish the Bot menu.  Allow for individual Bot connection,
048     *        reconnection, and disconnection, as well as the biligty to unload a
049     *        bot from the team.
050     *    <li>Add support for serializable Bots (load Bot from file, save Bot).
051     *  </ul>
052     *
053     *  @author <a href="mailto:amarshal#gamebots@isi.edu">Andrew n marshall</a>
054     */
055    public class BotRunnerApp implements GamebotsConstants {
056      // Private Constants
057      ///////////////////////////////////////////////////////////////////////////
058      protected static final int MAX_CONNECTION_ATTEMPTS = 2;
059      protected static final long TIMEOUT = 10000; // 10 seconds
060    
061      protected static final String DATA_CARD = "data";  // For AddBotDialog
062      protected static final String ERROR_CARD = "error";  // For AddBotDialog
063    
064      protected static final String BOT = "Bot";
065      protected static final String VIZ = "VizTool";
066    
067      // Private Data
068      ///////////////////////////////////////////////////////////////////////////
069      protected JTextAreaLog log = new JTextAreaLog();
070    
071      protected JFrame frame;
072      protected JMenu botMenu;
073      protected JTabbedPane tabbedPane;
074      protected JPanel serverPanel;
075      protected JTextArea text;
076      protected JLabel status;
077    
078      protected JTextAreaLog curLogForText = log;
079    
080      protected Hashtable tabManagers = new Hashtable();  // JCompopnet views to TabManagers
081      protected int botNameCount = 0;
082      protected int vizNameCount = 0;
083      protected int tabCount = 0;
084      protected int connectionCount = 0;
085    
086      protected JTextField serverField;
087      protected InetAddress serverAddress = null;
088      protected JTextField botPortField;
089      protected int serverBotPort = GamebotsClient.DEFAULT_BOT_PORT;
090      protected JTextField vizPortField;
091      protected int serverVizPort = GamebotsClient.DEFAULT_VIZ_PORT;
092    
093      protected Thread[] connectionEventThreads;
094      protected Vector connectionEventQueue;
095    
096      // Public Methods
097      ///////////////////////////////////////////////////////////////////////////
098      public static void main( String[] args ) {
099        new BotRunnerApp( args );
100      }
101    
102      public BotRunnerApp( String[] args ) {
103        buildGUI();
104        log.setJTextArea( text );
105    
106        connectionEventQueue = new Vector();
107        connectionEventThreads = new Thread[ MAX_CONNECTION_ATTEMPTS ];
108        for( int i=0; i<MAX_CONNECTION_ATTEMPTS; i++ ) {
109          connectionEventThreads[i] = new Thread( new ConnectionEventRunner(i) );
110          connectionEventThreads[i].start();
111        }
112    
113        frame.show();
114      }
115    
116      //  Private Methods
117      ///////////////////////////////////////////////////////////////////////////
118      protected void buildGUI() {
119        JMenuBar menuBar;
120        JMenu menu;
121        JMenuItem item;
122        JPanel panel, panel2;
123        JScrollPane scroll;
124        JSplitPane splitter;
125        JLabel label;
126        GridBagConstraints gbc;
127    
128        requestAddBot.putValue( Action.NAME, "Add "+BOT+"..." );
129        requestAddViz.putValue( Action.NAME, "Add "+VIZ+"..." );
130        connectAll.putValue( Action.NAME, "Connect All" );
131          connectAll.setEnabled( false );
132        reconnectAll.putValue( Action.NAME, "Reconnect All" );
133          connectAll.setEnabled( false );
134        disconnectAll.putValue( Action.NAME, "Disconnect All" );
135          disconnectAll.setEnabled( false );
136        requestExit.putValue( Action.NAME, "Exit" );
137        handleExit.putValue( Action.NAME, "Force Exit" );
138    
139        frame = new JFrame( "Gamebots Java Client" );
140          menuBar = new JMenuBar();
141            menu = new JMenu( "Team" );
142              menu.add( new JMenuItem( requestAddBot ) );
143              menu.add( new JMenuItem( requestAddViz ) );
144              menu.addSeparator();
145              menu.add( new JMenuItem( connectAll ) );
146              menu.add( new JMenuItem( reconnectAll ) );
147              menu.add( new JMenuItem( disconnectAll ) );
148              menu.addSeparator();
149              menu.add( new JMenuItem( requestExit ) );
150            menuBar.add( menu );
151    
152            botMenu = new JMenu( BOT );
153              botMenu.setEnabled( false );
154            menuBar.add( botMenu );
155          frame.setJMenuBar( menuBar );
156    
157          panel = new JPanel( new BorderLayout() );
158            splitter = new JSplitPaneFix( JSplitPane.VERTICAL_SPLIT );
159              tabbedPane = new JTabbedPane();
160                tabbedPane.addChangeListener( tabListener );
161    
162                serverPanel = new JPanel( new GridBagLayout() );
163                  serverPanel.setBorder( new EmptyBorder( 2, 2, 2, 2 ) );
164    
165                  label = new JLabel( "Server:" );
166                  gbc = new GridBagConstraints();
167                    gbc.gridx = gbc.gridy = 0;
168                    gbc.weightx = 0.0;
169                    gbc.weighty = 0.6;
170                    gbc.fill = gbc.NONE;
171                    gbc.anchor = gbc.SOUTHEAST;
172                  serverPanel.add( label, gbc );
173    
174                  serverField = new JTextField();
175                    serverField.addFocusListener( serverFocusListener );
176    
177                    gbc.gridx++;
178                    gbc.weightx = 1.0;
179                    gbc.anchor = gbc.SOUTHWEST;
180                    gbc.fill = gbc.HORIZONTAL;
181                  serverPanel.add( serverField, gbc );
182    
183                  label = new JLabel( BOT+" Port:", SwingConstants.TRAILING );
184                    gbc.gridx = 0;
185                    gbc.gridy++;
186                    gbc.weightx = gbc.weighty = 0.0;
187                    gbc.fill = gbc.NONE;
188                    gbc.anchor = gbc.EAST;
189                  serverPanel.add( label, gbc );
190    
191                  botPortField = new JTextField( Integer.toString( serverBotPort ) );
192                    botPortField.addFocusListener( botPortFocusListener );
193    
194                    gbc.gridx++;
195                    gbc.weightx = 1.0;
196                    gbc.fill = gbc.HORIZONTAL;
197                    gbc.anchor = gbc.EAST;
198                  serverPanel.add( botPortField, gbc );
199    
200                  label = new JLabel( VIZ+" Port:", SwingConstants.TRAILING );
201                    gbc.gridx = 0;
202                    gbc.gridy++;
203                    gbc.weightx = gbc.weighty = 0.0;
204                    gbc.anchor = gbc.EAST;
205                    gbc.fill = gbc.NONE;
206                  serverPanel.add( label, gbc );
207    
208                  vizPortField = new JTextField( Integer.toString( serverVizPort ) );
209                    vizPortField.addFocusListener( vizPortFocusListener );
210    
211                    gbc.gridx++;
212                    gbc.anchor = gbc.WEST;
213                    gbc.fill = gbc.HORIZONTAL;
214                  serverPanel.add( vizPortField, gbc );
215    
216                  panel2 = new JPanel( new FlowLayout( FlowLayout.TRAILING ) );
217                    panel2.add( new JButton( connectAll ) );
218    //                panel2.add( new JButton( reconnectAll ) );
219                    panel2.add( new JButton( disconnectAll ) );
220    
221                    gbc.gridx = 0;
222                    gbc.gridy++;
223                    gbc.weightx = gbc.weighty = 1.0;
224                    gbc.gridwidth = 2;
225                    gbc.anchor = gbc.SOUTH;
226                    gbc.fill = gbc.HORIZONTAL;
227                  serverPanel.add( panel2, gbc );
228                tabbedPane.add( serverPanel, "Server" );
229              splitter.setTopComponent( tabbedPane );
230    
231              text = new JTextArea();
232                text.setFont( new Font( "Monospaced", Font.PLAIN, 11 ) );
233                text.setAutoscrolls( true );
234                text.setEditable( false );
235                text.setMargin( new Insets( 2, 2, 2, 2 ) );
236              scroll = new JScrollPane( text );
237                scroll.setPreferredSize( new Dimension( 480, 360 ) );
238              splitter.setBottomComponent( scroll );
239    
240            panel.add( splitter, BorderLayout.CENTER );
241    
242            status = new JLabel( " " );
243            panel.add( status, BorderLayout.SOUTH );
244          frame.setContentPane( panel );
245    
246          frame.setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE );
247          frame.addWindowListener( new WindowAdapter() {
248              public void windowClosing( WindowEvent event ) {
249                requestExit.actionPerformed( null );
250              }
251    
252              public void windowClosed( WindowEvent event ) {
253                handleExit.actionPerformed( null );
254              }
255            } );
256        frame.pack();
257      }
258    
259      protected void setStatus( String status ) {
260        this.status.setText( status );
261      }
262    
263      // Event Handlers
264      protected Action requestAddBot = new AbstractAction() {
265        public void actionPerformed( ActionEvent event ) {
266          JDialog dialog = new AddTabDialog( BOT );
267          dialog.show();
268        }
269      };
270    
271      protected Action requestAddViz = new AbstractAction() {
272        public void actionPerformed( ActionEvent event ) {
273          JDialog dialog = new AddTabDialog( VIZ );
274          dialog.show();
275        }
276      };
277    
278      protected Action connectAll = new AbstractAction() {
279        public void actionPerformed( ActionEvent event ) {
280          if( serverAddress == null ) {
281            log.logNote( "Cannot connect: No server defined." );
282            log.flush();
283            return;
284          }
285    
286          synchronized( connectionEventQueue ) {
287            synchronized( tabManagers ) {
288              Iterator i = tabManagers.values().iterator();
289              TabManager tab;
290              while( i.hasNext() )
291                connectionEventQueue.add( ((TabManager) i.next()).connectRunner );
292            } // end tab sync
293    
294            connectionEventQueue.notifyAll();
295          }
296        }
297      };
298    
299      protected Action reconnectAll = new AbstractAction() {
300        public void actionPerformed( ActionEvent event ) {
301          synchronized( connectionEventQueue ) {
302            connectAll.actionPerformed( event );
303            disconnectAll.actionPerformed( event );
304          }
305        }
306      };
307    
308      protected Action disconnectAll = new AbstractAction() {
309        public void actionPerformed( ActionEvent event ) {
310          synchronized( connectionEventQueue ) {
311            synchronized( tabManagers ) {
312              Iterator i = tabManagers.values().iterator();
313              TabManager tab;
314              while( i.hasNext() )
315                connectionEventQueue.add( ((TabManager) i.next()).disconnectRunner );
316            } // end tab sync
317    
318            connectionEventQueue.notifyAll();
319          }
320        }
321      };
322    
323      protected ChangeListener tabListener = new ChangeListener() {
324        public void stateChanged( ChangeEvent event ) {
325          Component selected = tabbedPane.getSelectedComponent();
326    
327          if( selected == serverPanel ) {
328            if( curLogForText != log ) {
329              curLogForText.setJTextArea( null );
330              text.setText( "" );
331              curLogForText = log;
332              curLogForText.setJTextArea( text );
333    
334              botMenu.setEnabled( false );
335            }
336          } else {
337            Object tab = tabManagers.get( selected );
338            if( tab == null ) {
339              log.logError( "Selected component <"+selected+"> is not recognized." );
340            } else {
341              JTextAreaLog newLog = ((TabManager) tab).tabLog;
342              if( curLogForText != newLog ) {
343                curLogForText.setJTextArea( null );
344                text.setText( "" );
345                curLogForText = newLog;
346                curLogForText.setJTextArea( text );
347              }
348            }
349          }
350        }
351      };
352    
353      protected FocusListener serverFocusListener = new FocusListener() {
354        public void focusGained( FocusEvent event ) {
355          serverField.setForeground( Color.black );
356        }
357    
358        public void focusLost( FocusEvent event ) {
359          // This doesn't work
360          frame.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
361    
362          try {
363            InetAddress oldAddress = serverAddress;
364            serverAddress = InetAddress.getByName( serverField.getText() );
365    
366            if( oldAddress == null ||
367                !oldAddress.equals( serverAddress ) ) {
368              synchronized( tabManagers ) {
369                Iterator i = tabManagers.values().iterator();
370                TabManager tab;
371                while( i.hasNext() ) {
372                  tab = (TabManager) i.next();
373                  tab.client.setServerAddress( serverAddress );
374                }
375              }
376            }
377          } catch( UnknownHostException error ) {
378            serverField.setForeground( Color.red );
379          }
380          frame.setCursor( Cursor.getDefaultCursor() );
381        }
382      };
383    
384      protected FocusListener botPortFocusListener = new FocusListener() {
385        public void focusGained( FocusEvent event ) {
386          botPortField.setForeground( Color.black );
387        }
388    
389        public void focusLost( FocusEvent event ) {
390          try {
391            int oldPort = serverBotPort;
392            serverBotPort = Integer.parseInt( botPortField.getText() );
393    
394            if( serverBotPort <= 0 ) {
395              botPortField.setForeground( Color.red );
396            } else if( oldPort != serverBotPort ) {
397              synchronized( tabManagers ) {
398                Iterator i = tabManagers.values().iterator();
399                TabManager tab;
400                while( i.hasNext() ) {
401                  tab = (TabManager) i.next();
402                  if( tab instanceof BotManager )
403                    tab.client.setServerPort( serverBotPort );
404                }
405              }
406            }
407          } catch( NumberFormatException error ) {
408            botPortField.setForeground( Color.red );
409          }
410        }
411      };
412    
413      protected FocusListener vizPortFocusListener = new FocusListener() {
414        public void focusGained( FocusEvent event ) {
415          vizPortField.setForeground( Color.black );
416        }
417    
418        public void focusLost( FocusEvent event ) {
419          try {
420            int oldPort = serverVizPort;
421            serverVizPort = Integer.parseInt( vizPortField.getText() );
422    
423            if( serverVizPort <= 0 ) {
424              vizPortField.setForeground( Color.red );
425            } else if( oldPort != serverVizPort ) {
426              synchronized( tabManagers ) {
427                Iterator i = tabManagers.values().iterator();
428                TabManager tab;
429                while( i.hasNext() ) {
430                  tab = (TabManager) i.next();
431                  if( tab instanceof VizManager ) {
432                    tab.disconnect();
433                    tab.client.setServerPort( serverVizPort );
434                  }
435                }
436              }
437            }
438          } catch( NumberFormatException error ) {
439            vizPortField.setForeground( Color.red );
440          }
441        }
442      };
443    
444      protected Action requestExit = new AbstractAction() {
445        public void actionPerformed( ActionEvent event ) {
446          frame.setVisible( false );
447          frame.dispose();
448        }
449      };
450    
451      protected Action handleExit = new AbstractAction() {
452        public void actionPerformed( ActionEvent event ) {
453          frame.setVisible( false );
454          frame.dispose();
455    
456          for( int i=0; i<MAX_CONNECTION_ATTEMPTS; i++ )
457            connectionEventThreads[i] = null;
458    
459          synchronized( tabManagers ) {
460            Iterator i = tabManagers.values().iterator();
461            while( i.hasNext() )
462              ((TabManager) i.next()).destroy();
463          } // end sync
464    
465          System.exit( 0 );
466        }
467      };
468    
469    
470      //  Protected Inner Classes
471      ///////////////////////////////////////////////////////////////////////////
472      protected class ConnectionEventRunner implements Runnable {
473        protected int number;
474    
475        public ConnectionEventRunner( int number ) {
476          this.number = number;
477        }
478    
479        public void run() {
480          Thread thisThread = Thread.currentThread();
481    
482          Runnable runner = null;
483          while( connectionEventThreads[number] == thisThread ) {
484            synchronized( connectionEventQueue ) {
485              if( connectionEventQueue.isEmpty() ) {
486                try {
487                  connectionEventQueue.wait();
488                } catch( InterruptedException error ) {
489                  // ignore...
490                }
491              } else {
492                runner = (Runnable) connectionEventQueue.get( 0 );
493                connectionEventQueue.remove( 0 );
494              }
495            }  //  end synch
496    
497            if( runner != null )
498              runner.run();
499          }
500        }
501      }
502    
503      protected class AddTabDialog extends JDialog {
504        //  Private Data
505        /////////////////////////////////////////////////////////////////////////
506    /*
507        protected final JMenuItem ANY_TEAM_ITEM;
508        protected final JMenuItem RED_TEAM_ITEM;
509        protected final JMenuItem BLUE_TEAM_ITEM;
510        protected final JMenuItem GREEN_TEAM_ITEM;
511        protected final JMenuItem YELLOW_TEAM_ITEM;
512        protected final JMenuItem NO_TEAM_ITEM;
513    */
514    
515        protected String[] knownBots = {
516          "edu.isi.gamebots.examples.ExampleBot",
517          "edu.cmu.gamebots.CMU_JBot",
518          "edu.tamu.gamebots.humanbot.HumanBot",
519          "edu.tamu.agents.CAST_PM.unreal.AgentUnrealTournamentAdapter"
520        };
521        
522        protected String[] knownVizTools ={
523          "edu.isi.gamebots.examples.ExampleVizTool"
524        };
525        
526        protected String panelType;
527    
528        protected JCardStack cards;
529        protected JTextField nameField;
530        protected JComboBox  teamField;
531        protected JTextField classpathField;
532        protected JComboBox classField;//changed by Ryan Rozich for user convienience
533        protected JButton addButton, cancelButton;
534    
535        //  Public Methods
536        /////////////////////////////////////////////////////////////////////////
537        public AddTabDialog( String type ) {
538    /*
539          if( type == BOT ) {
540            ANY_TEAM_ITEM = new JMenuItem( "Any" );
541            RED_TEAM_ITEM = new JMenuItem( "Red" );
542              RED_TEAM_ITEM.setForeground( Color.red );
543            BLUE_TEAM_ITEM = new JMenuItem( "Blue" );
544              BLUE_TEAM_ITEM.setForeground( Color.blue );
545            GREEN_TEAM_ITEM = new JMenuItem( "Green" );
546              GREEN_TEAM_ITEM.setForeground( Color.green );
547            YELLOW_TEAM_ITEM = new JMenuItem( "Yellow" );
548              YELLOW_TEAM_ITEM.setForeground( Color.yellow.darker() );
549            NO_TEAM_ITEM = new JMenuItem( "None" );
550          } else {
551            ANY_TEAM_ITEM = RED_TEAM_ITEM = BLUE_TEAM_ITEM = GREEN_TEAM_ITEM
552              = YELLOW_TEAM_ITEM = NO_TEAM_ITEM = null;
553          }
554    */
555    
556          panelType = type;
557    
558          JPanel panel, panel2;
559          JLabel label;
560          JButton button;
561          GridBagConstraints gbc;
562    
563          Container content = getContentPane();
564            cards = new JCardStack();
565              panel = new JPanel( new GridBagLayout() );
566                panel.setBorder( new EmptyBorder( 2, 2, 2, 2 ) );
567    
568                label = new JLabel( "Name:" );
569                gbc = new GridBagConstraints();
570                  gbc.gridx = gbc.gridy = 0;
571                  gbc.weightx = 0.0;
572                  gbc.weighty = 0.3;
573                  gbc.anchor = gbc.SOUTHEAST;
574                panel.add( label, gbc );
575    
576                if( panelType == BOT )
577                  nameField = new JTextField( panelType+(++botNameCount) );
578                else // assume VIZ
579                  nameField = new JTextField( panelType+(++vizNameCount) );
580    
581                  gbc.gridx++;
582                  gbc.weightx = 1.0;
583                  gbc.anchor = gbc.SOUTHWEST;
584                  gbc.fill = gbc.HORIZONTAL;
585                panel.add( nameField, gbc );
586    
587                if( panelType == BOT ) {
588                  label = new JLabel( "Team:", SwingConstants.TRAILING );
589                    gbc.gridx++;
590                    gbc.weightx = 0.0;
591                    gbc.fill = gbc.NONE;
592                    gbc.anchor = gbc.SOUTHEAST;
593                  panel.add( label, gbc );
594    
595                    teamField = new JComboBox();
596                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_ANY ) );
597                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_NONE ) );
598                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_RED ) );
599                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_BLUE ) );
600                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_GREEN ) );
601                      teamField.addItem( GamebotsConstants.TeamColorMap.teamToName( TEAM_GOLD ) );
602                      teamField.setEditable( false );
603    
604                    gbc.gridx++;
605                    gbc.anchor = gbc.SOUTHWEST;
606                    gbc.fill = gbc.HORIZONTAL;
607                  panel.add( teamField, gbc );
608                }
609    
610    /*
611                label = new JLabel( "Classpath:", SwingConstants.TRAILING );
612                  gbc.gridx = 0;
613                  gbc.gridy++;
614                  gbc.weightx = gbc.weighty = 0.0;
615                  gbc.anchor = gbc.EAST;
616                panel.add( label, gbc );
617    
618                  classpathField = new JTextField();
619    
620                  gbc.gridx++;
621                  gbc.gridwidth = 3;
622                  gbc.weightx = 1.0;
623                  gbc.anchor = gbc.WEST;
624                  gbc.fill = gbc.HORIZONTAL;
625                panel.add( classpathField, gbc );
626    */
627    
628                label = new JLabel( "Class:", SwingConstants.TRAILING );
629                  gbc.gridx = 0;
630                  gbc.gridy++;
631                  gbc.gridwidth = 1;
632                  gbc.weightx = 0.0;
633                  gbc.weighty = 1.0;
634                  gbc.anchor = gbc.NORTHEAST;
635                panel.add( label, gbc );
636    
637                if( panelType == BOT )
638                  classField = new JComboBox( knownBots );
639                else // assume VIZ
640                  classField = new JComboBox( knownVizTools );
641                
642                classField.setEditable(true);
643    
644                  gbc.gridx++;
645                  gbc.gridwidth = 3;
646                  gbc.weightx = 1.0;
647                  gbc.anchor = gbc.NORTHWEST;
648                  gbc.fill = gbc.HORIZONTAL;
649                panel.add( classField, gbc );
650              cards.add( panel, DATA_CARD );
651            content.add( cards, BorderLayout.CENTER );
652    
653            panel = new JPanel( new FlowLayout( FlowLayout.TRAILING ) );
654              addButton = new JButton( "Add "+panelType );
655              addButton.addActionListener( new ActionListener() {
656                  public void actionPerformed( ActionEvent event ) {
657                    addTab();
658                  }
659                } );
660              panel.add( addButton );
661    
662              cancelButton = new JButton( "Cancel" );
663              cancelButton.addActionListener( new ActionListener() {
664                  public void actionPerformed( ActionEvent event ) {
665                    hide();
666                    dispose();
667                  }
668                } );
669              panel.add( cancelButton );
670            content.add( panel, BorderLayout.SOUTH );
671          pack();
672        }
673    
674        //  Private Methods
675        /////////////////////////////////////////////////////////////////////////
676        protected void addTab() {
677          JLabel label;
678    
679          try {
680            if( panelType == BOT ) {
681              Class botClass = Class.forName( ((String)classField.getSelectedItem()).trim() );
682              Bot bot = (Bot) botClass.newInstance();
683              int team;
684              String teamName = teamField.getSelectedItem().toString();
685              if( teamName.equals("Any") )
686                team = TEAM_ANY;
687              else if( teamName.equals("Red") )
688                team = TEAM_RED;
689              else if( teamName.equals("Blue") )
690                team = TEAM_BLUE;
691              else if( teamName.equals("Green") )
692                team = TEAM_GREEN;
693              else if( teamName.equals("Gold") )
694                team = TEAM_GOLD;
695              else // if( teamName.equals("None") )
696                team = TEAM_NONE;
697    
698              new BotManager( bot, nameField.getText().trim(), team );
699    
700            } else if( panelType == VIZ ) {
701              Class vizClass = Class.forName( ((String)classField.getSelectedItem()).trim() );
702              VizTool viz = (VizTool) vizClass.newInstance();
703              new VizManager( viz, nameField.getText().trim() );
704            }
705    
706            // Success, go ahead and clear the dialog
707            hide();
708            dispose();
709          } catch( Exception error ) {
710            label = new JLabel( "<html><font color='black'><b>Unable to create "+panelType+":</b><p>"+error.toString() );
711            cards.add( label, ERROR_CARD );
712            cards.show( ERROR_CARD );
713            addButton.setVisible( false );
714          }
715        }
716      }
717    
718      protected abstract class TabManager extends GamebotsClient.Adapter {
719        //  Private Data
720        ///////////////////////////////////////////////////////////////////////
721        protected final Object      clientLock = new Object();
722        public final GamebotsClient client;
723    
724        public final JTextAreaLog   tabLog;
725    
726        protected String            title;
727    
728        //  Protected Constructor
729        ///////////////////////////////////////////////////////////////////////
730        protected TabManager( String title ) {
731          this.title = title;
732          tabLog = new JTextAreaLog();
733          client = new GamebotsClient();
734    
735          client.setLog( tabLog );
736          client.addListener( this );
737    
738          if( serverAddress != null )
739            client.setServerAddress( serverAddress );
740        }
741    
742        //  Public Methods
743        ///////////////////////////////////////////////////////////////////////
744        public void connect() {
745    //      tabLog.logNote( "Attempting Connection: Server: "+client.getServerAddress()+":"+client.getServerPort() );
746          try {
747            synchronized( clientLock ) {
748              client.connect();
749              long remaining = TIMEOUT;
750              long endTime = System.currentTimeMillis() + remaining;
751              while( !client.isConnected() ) {
752                try {
753                  clientLock.wait( TIMEOUT );
754                } catch( InterruptedException error ) {
755                  // ignore
756                }
757                remaining = endTime - System.currentTimeMillis();
758                if( remaining < 0 ) {
759                  log.logNote( title+" <"+getClass().getName()+"> connection attempt timed out ("+new Date()+")" );
760                  break;
761                }
762              }
763            } // end synch
764          } catch ( IOException error ) {
765            log.logError( title+" <"+getClass().getName()+"> could not connect ("+new Date()+")", error );
766          } catch ( RuntimeException error ) {
767            log.logError( title+" <"+getClass().getName()+"> could not connect ("+new Date()+")", error );
768          }
769        }
770    
771        public void disconnect() {
772          try {
773            synchronized( clientLock ) {
774              if( client.isConnected() ) {
775                client.disconnect();
776                long remaining = TIMEOUT;
777                long endTime = System.currentTimeMillis() + remaining;
778                while( !client.isConnected() ) {
779                  try {
780                    clientLock.wait( TIMEOUT );
781                  } catch( InterruptedException error ) {
782                    // ignore
783                  }
784                  remaining = endTime - System.currentTimeMillis();
785                  if( remaining < 0 ) {
786                    log.logNote( title+" <"+getClass().getName()+"> disconnection attempt timed out ("+new Date()+")" );
787                    break;
788                  }
789                }
790              }
791            } // end synch
792          } catch ( RuntimeException error ) {
793            log.logError( title+" <"+getClass().getName()+"> failed duing disconnect()", error );
794          }
795        }
796    
797        public void destroy() {
798          // close log???
799    
800          client.disconnect();
801        }
802    
803        // Event Handlers
804        public void connected() {
805          log.logNote( "Connection: \""+title+"\" ("+new Date()+")" );
806          synchronized( tabManagers ) {
807            ++connectionCount;
808    
809            disconnectAll.setEnabled( true );
810            reconnectAll.setEnabled( true );
811            connectAll.setEnabled( connectionCount != tabCount );
812          }
813          synchronized( clientLock ) {
814            clientLock.notifyAll();
815          }
816        }
817    
818        public void disconnected() {
819          log.logNote( "Disconnection: \""+title+"\" ("+new Date()+")" );
820          synchronized( tabManagers ) {
821            --connectionCount;
822    
823            connectAll.setEnabled( true );
824            boolean moreConnections = connectionCount > 0;
825            disconnectAll.setEnabled( moreConnections );
826            reconnectAll.setEnabled( moreConnections );
827          }
828          synchronized( clientLock ) {
829            clientLock.notifyAll();
830          }
831        }
832    
833        // Public Connection Event Runners
834        public Runnable connectRunner = new Runnable() {
835          public void run() {
836            try {
837              connect();
838            } catch( RuntimeException error ) {
839              tabLog.logError( "Unexpected error during connection ("+new Date()+")", error );
840            }
841          }
842        };
843    
844        public Runnable disconnectRunner = new Runnable() {
845          public void run() {
846            disconnect();
847          }
848        };
849    
850        //  Private Methods
851        ///////////////////////////////////////////////////////////////////////
852        protected void addTab( JComponent view ) {
853          synchronized( tabManagers ) {
854            ++tabCount;
855            tabbedPane.add( view, title );
856            tabManagers.put( view, TabManager.this );
857    
858            connectAll.setEnabled( true );
859          }
860        }
861      }
862    
863      protected class BotManager extends TabManager {
864        public final Bot bot;
865    
866        public BotManager( Bot bot, String name, int team ) {
867          super( name );
868    
869          this.bot = bot;
870    
871          client.setServerPort( serverBotPort );
872    
873          bot.init( name, client, team, tabLog );
874          addTab( bot.getView() );
875        }
876    
877        public void connect() {
878          if( client.isConnected() )
879            return;
880    
881          tabLog.logNote( "Attempting Connection: Server: "+client.getServerAddress()+":"+client.getServerPort() );
882          try {
883            synchronized( clientLock ) {
884              bot.connect();
885              long remaining = TIMEOUT;
886              long endTime = System.currentTimeMillis() + remaining;
887              while( !client.isConnected() ) {
888                try {
889                  clientLock.wait( TIMEOUT );
890                } catch( InterruptedException error ) {
891                  // ignore
892                }
893                remaining = endTime - System.currentTimeMillis();
894                if( remaining < 0 ) {
895                  log.logNote( BOT+" \""+bot.getName()+"\" connection attempt timed out ("+new Date()+")" );
896                  break;
897                }
898              }
899            }
900          } catch ( RuntimeException error ) {
901            log.logError( BOT+" \""+bot.getName()+"\" could not connect ("+new Date()+")", error );
902          }
903        }
904    
905        public void disconnect() {
906          try {
907            bot.disconnect();
908          } catch ( RuntimeException error ) {
909            log.logError( BOT+" \""+bot.getName()+"\" could not disconnect ("+new Date()+")", error );
910          }
911        }
912    
913        public void destroy() {
914          try {
915            bot.destroy();
916          } catch ( RuntimeException error ) {
917            log.logError( BOT+" \""+bot.getName()+"\" failed during disconnect()", error );
918          }
919    
920          super.destroy();
921        }
922      }
923    
924      protected class VizManager extends TabManager {
925        //  Private Data
926        /////////////////////////////////////////////////////////////////////////
927        protected final VizTool viz;
928    
929        //  Public Methods
930        /////////////////////////////////////////////////////////////////////////
931        public VizManager( VizTool viz, String name ) {
932          super( name );
933    
934          this.viz = viz;
935    
936          client.setServerPort( serverVizPort );
937    
938          viz.init( tabLog );
939          client.addListener( viz.getListener() );
940          addTab( viz.getView() );
941        }
942      }
943    }