edu.umich.robot.GuiApplication.java Source code

Java tutorial

Introduction

Here is the source code for edu.umich.robot.GuiApplication.java

Source

/*
 * Copyright (c) 2011, Regents of the University of Michigan
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package edu.umich.robot;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ToolTipManager;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import april.config.Config;
import april.config.ConfigFile;
import april.jmat.geom.GRay3D;
import april.viewer.Viewer;
import april.vis.VisCanvas;
import april.vis.VisCanvasEventAdapter;

import com.google.common.collect.Maps;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import edu.umich.robot.actions.ActionManager;
import edu.umich.robot.actions.AddObjectAction;
import edu.umich.robot.actions.ConnectSuperdroidAction;
import edu.umich.robot.actions.CreateSplinterRobotAction;
import edu.umich.robot.actions.CreateSuperdroidRobotAction;
import edu.umich.robot.actions.DisableFollowAction;
import edu.umich.robot.actions.ExitAction;
import edu.umich.robot.actions.FollowPositionAction;
import edu.umich.robot.actions.FollowPositionAndThetaAction;
import edu.umich.robot.actions.MoveCameraAboveAction;
import edu.umich.robot.actions.MoveCameraBehindAction;
import edu.umich.robot.actions.ResetAction;
import edu.umich.robot.actions.ResetPreferencesAction;
import edu.umich.robot.actions.SaveMapAction;
import edu.umich.robot.actions.SimSpeedAction;
import edu.umich.robot.actions.SoarDataAction;
import edu.umich.robot.actions.SoarParametersAction;
import edu.umich.robot.actions.SoarStepAction;
import edu.umich.robot.actions.SoarToggleAction;
import edu.umich.robot.actions.TextMessageAction;
import edu.umich.robot.april.SoarViewRobot;
import edu.umich.robot.april.SoarViewRobot.FollowMode;
import edu.umich.robot.april.ViewTrajectory;
import edu.umich.robot.events.AfterResetEvent;
import edu.umich.robot.events.ControllerActivatedEvent;
import edu.umich.robot.events.ControllerDeactivatedEvent;
import edu.umich.robot.events.RobotAddedEvent;
import edu.umich.robot.events.RobotRemovedEvent;
import edu.umich.robot.gp.Gamepad;
import edu.umich.robot.util.Configs;
import edu.umich.robot.util.Pose;
import edu.umich.robot.util.events.RobotEvent;
import edu.umich.robot.util.events.RobotEventListener;

/**
 * <p>
 * Top-level class for applications that start with an interactive GUI. See also
 * Headless Application.
 * 
 * @author voigtjr@gmail.com
 */
public class GuiApplication {
    private static final Log logger = LogFactory.getLog(GuiApplication.class);

    /**
     * <p>
     * Used primarily for window location and stuff like that.
     */
    private final Preferences PREFERENCES = Preferences.userRoot().node("edu/umich/robot");

    private final JFrame frame = new JFrame();

    /**
     * <p>
     * The main hub for the simulation components and events.
     */
    private final Controller controller;

    /**
     * <p>
     * The 3D view in to the simulation.
     */
    private final Viewer viewer;

    /**
     * <p>
     * True forces a reset of the various window settings and such that normally
     * persist across sessions.
     */
    private boolean resetPreferencesAtExit = false;

    /**
     * <p>
     * Container for the 3D view (viewer).
     */
    private final ViewerView viewerView;

    /**
     * <p>
     * The list of robots and controllers.
     */
    private final RobotsView robotsView;

    /**
     * <p>
     * The console text output for debugging feedback.
     */
    private final ConsoleView consoleView;

    /**
     * <p>
     * The interface for chatting with the robots.
     */
    private final ChatView chatView;

    /**
     * <p>
     * Viewer state saved so that robots can be removed from the simulation.
     * 
     * @author voigtjr
     */
    private static class RobotData {
        public RobotData(SoarViewRobot vr, ViewTrajectory vt) {
            this.vr = vr;
            this.vt = vt;
        }

        final SoarViewRobot vr;
        final ViewTrajectory vt;
    }

    /**
     * <p>
     * Maps robot names to robot data.
     */
    private final Map<String, RobotData> robotData = Maps.newConcurrentMap();

    /**
     * <p>
     * Container for the status bar at the bottom of the window.
     */
    private final StatusBar status;

    /**
     * <p>
     * When adding an object to the simulator, after selecting what object is to
     * be added, this gets filled in with that object type's name.
     */
    private String objectToAdd;

    /**
     * <p>
     * Manager and sort of container for the actions used in the GUI.
     */
    private ActionManager actionManager;

    /**
     * <p>
     * Firing up this application requires a configuration file. If no
     * configuration file is presented on the command line, this is called to
     * prompt the user to select a configuration file.
     * 
     * <p>
     * Future work should probably include some default instead of doing this.
     * 
     * @return The selected, loaded configuration file.
     */
    public static Config promptForConfig(Component parent) {

        System.out.println("CLASSPATH: " + System.getenv("CLASSPATH"));
        System.out.println("DYLD_LIBRARY_PATH: " + System.getenv("DYLD_LIBRARY_PATH"));
        System.out.println("LD_LIBRARY_PATH: " + System.getenv("LD_LIBRARY_PATH"));
        System.out.println("SOAR_HOME: " + System.getenv("SOAR_HOME"));
        System.out.println("java.library.path: " + System.getProperty("java.library.path"));

        JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
        fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        FileFilter filter = new FileNameExtensionFilter("Text Config File", "txt");
        fc.setFileFilter(filter);
        fc.setMultiSelectionEnabled(false);
        int ret = fc.showOpenDialog(parent);
        if (ret == JFileChooser.APPROVE_OPTION) {
            try {
                return new ConfigFile(fc.getSelectedFile().getAbsolutePath());
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        return null;
    }

    /**
     * Entry point.
     * 
     * @param args Args from command line.
     */
    public GuiApplication(Config config) {
        // Heavyweight is not desirable but it is the only thing that will
        // render in front of the Viewer on all platforms. Blame OpenGL
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
        ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);

        // must have config
        //Config config = (args.length > 0) ? ConfigUtil.getDefaultConfig(args) : promptForConfig(frame);
        if (config == null)
            System.exit(1);

        // Add more stuff to the config file that doesn't change between runs.
        Application.setupSimulatorConfig(config);
        setupViewerConfig(config);

        Configs.toLog(logger, config);

        controller = new Controller(config, new Gamepad());
        controller.initializeGamepad();

        viewer = new Viewer(config, frame);
        // This puts us in full 3d mode by default. The Viewer GUI doesn't
        // reflect this in its right click drop-down, a bug.
        viewer.getVisCanvas().getViewManager().setInterfaceMode(3);

        controller.addListener(RobotAddedEvent.class, listener);
        controller.addListener(RobotRemovedEvent.class, listener);
        controller.addListener(AfterResetEvent.class, listener);
        controller.addListener(ControllerActivatedEvent.class, listener);
        controller.addListener(ControllerDeactivatedEvent.class, listener);

        actionManager = new ActionManager(this);
        initActions();

        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                controller.shutdown();
                System.exit(0);
            }
        });
        frame.setLayout(new BorderLayout());

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        fileMenu.add(actionManager.getAction(CreateSplinterRobotAction.class));
        fileMenu.add(actionManager.getAction(CreateSuperdroidRobotAction.class));
        fileMenu.add(new JSeparator());
        fileMenu.add(actionManager.getAction(ConnectSuperdroidAction.class));
        fileMenu.add(new JSeparator());
        fileMenu.add(actionManager.getAction(ResetPreferencesAction.class));

        /*
        fileMenu.add(new JSeparator());
        fileMenu.add(actionManager.getAction(TextMessageAction.class));
        */

        fileMenu.add(new JSeparator());
        fileMenu.add(actionManager.getAction(SaveMapAction.class));
        fileMenu.add(new JSeparator());
        fileMenu.add(actionManager.getAction(ExitAction.class));
        menuBar.add(fileMenu);

        JMenu cameraMenu = new JMenu("Camera");
        cameraMenu.add(actionManager.getAction(DisableFollowAction.class));
        cameraMenu.add(actionManager.getAction(FollowPositionAction.class));
        cameraMenu.add(actionManager.getAction(FollowPositionAndThetaAction.class));
        cameraMenu.add(new JSeparator());
        cameraMenu.add(actionManager.getAction(MoveCameraBehindAction.class));
        cameraMenu.add(actionManager.getAction(MoveCameraAboveAction.class));
        menuBar.add(cameraMenu);

        JMenu objectMenu = new JMenu("Objects");
        boolean added = false;
        for (String objectName : controller.getObjectNames()) {
            added = true;
            objectMenu.add(new AddObjectAction(this, objectName));
        }
        if (!added)
            objectMenu.add(new JLabel("No objects available"));
        menuBar.add(objectMenu);

        menuBar.revalidate();
        frame.setJMenuBar(menuBar);

        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        toolBar.setRollover(true);
        toolBar.add(actionManager.getAction(SoarParametersAction.class));
        toolBar.add(actionManager.getAction(SoarDataAction.class));
        toolBar.add(actionManager.getAction(ResetAction.class));
        toolBar.add(actionManager.getAction(SoarToggleAction.class));
        toolBar.add(actionManager.getAction(SoarStepAction.class));
        toolBar.add(actionManager.getAction(SimSpeedAction.class));
        frame.add(toolBar, BorderLayout.PAGE_START);

        viewerView = new ViewerView(viewer.getVisCanvas());
        robotsView = new RobotsView(this, actionManager);

        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, viewerView, robotsView);
        splitPane.setDividerLocation(0.75);

        // TODO SoarApril
        /*
        viewer.addRobotSelectionChangedListener(robotsView);
        viewer.getVisCanvas().setDrawGround(true);
        */

        viewer.getVisCanvas().addEventHandler(new VisCanvasEventAdapter() {

            public String getName() {
                return "Place Object";
            }

            @Override
            public boolean mouseClicked(VisCanvas vc, GRay3D ray, MouseEvent e) {
                boolean ret = false;
                synchronized (GuiApplication.this) {
                    if (objectToAdd != null && controller != null) {
                        controller.addObject(objectToAdd, ray.intersectPlaneXY());
                        objectToAdd = null;
                        ret = true;
                    } else {
                        double[] click = ray.intersectPlaneXY();
                        chatView.setClick(new Point2D.Double(click[0], click[1]));
                    }
                }
                status.setMessage(
                        String.format("%3.1f,%3.1f", ray.intersectPlaneXY()[0], ray.intersectPlaneXY()[1]));
                return ret;
            }
        });

        consoleView = new ConsoleView();
        chatView = new ChatView(this);
        controller.getRadio().addRadioHandler(chatView);

        final JSplitPane bottomPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, consoleView, chatView);
        bottomPane.setDividerLocation(200);

        final JSplitPane splitPane2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, splitPane, bottomPane);
        splitPane2.setDividerLocation(0.75);

        frame.add(splitPane2, BorderLayout.CENTER);

        status = new StatusBar();
        frame.add(status, BorderLayout.SOUTH);

        /*
        frame.addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e)
        {
            final Preferences windowPrefs = getWindowPreferences();
            final Rectangle r = frame.getBounds();
            if(frame.getExtendedState() == JFrame.NORMAL)
            {
                windowPrefs.putInt("x", r.x);
                windowPrefs.putInt("y", r.y);
                windowPrefs.putInt("width", r.width);
                windowPrefs.putInt("height", r.height);
                windowPrefs.putInt("divider", splitPane.getDividerLocation());
            }
                
            exit();
        }});
            
        Preferences windowPrefs = getWindowPreferences();
        if (windowPrefs.get("x", null) != null)
        {
        frame.setBounds(
                windowPrefs.getInt("x", 0), 
                windowPrefs.getInt("y", 0), 
                windowPrefs.getInt("width", 1200), 
                windowPrefs.getInt("height", 900));
        splitPane.setDividerLocation(windowPrefs.getInt("divider", 500));
        }
        else
        {
        frame.setBounds(
                windowPrefs.getInt("x", 0), 
                windowPrefs.getInt("y", 0), 
                windowPrefs.getInt("width", 1200), 
                windowPrefs.getInt("height", 900));
        splitPane.setDividerLocation(0.75);
        frame.setLocationRelativeTo(null); // center
        }
        */

        frame.getRootPane().setBounds(0, 0, 1600, 1200);

        frame.getRootPane().registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                frame.dispose();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);

        frame.pack();

        frame.setVisible(true);

        for (String s : config.getStrings("splinters", new String[0])) {
            double[] pos = config.getDoubles(s + ".position");
            if (pos == null) {
                logger.error("Splinter indexed in config file but no position defined: " + s);
                continue;
            }

            Pose pose = new Pose(pos);
            String prods = config.getString(s + ".productions");
            boolean collisions = config.getBoolean(s + ".wallCollisions", true);

            controller.createSplinterRobot(s, pose, collisions);
            boolean simulated = config.getBoolean(s + ".simulated", true);
            if (simulated)
                controller.createSimSplinter(s);
            else
                controller.createRealSplinter(s);
            controller.createSimLaser(s);
            if (prods != null) {
                controller.createSoarController(s, s, prods, config.getChild(s + ".properties"));
                PREFERENCES.put("lastProductions", prods);
            }
        }

        for (String s : config.getStrings("superdroids", new String[0])) {
            double[] pos = config.getDoubles(s + ".position");
            if (pos == null) {
                logger.error("Superdroid indexed in config file but no position defined: " + s);
                continue;
            }

            Pose pose = new Pose(pos);
            String prods = config.getString(s + ".productions");
            boolean collisions = config.getBoolean(s + ".wallCollisions", true);

            controller.createSuperdroidRobot(s, pose, collisions);
            boolean simulated = config.getBoolean(s + ".simulated", true);
            if (simulated)
                controller.createSimSuperdroid(s);
            else {
                try {
                    controller.createRealSuperdroid(s, "192.168.1.165", 3192);
                } catch (UnknownHostException e1) {
                    e1.printStackTrace();
                } catch (SocketException e1) {
                    e1.printStackTrace();
                }
            }
            controller.createSimLaser(s);
            if (prods != null) {
                // wait a sec
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
                controller.createSoarController(s, s, prods, config.getChild(s + ".properties"));
                PREFERENCES.put("lastProductions", prods);
            }
        }

    }

    /**
     * <p>
     * Initialize all actions here, together, they all report to the
     * actionManager which stores them.
     */
    private void initActions() {
        new ResetAction(actionManager);
        new SaveMapAction(actionManager);
        new SoarDataAction(actionManager);
        new SoarParametersAction(actionManager);
        new SoarToggleAction(actionManager);
        new SoarStepAction(actionManager);
        new CreateSplinterRobotAction(actionManager);
        new CreateSuperdroidRobotAction(actionManager);
        new ConnectSuperdroidAction(actionManager);
        new ResetPreferencesAction(actionManager);
        new ExitAction(actionManager);
        new DisableFollowAction(actionManager);
        new FollowPositionAction(actionManager);
        new FollowPositionAndThetaAction(actionManager);
        new MoveCameraAboveAction(actionManager);
        new MoveCameraBehindAction(actionManager);
        new SimSpeedAction(actionManager);
        //new TextMessageAction(actionManager);
    }

    /**
     * <p>
     * Returns the controller instance so things like actions can use it.
     * 
     * @return The controller instance.
     */
    public Controller getController() {
        return controller;
    }

    /**
     * <p>
     * Get the selected robot, or null.
     * 
     * @return
     */
    private SoarViewRobot getRobot() {
        String name = controller.getSelectedRobotName();
        if (name == null)
            return null;

        return robotData.get(name).vr;
    }

    /**
     * <p>
     * Change the follow mode in the viewer. A robot must currently be selected.
     * 
     * @param mode
     *            New follow mode.
     */
    // TODO SoarApril
    public void setFollowMode(FollowMode mode) {
        SoarViewRobot vr = getRobot();
        if (vr == null)
            return;
        vr.setFollowMode(mode, viewer.getVisCanvas());
    }

    /**
     * <p>
     * Moves the camera to the robot using the specified mode. See
     * robot.moveCameraToRobot.
     * 
     * @param mode
     *            See robot.moveCameraToRobot
     */
    // TODO SoarApril
    public void snapCamera(int mode) {
        SoarViewRobot robot = getRobot();
        if (robot != null)
            robot.moveCameraToRobot(mode, viewer.getVisCanvas());
    }

    public void dispose() {
        frame.dispose();
    }

    private void exit() {
        if (resetPreferencesAtExit) {
            try {
                PREFERENCES.removeNode();
            } catch (BackingStoreException e) {
                logger.error(e);
            }
        }
    }

    /**
     * <p>
     * Return persistent window preferences.
     * 
     * @return Persistent window preferences.
     */
    public Preferences getWindowPreferences() {
        return PREFERENCES.node("window");
    }

    /**
     * <p>
     * Pops up a window to create a new splinter robot to add to the simulation.
     */
    public void createSplinterRobotDialog() {
        final Pose pose = new Pose();

        FormLayout layout = new FormLayout("right:pref, 4dlu, 30dlu, 4dlu, right:pref, 4dlu, 30dlu",
                "pref, 2dlu, pref, 2dlu, pref");

        layout.setRowGroups(new int[][] { { 1, 3 } });

        final JDialog dialog = new JDialog(frame, "Create Splinter Robot", true);
        dialog.setLayout(layout);
        final JTextField name = new JTextField();
        final JTextField x = new JTextField(Double.toString((pose.getX())));
        final JTextField y = new JTextField(Double.toString((pose.getY())));
        final JButton cancel = new JButton("Cancel");
        final JButton ok = new JButton("OK");

        CellConstraints cc = new CellConstraints();
        dialog.add(new JLabel("Name"), cc.xy(1, 1));
        dialog.add(name, cc.xyw(3, 1, 5));
        dialog.add(new JLabel("x"), cc.xy(1, 3));
        dialog.add(x, cc.xy(3, 3));
        dialog.add(new JLabel("y"), cc.xy(5, 3));
        dialog.add(y, cc.xy(7, 3));
        dialog.add(cancel, cc.xyw(1, 5, 3));
        dialog.add(ok, cc.xyw(5, 5, 3));

        x.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                try {
                    pose.setX(Double.parseDouble(x.getText()));
                } catch (NumberFormatException ex) {
                    x.setText(Double.toString(pose.getX()));
                }
            }
        });

        y.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                try {
                    pose.setY(Double.parseDouble(y.getText()));
                } catch (NumberFormatException ex) {
                    y.setText(Double.toString(pose.getX()));
                }
            }
        });

        final ActionListener okListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String robotName = name.getText().trim();
                if (robotName.isEmpty()) {
                    logger.error("Create splinter: robot name empty");
                    return;
                }
                for (char c : robotName.toCharArray())
                    if (!Character.isDigit(c) && !Character.isLetter(c)) {
                        logger.error("Create splinter: illegal robot name");
                        return;
                    }

                controller.createSplinterRobot(robotName, pose, true);
                controller.createSimSplinter(robotName);
                controller.createSimLaser(robotName);
                dialog.dispose();
            }
        };
        name.addActionListener(okListener);
        x.addActionListener(okListener);
        y.addActionListener(okListener);
        ok.addActionListener(okListener);

        ActionListener cancelAction = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dialog.dispose();
            }
        };
        cancel.addActionListener(cancelAction);
        dialog.getRootPane().registerKeyboardAction(cancelAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);

        dialog.setLocationRelativeTo(frame);
        dialog.pack();
        dialog.setVisible(true);
    }

    public void createSuperdroidRobotDialog() {
        final Pose pose = new Pose();

        FormLayout layout = new FormLayout("right:pref, 4dlu, 30dlu, 4dlu, right:pref, 4dlu, 30dlu",
                "pref, 2dlu, pref, 2dlu, pref");

        layout.setRowGroups(new int[][] { { 1, 3 } });

        final JDialog dialog = new JDialog(frame, "Create Superdroid Robot", true);
        dialog.setLayout(layout);
        final JTextField name = new JTextField();
        final JTextField x = new JTextField(Double.toString((pose.getX())));
        final JTextField y = new JTextField(Double.toString((pose.getY())));
        final JButton cancel = new JButton("Cancel");
        final JButton ok = new JButton("OK");

        CellConstraints cc = new CellConstraints();
        dialog.add(new JLabel("Name"), cc.xy(1, 1));
        dialog.add(name, cc.xyw(3, 1, 5));
        dialog.add(new JLabel("x"), cc.xy(1, 3));
        dialog.add(x, cc.xy(3, 3));
        dialog.add(new JLabel("y"), cc.xy(5, 3));
        dialog.add(y, cc.xy(7, 3));
        dialog.add(cancel, cc.xyw(1, 5, 3));
        dialog.add(ok, cc.xyw(5, 5, 3));

        x.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                try {
                    pose.setX(Double.parseDouble(x.getText()));
                } catch (NumberFormatException ex) {
                    x.setText(Double.toString(pose.getX()));
                }
            }
        });

        y.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                try {
                    pose.setY(Double.parseDouble(y.getText()));
                } catch (NumberFormatException ex) {
                    y.setText(Double.toString(pose.getX()));
                }
            }
        });

        final ActionListener okListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String robotName = name.getText().trim();
                if (robotName.isEmpty()) {
                    logger.error("Create Superdroid: robot name empty");
                    return;
                }
                for (char c : robotName.toCharArray())
                    if (!Character.isDigit(c) && !Character.isLetter(c)) {
                        logger.error("Create Superdroid: illegal robot name");
                        return;
                    }

                controller.createSuperdroidRobot(robotName, pose, true);
                controller.createSimSuperdroid(robotName);
                dialog.dispose();
            }
        };
        name.addActionListener(okListener);
        x.addActionListener(okListener);
        y.addActionListener(okListener);
        ok.addActionListener(okListener);

        ActionListener cancelAction = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dialog.dispose();
            }
        };
        cancel.addActionListener(cancelAction);
        dialog.getRootPane().registerKeyboardAction(cancelAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);

        dialog.setLocationRelativeTo(frame);
        dialog.pack();
        dialog.setVisible(true);
    }

    /**
     * <p>
     * Responds to events coming from the controller.
     */
    private final RobotEventListener listener = new RobotEventListener() {
        public void onEvent(RobotEvent event) {
            if (event instanceof RobotAddedEvent) {
                RobotAddedEvent e = (RobotAddedEvent) event;

                SoarViewRobot vr = addViewRobot(e.getRobot().getName(), e.getRobot().getType());
                addViewLidars(e.getRobot().getName());
                addViewWaypoints(e.getRobot().getName());
                ViewTrajectory vt = addViewTrajectory(e.getRobot().getName());

                robotData.put(e.getRobot().getName(), new RobotData(vr, vt));
            }

            // TODO SoarApril
            /*
            else if (event instanceof AfterResetEvent)
            {
            for (RobotData rd : robotData.values())
                rd.vt.clear();
            }
            */

            // there are a few unhandled actions

            actionManager.updateActions();
        }
    };

    /**
     * <p>
     * Elaborates unchanging stuff for the viewer. This stuff used to be in the
     * config files but it doesn't change so it was moved here.
     * 
     * @param config
     *            The config file to elaborate.
     */
    private void setupViewerConfig(Config config) {
        config.setStrings("viewer.viewobjects", new String[] { "obstacles", "walls", "areas" });

        config.setString("viewer.obstacles.class", "edu.umich.robot.april.ViewObstaclesReadOnly");

        config.setString("viewer.walls.class", "edu.umich.robot.april.ViewWalls");
        Application.addImageData(config, "viewer.walls.obstacles.");

        // The following code enables other viewing options.

        //        config.setString("viewer.floor.class", "edu.umich.robot.april.ViewFloor"); // need to add floor to viewobjects list
        //        addImageData(config, "viewer.floor.obstacles.");

        config.setString("viewer.areas.class", "edu.umich.robot.april.ViewAreaDescriptions");

        config.setString("viewer.skybox.class", "edu.umich.robot.april.ViewSkybox"); // need to add skybox to viewobjects list
        config.setString("viewer.skybox.north_image", "../common/north.jpg");
        config.setString("viewer.skybox.south_image", "../common/south.jpg");
        config.setString("viewer.skybox.east_image", "../common/east.jpg");
        config.setString("viewer.skybox.west_image", "../common/west.jpg");
        config.setString("viewer.skybox.up_image", "../common/top.jpg");
        config.setString("viewer.skybox.down_image", "../common/floor.jpg");
    }

    /**
     * <p>
     * Adds position information to the config appended to the passed prefix.
     * Specifically adds prefix.position, prefix.rollpitchyaw_degrees, and
     * prefix.color.
     * 
     * @param config
     *            The config to elaborate.
     * @param prefix
     *            The prefix to add the position to.
     * @param position
     *            The position to add.
     * @param rpy
     *            Roll pitch yaw to add.
     * @param color
     *            Color to add, or null to skip.
     */
    private void addPositionInfo(Config config, String prefix, double[] position, double[] rpy, int[] color) {
        config.setDoubles(prefix + "position", position);
        config.setDoubles(prefix + "rollpitchyaw_degrees", rpy);
        if (color != null)
            config.setInts(prefix + "color", color);
    }

    /**
     * <p>
     * Add a robot to the viewer, by name.
     * 
     * @param name
     *            Robot name to add.
     * 
     * @return The viewer object representing the robot.
     */
    private SoarViewRobot addViewRobot(String name, RobotType type) {
        Config config = new Config();
        config.setString("class", "edu.umich.robot.april.SoarViewRobot");
        addPositionInfo(config, "avatar.", new double[] { 0, 0, 0 }, new double[] { 0, 0, 0 }, null);

        if (type == RobotType.SUPERDROID)
            config.setBoolean("model4", true);

        Configs.toLog(logger, config);
        return (SoarViewRobot) viewer.addObject(name, config);
    }

    /**
     * <p>
     * Add lidar sensor feedback to the viewer by robot name.
     * 
     * @param name
     *            Robot name to add sensor for.
     */
    private void addViewLidars(String name) {
        Config config = new Config();
        config.setString("class", "april.viewer.ViewLaser");
        config.setString("pose", name);
        config.setStrings("channels",
                new String[] { "SIM_LIDAR_FRONT", "SICK_LIDAR_FRONT", "LIDAR_LOWRES", "URG_RANGE" });
        addPositionInfo(config, "SIM_LIDAR_FRONT_" + name + ".", new double[] { 0, 0, 0.4 },
                new double[] { 0, 0, 0 }, new int[] { 1, 0, 0 });
        addPositionInfo(config, "SICK_LIDAR_FRONT_" + name + ".", new double[] { 0, 0, 0.4 },
                new double[] { 0, 0, 0 }, new int[] { 0, 1, 0 });
        addPositionInfo(config, "LIDAR_LOWRES_" + name + ".", new double[] { 0, 0, 0.4 }, new double[] { 0, 0, 0 },
                new int[] { 0, 0, 1 });
        addPositionInfo(config, "URG_RANGE_" + name + ".", new double[] { 0, 0, 0.4 }, new double[] { 0, 0, 0 },
                new int[] { 0, 1, 0 });

        Configs.toLog(logger, config);
        viewer.addObject(name + "lidars", config);
    }

    /**
     * <p>
     * Add waypoint feedback to viewer by robot name.
     * 
     * @param name
     *            Robot name to add waypoint feedback for.
     */
    private void addViewWaypoints(String name) {
        Config config = new Config();
        config.setString("class", "edu.umich.robot.april.ViewWaypoints");
        config.setString("channel", "WAYPOINTS_" + name);

        Configs.toLog(logger, config);

        viewer.addObject(name + "waypoints", config);
    }

    /**
     * <p>
     * Add view trajectory to viewer by robot name.
     * 
     * @param name
     *            The robot name to add trajectory for.
     * @return The viewer object representing the trajectory.
     */
    private ViewTrajectory addViewTrajectory(String name) {
        Config config = new Config();
        config.setString("class", "edu.umich.robot.april.ViewTrajectory");
        config.setString("pose", name);

        Configs.toLog(logger, config);
        return (ViewTrajectory) viewer.addObject(name + "trajectory", config);
    }

    /**
     * <p>
     * Sets the object type to add at the location of the next click in the
     * viewer.
     * 
     * @param name
     *            Object type to add at next click.
     */
    public void addObjectOnNextClick(String name) {
        synchronized (this) {
            objectToAdd = name;
        }
        status.setMessage("Click on map to place object.");
    }

    /**
     * <p>
     * Change the message in the status bar.
     * 
     * @param message
     *            The new message.
     */
    public void setStatusBarMessage(String message) {
        status.setMessage(message);
    }

    public Component getTopLevelAncestor() {
        return frame;
    }

    /**
     * <p>
     * Request that window preferences be reset on exit.
     * 
     * @param b True to request reset.
     */
    public void setResetPreferencesAtExit(boolean b) {
        resetPreferencesAtExit = b;
    }

    public Preferences getPreferences() {
        return PREFERENCES;
    }

    public void connectSuperdroidRobotDialog() {
        final int defaultPort = 3192;

        FormLayout layout = new FormLayout("right:pref, 4dlu, 35dlu, 4dlu, 35dlu",
                "pref, 2dlu, pref, 2dlu, pref, 2dlu, pref");

        final JDialog dialog = new JDialog(frame, "Connect to Superdroid", true);
        dialog.setLayout(layout);
        final JTextField namefield = new JTextField("charlie");
        final JTextField hostfield = new JTextField("192.168.1.165");
        final JTextField portfield = new JTextField(Integer.toString(defaultPort));
        final JButton cancel = new JButton("Cancel");
        final JButton ok = new JButton("OK");

        CellConstraints cc = new CellConstraints();
        dialog.add(new JLabel("Name"), cc.xy(1, 1));
        dialog.add(namefield, cc.xyw(3, 1, 3));
        dialog.add(new JLabel("Host"), cc.xy(1, 3));
        dialog.add(hostfield, cc.xyw(3, 3, 3));
        dialog.add(new JLabel("Port"), cc.xy(1, 5));
        dialog.add(portfield, cc.xyw(3, 5, 3));
        dialog.add(cancel, cc.xy(3, 7));
        dialog.add(ok, cc.xy(5, 7));

        portfield.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                int p = defaultPort;
                try {
                    p = Integer.parseInt(portfield.getText());
                    if (p < 1)
                        p = 1;
                    if (p > 65535)
                        p = 65535;
                } catch (NumberFormatException ex) {
                }
                portfield.setText(Integer.toString(p));
            }
        });

        final ActionListener okListener = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String robotName = namefield.getText().trim();
                if (robotName.isEmpty()) {
                    logger.error("Connect Superdroid: robot name empty");
                    return;
                }

                for (char c : robotName.toCharArray()) {
                    if (!Character.isDigit(c) && !Character.isLetter(c)) {
                        logger.error("Create Superdroid: illegal robot name");
                        return;
                    }
                }

                try {
                    controller.createRealSuperdroid(robotName, hostfield.getText(),
                            Integer.valueOf(portfield.getText()));
                } catch (UnknownHostException ex) {
                    ex.printStackTrace();
                    logger.error("Connect Superdroid: " + ex);
                } catch (SocketException ex) {
                    ex.printStackTrace();
                    logger.error("Connect Superdroid: " + ex);
                }
                dialog.dispose();
            }
        };

        namefield.addActionListener(okListener);
        hostfield.addActionListener(okListener);
        portfield.addActionListener(okListener);
        ok.addActionListener(okListener);

        ActionListener cancelAction = new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dialog.dispose();
            }
        };
        cancel.addActionListener(cancelAction);

        dialog.getRootPane().registerKeyboardAction(cancelAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
                JComponent.WHEN_IN_FOCUSED_WINDOW);

        dialog.setLocationRelativeTo(frame);
        dialog.pack();
        dialog.setVisible(true);
    }
}