org.rdv.ui.MainPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.rdv.ui.MainPanel.java

Source

/*
 * RDV
 * Real-time Data Viewer
 * http://rdv.googlecode.com/
 * 
 * Copyright (c) 2005-2007 University at Buffalo
 * Copyright (c) 2005-2007 NEES Cyberinfrastructure Center
 * Copyright (c) 2008 Palta Software
 *
 * 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.
 * 
 * $URL$
 * $Revision$
 * $Date$
 * $Author$
 */

package org.rdv.ui;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.application.Application;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.ResourceMap;
import org.rdv.ConfigurationManager;
import org.rdv.DataPanelManager;
import org.rdv.DataViewer;
import org.rdv.Extension;
import org.rdv.RDV;
import org.rdv.action.ActionFactory;
import org.rdv.action.Actions;
import org.rdv.action.DataViewerAction;
import org.rdv.auth.AuthenticationManager;
import org.rdv.data.Channel;
import org.rdv.rbnb.ConnectionListener;
import org.rdv.rbnb.LocalServer;
import org.rdv.rbnb.MessageListener;
import org.rdv.rbnb.Player;
import org.rdv.rbnb.RBNBController;
import org.rdv.rbnb.RBNBHelper;
import org.rdv.rbnb.StateListener;
import org.rdv.rbnb.TimeRange;
import org.rdv.util.RDVConfigurationFileFilter;

import com.jgoodies.uif_lite.component.Factory;

/**
 * Main frame for the application
 * 
 * @author  Jason P. Hanley
 * @author  Lawrence J. Miller
 * @since   1.2
 */
public class MainPanel extends JPanel implements MessageListener, ConnectionListener, StateListener {

    /** serialization version identifier */
    private static final long serialVersionUID = -4692978463068122918L;

    static Log log = LogFactory.getLog(MainPanel.class.getName());

    private RBNBController rbnb;
    private DataPanelManager dataPanelManager;

    private BusyDialog busyDialog;
    private LoadingDialog loadingDialog;
    private LoginDialog loginDialog;

    private JFrame frame;
    private GridBagConstraints c;
    private JMenuBar menuBar;
    private ChannelListPanel channelListPanel;
    private MetadataPanel metadataPanel;
    private JSplitPane leftPanel;
    private JPanel rightPanel;
    private ControlPanel controlPanel;
    private AudioPlayerPanel audioPlayerPanel;
    private MarkerSubmitPanel markerSubmitPanel;
    private DataPanelContainer dataPanelContainer;
    private JSplitPane splitPane;

    private AboutDialog aboutDialog;
    private RBNBConnectionDialog rbnbConnectionDialog;

    private Action fileAction;
    private Action connectAction;
    private Action disconnectAction;
    private Action loginAction;
    private Action logoutAction;
    private Action loadAction;
    private Action saveAction;
    private Action importAction;
    private Action exportAction;
    private Action exitAction;
    private Action exportVideoAction;

    private Action controlAction;
    private DataViewerAction realTimeAction;
    private DataViewerAction playAction;
    private DataViewerAction pauseAction;
    private Action beginningAction;
    private Action endAction;
    private Action gotoTimeAction;
    private Action updateChannelListAction;
    private Action dropDataAction;

    private Action viewAction;
    private Action showChannelListAction;
    private Action showMetadataPanelAction;
    private Action showControlPanelAction;
    private Action showAudioPlayerPanelAction;
    private Action showMarkerPanelAction;
    private Action dataPanelAction;
    private Action dataPanelHorizontalLayoutAction;
    private Action dataPanelVerticalLayoutAction;
    private Action showHiddenChannelsAction;
    private Action hideEmptyTimeAction;
    private Action fullScreenAction;

    private Action windowAction;
    private Action closeAllDataPanelsAction;

    private Action helpAction;
    private Action usersGuideAction;
    private Action supportAction;
    private Action releaseNotesAction;
    private Action aboutAction;

    private JLabel throbber;
    private Icon throbberStop;
    private Icon throbberAnim;

    private final Object loadingMonitor = new Object();

    /** the key mask used for menus shortucts */
    private final int menuShortcutKeyMask;

    public MainPanel() {
        super();

        this.rbnb = RBNBController.getInstance();
        this.dataPanelManager = DataPanelManager.getInstance();

        busyDialog = null;
        loadingDialog = null;

        // set the menu shortcut key mask in a platform independent way
        menuShortcutKeyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

        initFrame();
    }

    private void initFrame() {
        frame = Application.getInstance(RDV.class).getMainFrame();

        setLayout(new BorderLayout());

        c = new GridBagConstraints();

        initActions();
        initMenuBar();

        initChannelListPanel();
        initMetadataPanel();
        initLeftPanel();

        initRightPanel();
        initControls();
        initDataPanelContainer();
        initAudioPlayerPanel();
        initMarkerSubmitPanel();

        initSplitPane();

        channelListPanel.addChannelSelectionListener(metadataPanel);

        rbnb.addSubscriptionListener(controlPanel);

        rbnb.addTimeListener(controlPanel);

        rbnb.addStateListener(channelListPanel);
        rbnb.addStateListener(controlPanel);
        rbnb.addStateListener(this);

        rbnb.getMetadataManager().addMetadataListener(channelListPanel);
        rbnb.getMetadataManager().addMetadataListener(metadataPanel);
        rbnb.getMetadataManager().addMetadataListener(controlPanel);

        rbnb.addPlaybackRateListener(controlPanel);

        rbnb.addTimeScaleListener(controlPanel);

        rbnb.addMessageListener(this);

        rbnb.addConnectionListener(this);
    }

    private void initActions() {
        fileAction = new DataViewerAction("File", "File Menu", KeyEvent.VK_F);

        connectAction = new DataViewerAction("Connect", "Connect to RBNB server", KeyEvent.VK_C,
                KeyStroke.getKeyStroke(KeyEvent.VK_C, menuShortcutKeyMask | ActionEvent.SHIFT_MASK)) {
            /** serialization version identifier */
            private static final long serialVersionUID = 5038790506859429244L;

            public void actionPerformed(ActionEvent ae) {
                if (rbnbConnectionDialog == null) {
                    rbnbConnectionDialog = new RBNBConnectionDialog(frame, rbnb, dataPanelManager);
                } else {
                    rbnbConnectionDialog.setVisible(true);
                }
            }
        };

        disconnectAction = new DataViewerAction("Disconnect", "Disconnect from RBNB server", KeyEvent.VK_D,
                KeyStroke.getKeyStroke(KeyEvent.VK_D, menuShortcutKeyMask | ActionEvent.SHIFT_MASK)) {
            /** serialization version identifier */
            private static final long serialVersionUID = -1871076535376405181L;

            public void actionPerformed(ActionEvent ae) {
                dataPanelManager.closeAllDataPanels();
                rbnb.disconnect();
            }
        };

        loginAction = new DataViewerAction("Login", "Login as a NEES user") {
            /** serialization version identifier */
            private static final long serialVersionUID = 6105503896620555072L;

            public void actionPerformed(ActionEvent ae) {
                if (loginDialog == null) {
                    loginDialog = new LoginDialog(frame);
                } else {
                    loginDialog.setVisible(true);
                }
            }
        };

        logoutAction = new DataViewerAction("Logout", "Logout as a NEES user") {
            /** serialization version identifier */
            private static final long serialVersionUID = -2517567766044673777L;

            public void actionPerformed(ActionEvent ae) {
                AuthenticationManager.getInstance().setAuthentication(null);
            }
        };

        loadAction = new DataViewerAction("Load Setup", "Load data viewer setup from file") {
            /** serialization version identifier */
            private static final long serialVersionUID = 7197815395398039821L;

            public void actionPerformed(ActionEvent ae) {
                File configFile = UIUtilities.getFile(new RDVConfigurationFileFilter(), "Load");
                if (configFile != null) {
                    try {
                        URL configURL = configFile.toURI().toURL();
                        ConfigurationManager.loadConfiguration(configURL);
                    } catch (MalformedURLException e) {
                        DataViewer.alertError("\"" + configFile + "\" is not a valid configuration file URL.");
                    }
                }
            }
        };

        saveAction = new DataViewerAction("Save Setup", "Save data viewer setup to file") {
            /** serialization version identifier */
            private static final long serialVersionUID = -8259994975940624038L;

            public void actionPerformed(ActionEvent ae) {
                File file = UIUtilities.saveFile(new RDVConfigurationFileFilter());
                if (file != null) {
                    if (file.getName().indexOf(".") == -1) {
                        file = new File(file.getAbsolutePath() + ".rdv");
                    }

                    // prompt for overwrite if file already exists
                    if (file.exists()) {
                        int overwriteReturn = JOptionPane.showConfirmDialog(null,
                                file.getName() + " already exists. Do you want to overwrite it?", "Overwrite file?",
                                JOptionPane.YES_NO_OPTION);
                        if (overwriteReturn == JOptionPane.NO_OPTION) {
                            return;
                        }
                    }

                    ConfigurationManager.saveConfiguration(file);
                }
            }
        };

        importAction = new DataViewerAction("Import", "Import Menu", KeyEvent.VK_I, "icons/import.gif");

        exportAction = new DataViewerAction("Export", "Export Menu", KeyEvent.VK_E, "icons/export.gif");

        exportVideoAction = new DataViewerAction("Export video channels",
                "Export video on the server to the local computer") {
            /** serialization version identifier */
            private static final long serialVersionUID = -6420430928972633313L;

            public void actionPerformed(ActionEvent ae) {
                showExportVideoDialog();
            }
        };

        exitAction = new DataViewerAction("Exit", "Exit RDV", KeyEvent.VK_X) {
            /** serialization version identifier */
            private static final long serialVersionUID = 3137490972014710133L;

            public void actionPerformed(ActionEvent ae) {
                Application.getInstance().exit(ae);
            }
        };

        controlAction = new DataViewerAction("Control", "Control Menu", KeyEvent.VK_C);

        realTimeAction = new DataViewerAction("Real Time", "View data in real time", KeyEvent.VK_R,
                KeyStroke.getKeyStroke(KeyEvent.VK_R, menuShortcutKeyMask), "icons/rt.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -7564783609370910512L;

            public void actionPerformed(ActionEvent ae) {
                rbnb.monitor();
            }
        };

        playAction = new DataViewerAction("Play", "Playback data", KeyEvent.VK_P,
                KeyStroke.getKeyStroke(KeyEvent.VK_P, menuShortcutKeyMask), "icons/play.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 5974457444931142938L;

            public void actionPerformed(ActionEvent ae) {
                rbnb.play();
            }
        };

        pauseAction = new DataViewerAction("Pause", "Pause data display", KeyEvent.VK_A,
                KeyStroke.getKeyStroke(KeyEvent.VK_S, menuShortcutKeyMask), "icons/pause.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -5297742186923194460L;

            public void actionPerformed(ActionEvent ae) {
                rbnb.pause();
            }
        };

        beginningAction = new DataViewerAction("Go to beginning", "Move the location to the start of the data",
                KeyEvent.VK_B, KeyStroke.getKeyStroke(KeyEvent.VK_B, menuShortcutKeyMask), "icons/begin.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 9171304956895497898L;

            public void actionPerformed(ActionEvent ae) {
                controlPanel.setLocationBegin();
            }
        };

        endAction = new DataViewerAction("Go to end", "Move the location to the end of the data", KeyEvent.VK_E,
                KeyStroke.getKeyStroke(KeyEvent.VK_E, menuShortcutKeyMask), "icons/end.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 1798579248452726211L;

            public void actionPerformed(ActionEvent ae) {
                controlPanel.setLocationEnd();
            }
        };

        gotoTimeAction = new DataViewerAction("Go to time", "Move the location to specific date time of the data",
                KeyEvent.VK_T, KeyStroke.getKeyStroke(KeyEvent.VK_T, menuShortcutKeyMask)) {
            /** serialization version identifier */
            private static final long serialVersionUID = -6411442297488926326L;

            public void actionPerformed(ActionEvent ae) {
                TimeRange timeRange = RBNBHelper.getChannelsTimeRange();
                double time = DateTimeDialog.showDialog(frame, rbnb.getLocation(), timeRange.start, timeRange.end);
                if (time >= 0) {
                    rbnb.setLocation(time);
                }
            }
        };

        updateChannelListAction = new DataViewerAction("Update Channel List", "Update the channel list",
                KeyEvent.VK_U, KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0), "icons/refresh.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -170096772973697277L;

            public void actionPerformed(ActionEvent ae) {
                rbnb.updateMetadata();
            }
        };

        dropDataAction = new DataViewerAction("Drop Data", "Drop data if plaback can't keep up with data rate",
                KeyEvent.VK_D, "icons/drop_data.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 7079791364881120134L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                rbnb.dropData(menuItem.isSelected());
            }
        };

        viewAction = new DataViewerAction("View", "View Menu", KeyEvent.VK_V);

        showChannelListAction = new DataViewerAction("Show Channels", "", KeyEvent.VK_L, "icons/channels.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 4982129759386009112L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                channelListPanel.setVisible(menuItem.isSelected());
                layoutSplitPane();
                leftPanel.resetToPreferredSizes();
            }
        };

        showMetadataPanelAction = new DataViewerAction("Show Properties", "", KeyEvent.VK_P,
                "icons/properties.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 430106771704397810L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                metadataPanel.setVisible(menuItem.isSelected());
                layoutSplitPane();
                leftPanel.resetToPreferredSizes();
            }
        };

        showControlPanelAction = new DataViewerAction("Show Control Panel", "", KeyEvent.VK_C,
                "icons/control.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 6401715717710735485L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                controlPanel.setVisible(menuItem.isSelected());
            }
        };

        showAudioPlayerPanelAction = new DataViewerAction("Show Audio Player", "", KeyEvent.VK_A,
                "icons/audio.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -4248275698973916287L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                audioPlayerPanel.setVisible(menuItem.isSelected());
            }
        };

        showMarkerPanelAction = new DataViewerAction("Show Marker Panel", "", KeyEvent.VK_M, "icons/info.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -5253555511660929640L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                markerSubmitPanel.setVisible(menuItem.isSelected());
            }
        };

        dataPanelAction = new DataViewerAction("Arrange", "Arrange Data Panel Orientation", KeyEvent.VK_D);

        dataPanelHorizontalLayoutAction = new DataViewerAction("Horizontal Data Panel Orientation", "", -1,
                "icons/vertical.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = 3356151813557187908L;

            public void actionPerformed(ActionEvent ae) {
                dataPanelContainer.setLayout(DataPanelContainer.VERTICAL_LAYOUT);
            }
        };

        dataPanelVerticalLayoutAction = new DataViewerAction("Vertical Data Panel Orientation", "", -1,
                "icons/horizontal.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -4629920180285927138L;

            public void actionPerformed(ActionEvent ae) {
                dataPanelContainer.setLayout(DataPanelContainer.HORIZONTAL_LAYOUT);
            }
        };

        showHiddenChannelsAction = new DataViewerAction("Show Hidden Channels", "", KeyEvent.VK_H,
                KeyStroke.getKeyStroke(KeyEvent.VK_H, menuShortcutKeyMask), "icons/hidden.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -2723464261568074033L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                boolean selected = menuItem.isSelected();
                channelListPanel.showHiddenChannels(selected);
            }
        };

        hideEmptyTimeAction = new DataViewerAction("Hide time with no data", "", KeyEvent.VK_D) {
            /** serialization version identifier */
            private static final long serialVersionUID = -3123608144249355642L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                boolean selected = menuItem.isSelected();
                controlPanel.hideEmptyTime(selected);
            }
        };

        fullScreenAction = new DataViewerAction("Full Screen", "", KeyEvent.VK_F,
                KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0)) {
            /** serialization version identifier */
            private static final long serialVersionUID = -6882310862616235602L;

            public void actionPerformed(ActionEvent ae) {
                JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ae.getSource();
                if (menuItem.isSelected()) {
                    if (enterFullScreenMode()) {
                        menuItem.setSelected(true);
                    } else {
                        menuItem.setSelected(false);
                    }
                } else {
                    leaveFullScreenMode();
                    menuItem.setSelected(false);
                }
            }
        };

        windowAction = new DataViewerAction("Window", "Window Menu", KeyEvent.VK_W);

        closeAllDataPanelsAction = new DataViewerAction("Close all data panels", "", KeyEvent.VK_C,
                "icons/closeall.gif") {
            /** serialization version identifier */
            private static final long serialVersionUID = -8104876009869238037L;

            public void actionPerformed(ActionEvent ae) {
                dataPanelManager.closeAllDataPanels();
            }
        };

        helpAction = new DataViewerAction("Help", "Help Menu", KeyEvent.VK_H);

        usersGuideAction = new DataViewerAction("RDV Help", "Open the RDV User's Guide", KeyEvent.VK_H,
                KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)) {
            /** serialization version identifier */
            private static final long serialVersionUID = -2837190869008153291L;

            public void actionPerformed(ActionEvent ae) {
                try {
                    URL usersGuideURL = new URL("http://it.nees.org/library/telepresence/rdv-19-users-guide.php");
                    DataViewer.browse(usersGuideURL);
                } catch (Exception e) {
                }
            }
        };

        supportAction = new DataViewerAction("RDV Support", "Get support from NEESit", KeyEvent.VK_S) {
            /** serialization version identifier */
            private static final long serialVersionUID = -6855670513381679226L;

            public void actionPerformed(ActionEvent ae) {
                try {
                    URL supportURL = new URL("http://it.nees.org/support/");
                    DataViewer.browse(supportURL);
                } catch (Exception e) {
                }
            }
        };

        releaseNotesAction = new DataViewerAction("Release Notes", "Open the RDV Release Notes", KeyEvent.VK_R) {
            /** serialization version identifier */
            private static final long serialVersionUID = 7223639998298692494L;

            public void actionPerformed(ActionEvent ae) {
                try {
                    URL releaseNotesURL = new URL("http://it.nees.org/library/rdv/rdv-release-notes.php");
                    DataViewer.browse(releaseNotesURL);
                } catch (Exception e) {
                }
            }
        };

        aboutAction = new DataViewerAction("About RDV", "", KeyEvent.VK_A) {
            /** serialization version identifier */
            private static final long serialVersionUID = 3978467903181198979L;

            public void actionPerformed(ActionEvent ae) {
                showAboutDialog();
            }
        };

    }

    private void initMenuBar() {
        Application application = RDV.getInstance();
        ApplicationContext context = application.getContext();

        ResourceMap resourceMap = context.getResourceMap();
        String platform = resourceMap.getString("platform");
        boolean isMac = (platform != null && platform.equals("osx"));

        ActionFactory actionFactory = ActionFactory.getInstance();

        Actions actions = Actions.getInstance();
        ActionMap actionMap = context.getActionMap(actions);

        menuBar = new JMenuBar();

        JMenuItem menuItem;

        JMenu fileMenu = new JMenu(fileAction);

        menuItem = new JMenuItem(connectAction);
        fileMenu.add(menuItem);

        menuItem = new JMenuItem(disconnectAction);
        fileMenu.add(menuItem);

        fileMenu.addSeparator();

        menuItem = new JMenuItem(loginAction);
        fileMenu.add(menuItem);

        menuItem = new JMenuItem(logoutAction);
        fileMenu.add(menuItem);

        fileMenu.addMenuListener(new MenuListener() {
            public void menuCanceled(MenuEvent arg0) {
            }

            public void menuDeselected(MenuEvent arg0) {
            }

            public void menuSelected(MenuEvent arg0) {
                if (AuthenticationManager.getInstance().getAuthentication() == null) {
                    loginAction.setEnabled(true);
                    logoutAction.setEnabled(false);
                } else {
                    loginAction.setEnabled(false);
                    logoutAction.setEnabled(true);
                }
            }
        });

        fileMenu.addSeparator();

        menuItem = new JMenuItem(loadAction);
        fileMenu.add(menuItem);

        menuItem = new JMenuItem(saveAction);
        fileMenu.add(menuItem);

        fileMenu.addSeparator();

        fileMenu.add(new JMenuItem(actionMap.get("addLocalChannel")));

        fileMenu.addSeparator();

        JMenu importSubMenu = new JMenu(importAction);

        menuItem = new JMenuItem(actionFactory.getDataImportAction());
        importSubMenu.add(menuItem);

        menuItem = new JMenuItem(actionFactory.getOpenSeesDataImportAction());
        importSubMenu.add(menuItem);

        importSubMenu.add(new JMenuItem(actionMap.get("importJPEGs")));

        fileMenu.add(importSubMenu);

        JMenu exportSubMenu = new JMenu(exportAction);

        menuItem = new JMenuItem(actionFactory.getDataExportAction());
        exportSubMenu.add(menuItem);

        menuItem = new JMenuItem(exportVideoAction);
        exportSubMenu.add(menuItem);

        fileMenu.add(exportSubMenu);

        fileMenu.addSeparator();

        menuItem = new DataViewerCheckBoxMenuItem(actionFactory.getOfflineAction());
        fileMenu.add(menuItem);

        if (!isMac) {
            menuItem = new JMenuItem(exitAction);
            fileMenu.add(menuItem);
        }

        menuBar.add(fileMenu);

        JMenu controlMenu = new JMenu(controlAction);

        menuItem = new SelectableCheckBoxMenuItem(realTimeAction);
        controlMenu.add(menuItem);

        menuItem = new SelectableCheckBoxMenuItem(playAction);
        controlMenu.add(menuItem);

        menuItem = new SelectableCheckBoxMenuItem(pauseAction);
        controlMenu.add(menuItem);

        controlMenu.addMenuListener(new MenuListener() {
            public void menuCanceled(MenuEvent arg0) {
            }

            public void menuDeselected(MenuEvent arg0) {
            }

            public void menuSelected(MenuEvent arg0) {
                int state = rbnb.getState();
                realTimeAction.setSelected(state == Player.STATE_MONITORING);
                playAction.setSelected(state == Player.STATE_PLAYING);
                pauseAction.setSelected(state == Player.STATE_STOPPED);
            }
        });

        controlMenu.addSeparator();

        menuItem = new JMenuItem(beginningAction);
        controlMenu.add(menuItem);

        menuItem = new JMenuItem(endAction);
        controlMenu.add(menuItem);

        menuItem = new JMenuItem(gotoTimeAction);
        controlMenu.add(menuItem);

        menuBar.add(controlMenu);

        controlMenu.addSeparator();

        menuItem = new JMenuItem(updateChannelListAction);
        controlMenu.add(menuItem);

        controlMenu.addSeparator();

        menuItem = new JCheckBoxMenuItem(dropDataAction);
        controlMenu.add(menuItem);

        JMenu viewMenu = new JMenu(viewAction);

        menuItem = new JCheckBoxMenuItem(showChannelListAction);
        menuItem.setSelected(true);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(showMetadataPanelAction);
        menuItem.setSelected(true);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(showControlPanelAction);
        menuItem.setSelected(true);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(showAudioPlayerPanelAction);
        menuItem.setSelected(false);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(showMarkerPanelAction);
        menuItem.setSelected(true);
        viewMenu.add(menuItem);

        viewMenu.addSeparator();

        menuItem = new JCheckBoxMenuItem(showHiddenChannelsAction);
        menuItem.setSelected(false);
        viewMenu.add(menuItem);

        menuItem = new JCheckBoxMenuItem(hideEmptyTimeAction);
        menuItem.setSelected(false);
        viewMenu.add(menuItem);

        viewMenu.addSeparator();

        menuItem = new JCheckBoxMenuItem(fullScreenAction);
        menuItem.setSelected(false);
        viewMenu.add(menuItem);

        menuBar.add(viewMenu);

        JMenu windowMenu = new JMenu(windowAction);

        List<Extension> extensions = dataPanelManager.getExtensions();
        for (final Extension extension : extensions) {
            Action action = new DataViewerAction("Add " + extension.getName(), "", KeyEvent.VK_J) {
                /** serialization version identifier */
                private static final long serialVersionUID = 36998228704476723L;

                public void actionPerformed(ActionEvent ae) {
                    try {
                        dataPanelManager.createDataPanel(extension);
                    } catch (Exception e) {
                        log.error("Unable to open data panel provided by extension " + extension.getName() + " ("
                                + extension.getID() + ").");
                        e.printStackTrace();
                    }
                }
            };

            menuItem = new JMenuItem(action);
            windowMenu.add(menuItem);
        }

        windowMenu.addSeparator();

        menuItem = new JMenuItem(closeAllDataPanelsAction);
        windowMenu.add(menuItem);

        windowMenu.addSeparator();
        JMenu dataPanelSubMenu = new JMenu(dataPanelAction);

        ButtonGroup dataPanelLayoutGroup = new ButtonGroup();

        menuItem = new JRadioButtonMenuItem(dataPanelHorizontalLayoutAction);
        dataPanelSubMenu.add(menuItem);
        dataPanelLayoutGroup.add(menuItem);

        menuItem = new JRadioButtonMenuItem(dataPanelVerticalLayoutAction);
        menuItem.setSelected(true);
        dataPanelSubMenu.add(menuItem);
        dataPanelLayoutGroup.add(menuItem);
        windowMenu.add(dataPanelSubMenu);

        menuBar.add(windowMenu);

        JMenu helpMenu = new JMenu(helpAction);

        menuItem = new JMenuItem(usersGuideAction);
        helpMenu.add(menuItem);

        menuItem = new JMenuItem(supportAction);
        helpMenu.add(menuItem);

        menuItem = new JMenuItem(releaseNotesAction);
        helpMenu.add(menuItem);

        if (!isMac) {
            helpMenu.addSeparator();

            menuItem = new JMenuItem(aboutAction);
            helpMenu.add(menuItem);
        }

        menuBar.add(helpMenu);

        menuBar.add(Box.createHorizontalGlue());
        throbberStop = DataViewer.getIcon("icons/throbber.png");
        throbberAnim = DataViewer.getIcon("icons/throbber_anim.gif");
        throbber = new JLabel(throbberStop);
        throbber.setBorder(new EmptyBorder(0, 0, 0, 4));
        menuBar.add(throbber, BorderLayout.EAST);

        if (isMac) {
            registerMacOSXEvents();
        }

        frame.setJMenuBar(menuBar);
    }

    private void initChannelListPanel() {
        channelListPanel = new ChannelListPanel(dataPanelManager, rbnb);
        channelListPanel.setMinimumSize(new Dimension(0, 0));

        log.info("Created channel list panel.");
    }

    private void initMetadataPanel() {
        metadataPanel = new MetadataPanel(rbnb);

        log.info("Created metadata panel");
    }

    private void initLeftPanel() {
        leftPanel = Factory.createStrippedSplitPane(JSplitPane.VERTICAL_SPLIT, channelListPanel, metadataPanel,
                0.65f);
        leftPanel.setContinuousLayout(true);
        leftPanel.setBorder(new EmptyBorder(8, 8, 8, 0));

        log.info("Created left panel");
    }

    private void initRightPanel() {
        rightPanel = new JPanel();
        rightPanel.setMinimumSize(new Dimension(0, 0));
        rightPanel.setLayout(new GridBagLayout());
    }

    private void initControls() {
        controlPanel = new ControlPanel();
        add(controlPanel, BorderLayout.NORTH);

        log.info("Added control panel.");
    }

    private void initDataPanelContainer() {
        dataPanelContainer = dataPanelManager.getDataPanelContainer();

        c.fill = GridBagConstraints.BOTH;
        c.weightx = 1;
        c.weighty = 1;
        c.gridx = 0;
        c.gridy = 1;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.ipadx = 0;
        c.ipady = 0;
        c.insets = new java.awt.Insets(8, 0, 8, 6);
        c.anchor = GridBagConstraints.NORTHWEST;
        rightPanel.add(dataPanelContainer, c);

        log.info("Added data panel container.");
    }

    private void initAudioPlayerPanel() {
        audioPlayerPanel = new AudioPlayerPanel();
        audioPlayerPanel.setVisible(false);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weightx = 0;
        c.weighty = 0;
        c.gridx = 0;
        c.gridy = 2;
        c.gridwidth = 2;
        c.gridheight = 1;
        c.ipadx = 0;
        c.ipady = 0;
        c.insets = new java.awt.Insets(0, 0, 8, 6);
        c.anchor = GridBagConstraints.SOUTHWEST;
        rightPanel.add(audioPlayerPanel, c);

        log.info("Added Audio Player Panel.");
    }

    private void initMarkerSubmitPanel() {
        markerSubmitPanel = new MarkerSubmitPanel(rbnb);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.weightx = 0;
        c.weighty = 0;
        c.gridx = 0;
        c.gridy = 3;
        c.gridwidth = 2;
        c.gridheight = 1;
        c.ipadx = 0;
        c.ipady = 0;
        c.insets = new java.awt.Insets(0, 0, 8, 6);
        c.anchor = GridBagConstraints.SOUTHWEST;
        rightPanel.add(markerSubmitPanel, c);

        log.info("Added Marker Submission Panel.");
    }

    private void initSplitPane() {
        splitPane = Factory.createStrippedSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel, 0.2f);
        splitPane.setContinuousLayout(true);
        add(splitPane, BorderLayout.CENTER);
    }

    /**
     * Register event handlers for Mac OS X specific menu items.
     */
    private void registerMacOSXEvents() {
        try {
            Application rdv = RDV.getInstance();
            OSXAdapter.setQuitHandler(rdv, Application.class.getDeclaredMethod("exit", (Class[]) null));
            OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("showAboutDialog", (Class[]) null));
        } catch (Exception e) {
            log.warn("Unable to register Mac OS X events.");
            e.printStackTrace();
            return;
        }

        log.info("Registered Mac OS X events.");
    }

    /**
     * Hide the left part of the main split pane if both it's components are
     * visible. If either of them are visible, show it.
     */
    private void layoutSplitPane() {
        boolean visible = channelListPanel.isVisible() || metadataPanel.isVisible();

        if (leftPanel.isVisible() != visible) {
            leftPanel.setVisible(visible);
            splitPane.resetToPreferredSizes();
        }
    }

    private boolean enterFullScreenMode() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = ge.getScreenDevices();
        for (int i = 0; i < devices.length; i++) {
            GraphicsDevice device = devices[i];
            if (device.isFullScreenSupported() && device.getFullScreenWindow() == null) {
                log.info("Switching to full screen mode.");

                frame.setVisible(false);

                try {
                    device.setFullScreenWindow(frame);
                } catch (InternalError e) {
                    log.error("Failed to switch to full screen exclusive mode.");
                    e.printStackTrace();

                    frame.setVisible(true);
                    return false;
                }

                frame.dispose();
                frame.setUndecorated(true);
                frame.setVisible(true);
                frame.requestFocusInWindow();

                return true;
            }
        }

        log.warn("No screens available or full screen exclusive mode is unsupported on your platform.");

        postError("Full screen mode is not supported on your platform.");

        return false;
    }

    private void leaveFullScreenMode() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = ge.getScreenDevices();
        for (int i = 0; i < devices.length; i++) {
            GraphicsDevice device = devices[i];
            if (device.isFullScreenSupported() && device.getFullScreenWindow() == frame) {
                log.info("Leaving full screen mode.");

                frame.setVisible(false);
                device.setFullScreenWindow(null);
                frame.dispose();
                frame.setUndecorated(false);
                frame.setVisible(true);

                break;
            }
        }
    }

    public void postError(String errorMessage) {
        JOptionPane.showMessageDialog(this, errorMessage, "Error", JOptionPane.ERROR_MESSAGE);
    }

    public void postStatus(String statusMessage) {
        JOptionPane.showMessageDialog(this, statusMessage, "Status", JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Displays the about dialog
     */
    public void showAboutDialog() {
        if (aboutDialog == null) {
            aboutDialog = new AboutDialog(frame);
        } else {
            aboutDialog.setVisible(true);
        }
    }

    public void showExportVideoDialog() {
        List<String> channels = channelListPanel.getSelectedChannels();

        // remove non-data channels
        for (int i = channels.size() - 1; i >= 0; i--) {
            Channel channel = RBNBController.getInstance().getChannel(channels.get(i));
            String mime = channel.getMetadata("mime");
            if (!mime.equals("image/jpeg")) {
                channels.remove(i);
            }
        }

        // don't bring up the dialog if there are no channels specified
        if (channels.isEmpty()) {
            JOptionPane.showMessageDialog(null, "There are no video channels selected.", "Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }

        new ExportVideoDialog(frame, rbnb, channels);
    }

    /**
     * Gets a list of channels selected in the channel list.
     * 
     * @return  a list of selected channels
     */
    public List<String> getSelectedChannels() {
        return channelListPanel.getSelectedChannels();
    }

    /**
     * Gets if hidden channels are visible in the UI.
     * 
     * @return  true if hidden channels are visible, false otherwise
     */
    public boolean isHiddenChannelsVisible() {
        return channelListPanel.isShowingHiddenChannles();
    }

    public void connecting() {
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        if (busyDialog != null) {
            busyDialog.close();
            busyDialog = null;
        }

        busyDialog = new BusyDialog(frame);
        busyDialog.setCancelActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                rbnb.cancelConnect();
            }
        });
        busyDialog.start();

        startThrobber();
    }

    public void connected() {
        setCursor(null);

        if (busyDialog != null) {
            busyDialog.close();
            busyDialog = null;
        }

        stopThrobber();
    }

    public void connectionFailed() {
        setCursor(null);

        if (busyDialog != null) {
            busyDialog.close();
            busyDialog = null;
        }

        stopThrobber();
    }

    public void postState(int newState, int oldState) {
        if (newState == Player.STATE_DISCONNECTED) {
            frame.setTitle("RDV");

            controlAction.setEnabled(false);
            disconnectAction.setEnabled(false);
            saveAction.setEnabled(false);
            importAction.setEnabled(false);
            exportAction.setEnabled(false);

            controlPanel.setEnabled(false);
            markerSubmitPanel.setEnabled(false);

            ActionFactory.getInstance().getOfflineAction().setSelected(false);
        } else if (oldState == Player.STATE_DISCONNECTED) {
            frame.setTitle(rbnb.getServerName() + " - RDV");

            controlAction.setEnabled(true);
            disconnectAction.setEnabled(true);

            LocalServer localServer = LocalServer.getInstance();
            boolean offline = rbnb.getRBNBHostName().equals(localServer.getHost())
                    && rbnb.getRBNBPortNumber() == localServer.getPort();
            saveAction.setEnabled(!offline);
            importAction.setEnabled(offline);
            exportAction.setEnabled(true);

            controlPanel.setEnabled(true);
            markerSubmitPanel.setEnabled(true);
        }

        if (newState == Player.STATE_LOADING || newState == Player.STATE_PLAYING
                || newState == Player.STATE_MONITORING) {
            startThrobber();
        } else if (oldState == Player.STATE_LOADING || oldState == Player.STATE_PLAYING
                || oldState == Player.STATE_MONITORING) {
            stopThrobber();
        }

        if (newState == Player.STATE_LOADING) {
            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

            loadingDialog = new LoadingDialog(frame);

            new Thread() {
                public void run() {
                    synchronized (loadingMonitor) {
                        if (loadingDialog == null) {
                            return;
                        }

                        try {
                            loadingMonitor.wait(1000);
                        } catch (InterruptedException e) {
                        }

                        if (loadingDialog != null) {
                            loadingDialog.setVisible(true);
                            loadingDialog.start();
                        }
                    }
                }
            }.start();
        } else if (oldState == Player.STATE_LOADING) {
            setCursor(null);

            synchronized (loadingMonitor) {
                loadingMonitor.notify();

                if (loadingDialog != null) {
                    loadingDialog.close();
                    loadingDialog = null;
                }
            }
        }
    }

    private void startThrobber() {
        throbber.setIcon(throbberAnim);
    }

    private void stopThrobber() {
        throbber.setIcon(throbberStop);
    };

}