jchrest.gui.Shell.java Source code

Java tutorial

Introduction

Here is the source code for jchrest.gui.Shell.java

Source

// Copyright (c) 2012, Peter C. R. Lane
// Released under Open Works License, http://owl.apotheon.org/

package jchrest.gui;

import jchrest.gui.experiments.*;
import jchrest.architecture.Chrest;
import jchrest.lib.FileUtilities;
import jchrest.lib.ListPattern;
import jchrest.lib.PairedPattern;
import jchrest.lib.Scenes;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.*;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.swing.*;
import jchrest.lib.InputOutput;
import jchrest.lib.Modality;
import jchrest.lib.PairedAssociateExperiment;

import org.jfree.chart.*;
import org.jfree.chart.plot.*;
import org.jfree.data.statistics.*;

/**
 * The main panel for the Chrest shell.
 *
 * @author Peter C. R. Lane
 */
public class Shell extends JFrame implements Observer {
    private Chrest _model;
    private JMenu _dataMenu; //Required so that "Data" menu options can be disabled if the model is engaged in an experiment.

    public Shell() {
        this(new Chrest(0, false));
    }

    public Shell(Chrest model) {
        super("jCHREST");
        this._model = model;

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        createMenuBar(0);

        JLabel startupInfo = new JLabel(
                "<html>Load data by clicking on the 'Data' toolbar option.  Two types of data<br>"
                        + "can be loaded:" + "<ul>"
                        + " <li>Pre-experiment data: trains CHREST before undertaking an experiment.</li>"
                        + " <li>Experiment data: loads an experiment for CHREST to undertake.</li>" + "</ul>"
                        + "Note that only one set of experiment data can be used with CHREST at any<br>"
                        + "time whereas multiple pre-experiment data files can be used.<br>" + "<br>"
                        + "To reset CHREST and undertake a different experiment, select 'Model' then<br>"
                        + "'Clear' from the toolbar.  Clearing CHREST will remove any pre-experiment<br>"
                        + "data learned too." + "</html>");

        startupInfo.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
        setContentPane(startupInfo);

        setSize(800, 600);
        setLocationRelativeTo(null);
        setTheme("Nimbus");

        //_model.setNotLoadedIntoExperiment();
        //_model.setNotEngagedInExperiment();

        _model.addObserver(this);
    }

    private void createMenuBar(int time) {
        JMenuBar mb = new JMenuBar();
        mb.add(createShellMenu());
        mb.add(createDataMenu());
        mb.add(createModelMenu(time));
        setJMenuBar(mb);
    }

    public Chrest getModel() {
        return this._model;
    }

    @Override
    public void update(Observable o, Object arg) {
        if (_model.engagedInExperiment()) {
            for (int menuItem = 0; menuItem < this._dataMenu.getItemCount(); menuItem++) {
                this._dataMenu.getItem(menuItem).setEnabled(false);
            }
        } else {
            for (int menuItem = 0; menuItem < this._dataMenu.getItemCount(); menuItem++) {
                this._dataMenu.getItem(menuItem).setEnabled(true);
            }
        }
    }

    /**
     * Simple action to display a message about this application.
     */
    class AboutAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        AboutAction(Shell parent) {
            super("About", new ImageIcon(Shell.class.getResource("icons/About16.gif")));
            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(_parent,
                    "<HTML><P>This program is a graphical interface to the <BR>"
                            + "CHREST cognitive architecture.  You can load <BR>"
                            + "data, train models, and visualise results in a <BR>"
                            + "range of typical modelling problems for CHREST.</P>"
                            + "<P><P>Copyright (c) 2010-12, Peter C. R. Lane.</P></P>"
                            + "<P>Released under Open Works License</a>, version 0.9.2.</P>" +

                            "<p>See <a href=\"http://chrest.info\">http://chrest.info</a> for more information.</P></HTML>",
                    "About CHREST Shell v. 4.0.0-ALPHA-1", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * Simple action to allow user to change look and feel.
     */
    class LookFeelAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        LookFeelAction(Shell parent) {
            super("Theme");
            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            Object[] possibleValues = new Object[UIManager.getInstalledLookAndFeels().length];
            int i = 0;
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                possibleValues[i] = info.getName();
                i += 1;
            }
            String theme = (String) JOptionPane.showInputDialog(_parent, "Select look and feel:", "Change theme",
                    JOptionPane.INFORMATION_MESSAGE, null, possibleValues, UIManager.getLookAndFeel().getName());
            if (theme != null) {
                setTheme(theme);
            }
        }
    }

    /**
     * Action to load in a scripted experiment from file.
     */
    class LoadScriptedExperimentAction extends AbstractAction implements ActionListener {
        private final Shell _parent;
        private final String _scriptedExperimentClassName;

        LoadScriptedExperimentAction(Shell parent, String scriptedExperimentName,
                String scriptedExperimentClassName) {
            super(scriptedExperimentName);
            this._parent = parent;
            this._scriptedExperimentClassName = scriptedExperimentClassName;
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            //Retrieve the selected file as a File object and get the file's path 
            //as a string so that file.getPath() isn't repeated.
            File file = new File("classes" + File.separator + "jchrest" + File.separator + "experimentScripts"
                    + File.separator + this._scriptedExperimentClassName + ".class");
            String filePath = file.getPath();

            //Now, replace all file path seperators with periods to get a standard 
            //Java path specification.  Note that the file seperator needs to be 
            //escaped in the regex otherwise in Windows it will be a "\" and this is
            //used to indicate that the next character should be escaped in regex.  
            //Thus, a regex error will be thrown.  After this, get the path from 
            //"jchrest" to where the file extension begins i.e. the first part of the 
            //path to the scripted experiment class to the last.
            String fullyQualifiedExperimentClassName = filePath.replaceAll(Pattern.quote(File.separator), ".")
                    .substring(filePath.indexOf("jchrest"), filePath.lastIndexOf("."));

            //Finally, invoke the constructor for the scripted experiment class.
            try {
                Class<?> scriptedExperimentClass = Class.forName(fullyQualifiedExperimentClassName);
                Constructor scriptedExperimentConstructor = scriptedExperimentClass
                        .getDeclaredConstructor(jchrest.gui.Shell.class);
                scriptedExperimentConstructor.setAccessible(true);
                scriptedExperimentConstructor.newInstance(this._parent);
            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalArgumentException
                    | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
                Logger.getLogger(Shell.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Action to load in a new pre-experiment data set from file.
     */
    class LoadPreExperimentDataAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        LoadPreExperimentDataAction(Shell parent) {
            super("Load Pre-Experiment Data", new ImageIcon(Shell.class.getResource("icons/Open16.gif")));
            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            if (!_model.engagedInExperiment()) {
                (new LoadDataThread(_parent, "Load Pre-Experiment Data", false)).execute();
            }
        }
    }

    /**
     * Action to load in a new data set from file.
     */
    class LoadExperimentDataAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        LoadExperimentDataAction(Shell parent) {
            super("Load Experiment Data", new ImageIcon(Shell.class.getResource("icons/Open16.gif")));
            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            if (!_model.engagedInExperiment()) {
                (new LoadDataThread(_parent, "Load Experiment Data", true)).execute();
            }
        }

        @Override
        public String toString() {
            return "Load Experiment Data Action";
        }
    }

    enum Status {
        CANCELLED_SELECTION, CANCELLED_RUNNING, ERROR, OK
    };

    /**
     * Worker thread to handle loading the data.
     */
    public class LoadDataThread extends SwingWorker<Void, Void> {
        private final Shell _parent;
        private String _task;
        private List<ListPattern> _items;
        private List<PairedPattern> _pairs;
        private Scenes _scenes;
        private Status _status = Status.OK;
        private final String _openDialogTitle;
        private final boolean _experiment;
        private String _experimentName;

        public LoadDataThread(Shell parent, String openDialogTitle, boolean experiment) {
            _parent = parent;
            _task = "";
            _items = null;
            _pairs = null;
            _scenes = null;
            _openDialogTitle = openDialogTitle;
            _experiment = experiment;
        }

        @Override
        public Void doInBackground() {
            JFileChooser fileChooser = new JFileChooser(".");
            fileChooser.setDialogTitle(_openDialogTitle);
            int resultOfFileSelect = fileChooser.showOpenDialog(_parent);
            if (resultOfFileSelect == JFileChooser.APPROVE_OPTION) {
                File file = fileChooser.getSelectedFile();

                if (file == null) {
                    _status = Status.CANCELLED_SELECTION;
                } else {
                    try {
                        _status = Status.OK; // assume all will be fine
                        _experimentName = file.getName().replaceFirst("\\..*$", "");
                        _task = "";
                        // add a monitor to the input stream, to show a message if input is taking a while
                        InputStream inputStream = new ProgressMonitorInputStream(_parent, "Reading the input file",
                                new FileInputStream(file));
                        BufferedReader input = new BufferedReader(new InputStreamReader(inputStream));

                        String line = input.readLine();
                        if (line != null) {
                            _task = line.trim();
                        }

                        if (_task.equals("recognise-and-learn")) {
                            _items = InputOutput.readItems(input, false);
                        } else if (_task.equals("serial-anticipation")) {
                            _items = InputOutput.readItems(input, true);
                        } else if (_task.equals("paired-associate")) {
                            _pairs = InputOutput.readPairedItems(input, false);
                        } else if (_task.equals("categorisation")) {
                            _pairs = InputOutput.readPairedItems(input, true);
                        } else if (_task.equals("visual-search")) {
                            _scenes = Scenes.read(input); // throws IOException if any problem
                        }
                    } catch (InterruptedIOException ioe) {
                        _status = Status.CANCELLED_RUNNING; // flag cancelled error
                    } catch (IOException ioe) {
                        ioe.printStackTrace(System.err); //Give some meaningful info for debugging.
                        _status = Status.ERROR; // flag an IO error
                    }
                }
            } else if (resultOfFileSelect == JFileChooser.CANCEL_OPTION) {
                _status = Status.CANCELLED_SELECTION;
            }
            return null;
        }

        @Override
        protected void done() {
            switch (_status) {
            case CANCELLED_SELECTION:
                break;
            case ERROR:
                JOptionPane.showMessageDialog(_parent, "There was an error in processing your file", "File error",
                        JOptionPane.ERROR_MESSAGE);
                break;
            case CANCELLED_RUNNING:
                JOptionPane.showMessageDialog(_parent, "You cancelled the operation : no change",
                        "File Load Cancelled", JOptionPane.WARNING_MESSAGE);
                break;
            case OK:
                //Before loading a new experiment, save the maximum value of the 
                //model's clocks in this experiment so that the most recent state of
                //the model for this experiment can be rendered graphically, if
                //requested.
                _model.setMaxmimumTimeInExperiment(_model.getMaximumClockValue());

                if (this._experiment) {
                    _model.setLoadedIntoExperiment();
                    _model.addExperimentsLocatedInName(_experimentName);
                } else {
                    _model.setNotLoadedIntoExperiment();
                    _model.addExperimentsLocatedInName(Chrest.getPreExperimentPrepend() + _experimentName);
                }

                JPanel experimentInterface = null;

                if (_task.equals("recognise-and-learn") && _items != null) {
                    experimentInterface = new RecogniseAndLearnDemo(_model, _items);
                } else if (_task.equals("serial-anticipation") && _items != null) {
                    experimentInterface = new PairedAssociateInterface(_model,
                            PairedAssociateExperiment.makePairs(_items));
                } else if (_task.equals("paired-associate") && _pairs != null) {
                    experimentInterface = new PairedAssociateInterface(_model, _pairs);
                } else if (_task.equals("categorisation") && _pairs != null) {
                    experimentInterface = new CategorisationExperiment(_model, _pairs);
                } else if (_task.equals("visual-search") && _scenes != null) {
                    experimentInterface = new VisualSearchPane(_model, _scenes);
                } else {
                    JOptionPane.showMessageDialog(_parent, "Invalid task on first line of file", "File error",
                            JOptionPane.ERROR_MESSAGE);
                }
                _parent.setContentPane(experimentInterface);
                _parent.validate();
                _model.setCurrentExperiment((Experiment) experimentInterface);

                break;
            }
        }

        public List<ListPattern> getItems() {
            return this._items;
        }

        public List<PairedPattern> getPairs() {
            return this._pairs;
        }
    }

    /**
     * Action to clearShortAndLongTermMemory data held in the model.
     */
    class ClearModelAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        ClearModelAction(Shell parent) {
            super("Clear", new ImageIcon(Shell.class.getResource("icons/Delete16.gif")));

            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            if (JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog(_parent,
                    "Are you sure you want to clear the model?", "Clear model?", JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE)) {
                String lastExperimentLocatedInName = _model.getExperimentsLocatedInNames()
                        .get(_model.getExperimentsLocatedInNames().size() - 1);
                _model.clearShortAndLongTermMemory(0);
                _model.setNotEngagedInExperiment();
                _model.addExperimentsLocatedInName(lastExperimentLocatedInName);
            }
        }
    }

    /**
     * Action to show a dialog to change properties of model.
     */
    class ModelPropertiesAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        ModelPropertiesAction(Shell parent) {
            super("Properties", new ImageIcon(Shell.class.getResource("icons/Properties16.gif")));

            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            if (JOptionPane.OK_OPTION == JOptionPane.showOptionDialog(_parent, properties(),
                    "CHREST: Model properties", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null,
                    0)) {
                Experiment currentExperiment = _model.getCurrentExperiment();
                int currentExperimentTime = currentExperiment == null ? 0 : currentExperiment.getCurrentTime();

                _model.setAddProductionTime(((SpinnerNumberModel) _addLinkTime.getModel()).getNumber().intValue());
                _model.setDiscriminationTime(
                        ((SpinnerNumberModel) _discriminationTime.getModel()).getNumber().intValue());
                _model.setFamiliarisationTime(
                        ((SpinnerNumberModel) _familiarisationTime.getModel()).getNumber().intValue());
                _model.setRho(((SpinnerNumberModel) _rhoEntry.getModel()).getNumber().floatValue());
                _model.getStm(Modality.VISUAL).setCapacity(
                        ((SpinnerNumberModel) _visualStmSize.getModel()).getNumber().intValue(),
                        currentExperimentTime);
                _model.getStm(Modality.VERBAL).setCapacity(
                        ((SpinnerNumberModel) _verbalStmSize.getModel()).getNumber().intValue(),
                        currentExperimentTime);
                _model.getPerceiver().setFixationFieldOfView(
                        ((SpinnerNumberModel) _fieldOfView.getModel()).getNumber().intValue());
                _model.setCanCreateSemanticLinks(_createSemanticLinks.isSelected());
                _model.setCanCreateTemplates(_createTemplates.isSelected());
                _model.setExecutionHistoryRecording(_recordHistory.isSelected());
                _model.setNodeImageSimilarityThreshold(
                        ((SpinnerNumberModel) _similarityThreshold.getModel()).getNumber().intValue());
            }
        }

        private JSpinner _addLinkTime;
        private JSpinner _discriminationTime;
        private JSpinner _familiarisationTime;
        private JSpinner _rhoEntry;
        private JSpinner _visualStmSize;
        private JSpinner _verbalStmSize;
        private JSpinner _fieldOfView;
        private JSpinner _similarityThreshold;
        private JCheckBox _createSemanticLinks;
        private JCheckBox _createTemplates;
        private JCheckBox _recordHistory;

        private JPanel properties() {

            Experiment currentExperiment = _model.getCurrentExperiment();
            int currentExperimentTime = currentExperiment == null ? 0 : currentExperiment.getCurrentTime();

            // -- create entry widgets
            _addLinkTime = new JSpinner(new SpinnerNumberModel(_model.getAddProductionTime(), 1, 100000, 1));
            _discriminationTime = new JSpinner(
                    new SpinnerNumberModel(_model.getDiscriminationTime(), 1, 100000, 1));
            _familiarisationTime = new JSpinner(
                    new SpinnerNumberModel(_model.getFamiliarisationTime(), 1, 100000, 1));
            _rhoEntry = new JSpinner(new SpinnerNumberModel(_model.getRho(), 0.0, 1.0, 0.1));
            _visualStmSize = new JSpinner(
                    new SpinnerNumberModel((int) _model.getStm(Modality.VISUAL).getCapacity(currentExperimentTime),
                            (int) 1, (int) 10, (int) 1));
            _verbalStmSize = new JSpinner(
                    new SpinnerNumberModel((int) _model.getStm(Modality.VERBAL).getCapacity(currentExperimentTime),
                            (int) 1, (int) 10, (int) 1));
            _fieldOfView = new JSpinner(
                    new SpinnerNumberModel(_model.getPerceiver().getFixationFieldOfView(), 1, 100, 1));
            _similarityThreshold = new JSpinner(
                    new SpinnerNumberModel(_model.getNodeImageSimilarityThreshold(), 1, 100, 1));
            _createSemanticLinks = new JCheckBox("Use semantic links", _model.canCreateSemanticLinks());
            _createTemplates = new JCheckBox("Use templates", _model.canCreateTemplates());
            _recordHistory = new JCheckBox("Record history", _model.canRecordExecutionHistory());

            JPanel panel = new JPanel();
            panel.setLayout(new SpringLayout());
            Utilities.addLabel(panel, "Add link time (ms)", _addLinkTime);
            Utilities.addLabel(panel, "Discrimination time (ms)", _discriminationTime);
            Utilities.addLabel(panel, "Familiarisation time (ms)", _familiarisationTime);
            Utilities.addLabel(panel, "Rho", _rhoEntry);
            Utilities.addLabel(panel, "Visual STM size", _visualStmSize);
            Utilities.addLabel(panel, "Verbal STM size", _verbalStmSize);
            Utilities.addLabel(panel, "Field of view", _fieldOfView);
            Utilities.addLabel(panel, "Similarity threshold", _similarityThreshold);
            Utilities.addLabel(panel, "", _createSemanticLinks);
            Utilities.addLabel(panel, "", _createTemplates);
            Utilities.addLabel(panel, "", _recordHistory);

            Utilities.makeCompactGrid(panel, 11, 2, 3, 3, 10, 5);
            panel.setMaximumSize(panel.getPreferredSize());

            return panel;
        }
    }

    /**
     * Action to save test-link data from current model in VNA format.
     */
    class SaveModelAsVnaAction extends AbstractAction implements ActionListener {
        private Shell _parent;
        private int _time;

        SaveModelAsVnaAction(Shell parent, int time) {
            super("Save visual network (.VNA)");

            _parent = parent;
            _time = time;
        }

        public void actionPerformed(ActionEvent e) {
            File file = FileUtilities.getSaveFilename(_parent, "Save visual network");
            if (file == null)
                return;
            try {
                FileWriter writer = new FileWriter(file);
                _model.writeModelAsVna(writer, _time);
                writer.close();
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(_parent,
                        "File " + file.getName() + " could not be saved due to an error.", "Error: File save error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Action to save semantic links in current model as VNA.
     */
    class SaveModelSemanticLinksAsVnaAction extends AbstractAction implements ActionListener {
        private Shell _parent;
        private int _time;

        SaveModelSemanticLinksAsVnaAction(Shell parent, int time) {
            super("Save visual semantic links (.VNA)");

            _parent = parent;
            _time = time;
        }

        public void actionPerformed(ActionEvent e) {
            File file = FileUtilities.getSaveFilename(_parent, "Save visual semantic links");
            if (file == null)
                return;
            try {
                FileWriter writer = new FileWriter(file);
                _model.writeModelSemanticLinksAsVna(writer, _time);
                writer.close();
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(_parent,
                        "File " + file.getName() + " could not be saved due to an error.", "Error: File save error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /**
     * Action to display information about the current model.
     */
    class ModelInformationAction extends AbstractAction implements ActionListener {
        private int _time;

        ModelInformationAction(Shell parent, int time) {
            super("Information", new ImageIcon(Shell.class.getResource("icons/Information16.gif")));
            _time = time;
        }

        public void actionPerformed(ActionEvent e) {

            JPanel base = new JPanel();
            base.setLayout(new GridLayout(1, 1));

            JTabbedPane jtb = new JTabbedPane();
            jtb.addTab("Info", getInfoPane(_time));
            jtb.addTab("Contents", getHistogramPane(_model.getContentSizeCounts(_time), "contents",
                    "Histogram of Contents Sizes", "Contents size"));
            jtb.addTab("Images", getHistogramPane(_model.getImageSizeCounts(_time), "images",
                    "Histogram of Image Sizes", "Image size"));
            jtb.addTab("Semantic links", getHistogramPane(_model.getSemanticLinkCountsAndFrequencies(_time),
                    "semantic", "Histogram of Number of Semantic Links", "Number of semantic links"));
            base.add(jtb);

            JOptionPane pane = new JOptionPane(base, JOptionPane.INFORMATION_MESSAGE);
            JDialog dialog = pane.createDialog(Shell.this, "CHREST: Model information");
            dialog.setResizable(true);
            dialog.setVisible(true);
        }
    }

    private JLabel getInfoPane(int time) {
        DecimalFormat twoPlaces = new DecimalFormat("0.00");
        return new JLabel("<html><p>" + "Total nodes in LTM: " + _model.getLtmSize(time) + "<hr>" + "Visual nodes: "
                + _model.getLtmModalitySize(Modality.VISUAL, time) + " Average depth: "
                + twoPlaces.format(_model.getLtmAverageDepth(Modality.VISUAL, time)) + "<br>Verbal nodes: "
                + _model.getLtmModalitySize(Modality.VERBAL, time) + " Average depth: "
                + twoPlaces.format(_model.getLtmAverageDepth(Modality.VERBAL, time)) + "<br>Action nodes: "
                + _model.getLtmModalitySize(Modality.ACTION, time) + " Average depth: "
                + twoPlaces.format(_model.getLtmAverageDepth(Modality.ACTION, time)) + "<br>Number of templates: "
                + _model.countTemplatesInVisualLtm(time) + "</p></html>");
    }

    private JPanel getHistogramPane(Map<Integer, Integer> contentSizes, String label, String title, String xAxis) {
        int largest = 0;
        for (Integer key : contentSizes.keySet()) {
            if (key > largest)
                largest = key;
        }
        SimpleHistogramDataset dataset = new SimpleHistogramDataset(label);
        for (int i = 0; i <= largest; ++i) {
            SimpleHistogramBin bin = new SimpleHistogramBin((double) i, (double) (i + 1), true, false);
            int count = 0;
            if (contentSizes.containsKey(i)) {
                count = contentSizes.get(i);
            }
            bin.setItemCount(count);
            dataset.addBin(bin);
        }
        PlotOrientation orientation = PlotOrientation.VERTICAL;
        boolean show = false;
        boolean toolTips = true;
        boolean urls = false;
        JFreeChart chart = ChartFactory.createHistogram(title, xAxis, "frequency", dataset, orientation, show,
                toolTips, urls);

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(new ChartPanel(chart, 400, 300, 200, 100, 600, 600, true, true, true, true, true, true));
        JButton saveButton = new JButton("Save Data");
        saveButton.setToolTipText("Save the histogram data to a CSV file");
        saveButton.addActionListener(new SaveHistogramActionListener(this, contentSizes));
        panel.add(saveButton, BorderLayout.SOUTH);
        return panel;
    }

    class SaveHistogramActionListener implements ActionListener {
        Shell _parent;
        Map<Integer, Integer> _data;

        public SaveHistogramActionListener(Shell parent, Map<Integer, Integer> data) {
            _parent = parent;
            _data = data;
        }

        public void actionPerformed(ActionEvent e) {
            File file = FileUtilities.getSaveFilename(_parent, "Save histogram data");
            if (file == null)
                return;
            try {
                FileWriter writer = new FileWriter(file);
                for (Integer key : _data.keySet()) {
                    writer.write("" + key + ", " + _data.get(key) + "\n");
                }
                writer.close();
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(_parent,
                        "File " + file.getName() + " could not be saved due to an error.", "Error: File save error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    /** 
     * Action to display a separate frame with information about the current model.
     * Frame is composed of separate views onto the model, all using the observer 
     * design pattern to keep updated as the model changes.
     */
    class ViewModelAction extends AbstractAction implements ActionListener {
        private Shell _parent;

        ViewModelAction(Shell parent) {
            super("View", new ImageIcon(Shell.class.getResource("icons/Find16.gif")));

            _parent = parent;
        }

        public void actionPerformed(ActionEvent e) {
            new ChrestView(_parent, _model, _model.getMaximumTimeForExperiment(_model.getCurrentExperimentName()));
        }
    }

    private JMenu createShellMenu() {
        JMenuItem exit = new JMenuItem("Exit", new ImageIcon(Shell.class.getResource("icons/Stop16.gif")));
        exit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.exit(0);
            }
        });

        JMenu menu = new JMenu("Shell");
        menu.setMnemonic(KeyEvent.VK_S);
        menu.add(new AboutAction(this));
        menu.getItem(0).setMnemonic(KeyEvent.VK_A);
        menu.add(new LookFeelAction(this));
        menu.getItem(1).setMnemonic(KeyEvent.VK_T);
        menu.add(new JSeparator());
        menu.add(exit);
        menu.getItem(3).setMnemonic(KeyEvent.VK_X);

        menu.getItem(0).setAccelerator(KeyStroke.getKeyStroke('A', java.awt.Event.CTRL_MASK, false));
        menu.getItem(3).setAccelerator(KeyStroke.getKeyStroke('X', java.awt.Event.CTRL_MASK, false));
        return menu;
    }

    private JMenu createDataMenu() {
        this._dataMenu = new JMenu("Data");
        this._dataMenu.setMnemonic(KeyEvent.VK_D);

        JMenu scriptedExperimentSubMenu = new JMenu("Load Scripted Experiment");
        scriptedExperimentSubMenu.add(
                new LoadScriptedExperimentAction(this, "Paired Associate: Fast/Slow", "PairedAssociateFastSlow"));
        this._dataMenu.add(scriptedExperimentSubMenu);

        this._dataMenu.add(new LoadPreExperimentDataAction(this))
                .setAccelerator(KeyStroke.getKeyStroke('P', java.awt.Event.CTRL_MASK, false));
        this._dataMenu.add(new LoadExperimentDataAction(this))
                .setAccelerator(KeyStroke.getKeyStroke('O', java.awt.Event.CTRL_MASK, false));
        return this._dataMenu;
    }

    private JMenu createModelMenu(int time) {
        JMenu menu = new JMenu("Model");
        menu.setMnemonic(KeyEvent.VK_M);
        menu.add(new ClearModelAction(this));
        menu.getItem(0).setMnemonic(KeyEvent.VK_C);

        JMenu submenu = new JMenu("Save");
        submenu.setMnemonic(KeyEvent.VK_S);
        submenu.add(new SaveModelAsVnaAction(this, time));
        submenu.getItem(0).setMnemonic(KeyEvent.VK_N);
        submenu.add(new SaveModelSemanticLinksAsVnaAction(this, time));
        submenu.getItem(1).setMnemonic(KeyEvent.VK_L);
        menu.add(submenu);

        menu.add(new ModelPropertiesAction(this));
        menu.getItem(2).setMnemonic(KeyEvent.VK_P);
        menu.add(new JSeparator());
        menu.add(new ModelInformationAction(this, time));
        menu.getItem(4).setMnemonic(KeyEvent.VK_I);
        menu.add(new ViewModelAction(this));
        menu.getItem(5).setMnemonic(KeyEvent.VK_V);

        return menu;
    }

    /**
     * Set theme of user interface to the one named.
     */
    private void setTheme(String theme) {
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if (theme.equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (UnsupportedLookAndFeelException e) {
        } catch (ClassNotFoundException e) {
        } catch (InstantiationException e) {
        } catch (IllegalAccessException e) {
        }
        // make sure all components are updated
        SwingUtilities.updateComponentTreeUI(this);
        // SwingUtilities.updateComponentTreeUI(_fileChooser); TODO: update FileUtilities filechooser
    }

    /**
     * Start the GUI.
     * 
     * @param args Non-applicable.
     */
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Shell shell = new Shell();
                shell.setVisible(true);
            }
        });
    }
}