au.org.ala.delta.intkey.Intkey.java Source code

Java tutorial

Introduction

Here is the source code for au.org.ala.delta.intkey.Intkey.java

Source

/*******************************************************************************
 * Copyright (C) 2011 Atlas of Living Australia
 * All Rights Reserved.
 * 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 ******************************************************************************/
package au.org.ala.delta.intkey;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;

import javax.imageio.ImageIO;
import javax.swing.ActionMap;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputAdapter;
import javax.swing.plaf.FontUIResource;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.FloatRange;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.jdesktop.application.Action;
import org.jdesktop.application.Resource;
import org.jdesktop.application.ResourceMap;

import au.org.ala.delta.Logger;
import au.org.ala.delta.directives.AbstractDirective;
import au.org.ala.delta.intkey.directives.ChangeDirective;
import au.org.ala.delta.intkey.directives.CharactersDirective;
import au.org.ala.delta.intkey.directives.CommentDirective;
import au.org.ala.delta.intkey.directives.ContentsDirective;
import au.org.ala.delta.intkey.directives.DefineButtonDirective;
import au.org.ala.delta.intkey.directives.DefineCharactersDirective;
import au.org.ala.delta.intkey.directives.DefineEndIdentifyDirective;
import au.org.ala.delta.intkey.directives.DefineInformationDirective;
import au.org.ala.delta.intkey.directives.DefineNamesDirective;
import au.org.ala.delta.intkey.directives.DefineSubjectsDirective;
import au.org.ala.delta.intkey.directives.DefineTaxaDirective;
import au.org.ala.delta.intkey.directives.DescribeDirective;
import au.org.ala.delta.intkey.directives.DiagnoseDirective;
import au.org.ala.delta.intkey.directives.DifferencesDirective;
import au.org.ala.delta.intkey.directives.DisplayCharacterOrderBestDirective;
import au.org.ala.delta.intkey.directives.DisplayCharacterOrderNaturalDirective;
import au.org.ala.delta.intkey.directives.DisplayCharacterOrderSeparateDirective;
import au.org.ala.delta.intkey.directives.DisplayCommentsDirective;
import au.org.ala.delta.intkey.directives.DisplayContinuousDirective;
import au.org.ala.delta.intkey.directives.DisplayEndIdentifyDirective;
import au.org.ala.delta.intkey.directives.DisplayImagesDirective;
import au.org.ala.delta.intkey.directives.DisplayInapplicablesDirective;
import au.org.ala.delta.intkey.directives.DisplayInputDirective;
import au.org.ala.delta.intkey.directives.DisplayKeywordsDirective;
import au.org.ala.delta.intkey.directives.DisplayLogDirective;
import au.org.ala.delta.intkey.directives.DisplayNumberingDirective;
import au.org.ala.delta.intkey.directives.DisplayScaledDirective;
import au.org.ala.delta.intkey.directives.DisplayUnknownsDirective;
import au.org.ala.delta.intkey.directives.ExcludeCharactersDirective;
import au.org.ala.delta.intkey.directives.ExcludeTaxaDirective;
import au.org.ala.delta.intkey.directives.FileCharactersDirective;
import au.org.ala.delta.intkey.directives.FileDisplayDirective;
import au.org.ala.delta.intkey.directives.FileInputDirective;
import au.org.ala.delta.intkey.directives.FileJournalDirective;
import au.org.ala.delta.intkey.directives.FileLogDirective;
import au.org.ala.delta.intkey.directives.FileOutputDirective;
import au.org.ala.delta.intkey.directives.FileTaxaDirective;
import au.org.ala.delta.intkey.directives.FindCharactersDirective;
import au.org.ala.delta.intkey.directives.FindTaxaDirective;
import au.org.ala.delta.intkey.directives.IllustrateCharactersDirective;
import au.org.ala.delta.intkey.directives.IllustrateTaxaDirective;
import au.org.ala.delta.intkey.directives.IncludeCharactersDirective;
import au.org.ala.delta.intkey.directives.IncludeTaxaDirective;
import au.org.ala.delta.intkey.directives.InformationDirective;
import au.org.ala.delta.intkey.directives.IntkeyDirectiveParseException;
import au.org.ala.delta.intkey.directives.NewDatasetDirective;
import au.org.ala.delta.intkey.directives.OutputCharactersDirective;
import au.org.ala.delta.intkey.directives.OutputCommentDirective;
import au.org.ala.delta.intkey.directives.OutputDescribeDirective;
import au.org.ala.delta.intkey.directives.OutputDiagnoseDirective;
import au.org.ala.delta.intkey.directives.OutputDifferencesDirective;
import au.org.ala.delta.intkey.directives.OutputSimilaritiesDirective;
import au.org.ala.delta.intkey.directives.OutputSummaryDirective;
import au.org.ala.delta.intkey.directives.OutputTaxaDirective;
import au.org.ala.delta.intkey.directives.PreferencesDirective;
import au.org.ala.delta.intkey.directives.RestartDirective;
import au.org.ala.delta.intkey.directives.SetAutoToleranceDirective;
import au.org.ala.delta.intkey.directives.SetDemonstrationDirective;
import au.org.ala.delta.intkey.directives.SetDiagLevelDirective;
import au.org.ala.delta.intkey.directives.SetDiagTypeSpecimensDirective;
import au.org.ala.delta.intkey.directives.SetDiagTypeTaxaDirective;
import au.org.ala.delta.intkey.directives.SetExactDirective;
import au.org.ala.delta.intkey.directives.SetFixDirective;
import au.org.ala.delta.intkey.directives.SetImagePathDirective;
import au.org.ala.delta.intkey.directives.SetInfoPathDirective;
import au.org.ala.delta.intkey.directives.SetMatchDirective;
import au.org.ala.delta.intkey.directives.SetRBaseDirective;
import au.org.ala.delta.intkey.directives.SetReliabilitiesDirective;
import au.org.ala.delta.intkey.directives.SetStopBestDirective;
import au.org.ala.delta.intkey.directives.SetToleranceDirective;
import au.org.ala.delta.intkey.directives.SetVaryWtDirective;
import au.org.ala.delta.intkey.directives.ShowDirective;
import au.org.ala.delta.intkey.directives.SimilaritiesDirective;
import au.org.ala.delta.intkey.directives.StatusAllDirective;
import au.org.ala.delta.intkey.directives.StatusDisplayDirective;
import au.org.ala.delta.intkey.directives.StatusExcludeCharactersDirective;
import au.org.ala.delta.intkey.directives.StatusExcludeTaxaDirective;
import au.org.ala.delta.intkey.directives.StatusFilesDirective;
import au.org.ala.delta.intkey.directives.StatusIncludeCharactersDirective;
import au.org.ala.delta.intkey.directives.StatusIncludeTaxaDirective;
import au.org.ala.delta.intkey.directives.StatusSetDirective;
import au.org.ala.delta.intkey.directives.SummaryDirective;
import au.org.ala.delta.intkey.directives.TaxaDirective;
import au.org.ala.delta.intkey.directives.UseDirective;
import au.org.ala.delta.intkey.model.DisplayImagesReportType;
import au.org.ala.delta.intkey.model.ImageDisplayMode;
import au.org.ala.delta.intkey.model.IntkeyCharacterOrder;
import au.org.ala.delta.intkey.model.IntkeyContext;
import au.org.ala.delta.intkey.model.IntkeyDataset;
import au.org.ala.delta.intkey.model.SearchUtils;
import au.org.ala.delta.intkey.model.StartupFileData;
import au.org.ala.delta.intkey.model.StartupUtils;
import au.org.ala.delta.intkey.ui.AllowMismatchMessagePanel;
import au.org.ala.delta.intkey.ui.AttributeCellRenderer;
import au.org.ala.delta.intkey.ui.BestCharacterCellRenderer;
import au.org.ala.delta.intkey.ui.BusyGlassPane;
import au.org.ala.delta.intkey.ui.CharacterCellRenderer;
import au.org.ala.delta.intkey.ui.CharacterImageDialog;
import au.org.ala.delta.intkey.ui.CharacterKeywordSelectionDialog;
import au.org.ala.delta.intkey.ui.CharacterSelectionDialog;
import au.org.ala.delta.intkey.ui.ContentsDialog;
import au.org.ala.delta.intkey.ui.DefineButtonDialog;
import au.org.ala.delta.intkey.ui.DirectiveAction;
import au.org.ala.delta.intkey.ui.DirectivePopulator;
import au.org.ala.delta.intkey.ui.DirectivePopulatorInterceptor;
import au.org.ala.delta.intkey.ui.DisplayImagesDialog;
import au.org.ala.delta.intkey.ui.EditDatasetIndexDialog;
import au.org.ala.delta.intkey.ui.FindInCharactersDialog;
import au.org.ala.delta.intkey.ui.FindInTaxaDialog;
import au.org.ala.delta.intkey.ui.ImageDialog;
import au.org.ala.delta.intkey.ui.ImageUtils;
import au.org.ala.delta.intkey.ui.IntKeyDialogController;
import au.org.ala.delta.intkey.ui.IntegerInputDialog;
import au.org.ala.delta.intkey.ui.IntkeyUI;
import au.org.ala.delta.intkey.ui.IntkeyUIInterceptor;
import au.org.ala.delta.intkey.ui.MenuBuilder;
import au.org.ala.delta.intkey.ui.MessagePanel;
import au.org.ala.delta.intkey.ui.MultiStateInputDialog;
import au.org.ala.delta.intkey.ui.OnOffPromptDialog;
import au.org.ala.delta.intkey.ui.OpenDataSetDialog;
import au.org.ala.delta.intkey.ui.ReExecuteDialog;
import au.org.ala.delta.intkey.ui.RealInputDialog;
import au.org.ala.delta.intkey.ui.RtfReportDisplayDialog;
import au.org.ala.delta.intkey.ui.SetMainWindowSizeDialog;
import au.org.ala.delta.intkey.ui.SetMatchPromptDialog;
import au.org.ala.delta.intkey.ui.TaxonCellRenderer;
import au.org.ala.delta.intkey.ui.TaxonImageDialog;
import au.org.ala.delta.intkey.ui.TaxonInformationDialog;
import au.org.ala.delta.intkey.ui.TaxonKeywordSelectionDialog;
import au.org.ala.delta.intkey.ui.TaxonSelectionDialog;
import au.org.ala.delta.intkey.ui.TaxonWithDifferenceCountCellRenderer;
import au.org.ala.delta.intkey.ui.TextInputDialog;
import au.org.ala.delta.intkey.ui.ToolbarHelpDialog;
import au.org.ala.delta.intkey.ui.UIUtils;
import au.org.ala.delta.model.Attribute;
import au.org.ala.delta.model.Character;
import au.org.ala.delta.model.IntegerCharacter;
import au.org.ala.delta.model.Item;
import au.org.ala.delta.model.MatchType;
import au.org.ala.delta.model.MultiStateCharacter;
import au.org.ala.delta.model.RealCharacter;
import au.org.ala.delta.model.Specimen;
import au.org.ala.delta.model.TextAttribute;
import au.org.ala.delta.model.TextCharacter;
import au.org.ala.delta.model.format.Formatter.AngleBracketHandlingMode;
import au.org.ala.delta.model.format.Formatter.CommentStrippingMode;
import au.org.ala.delta.model.format.ItemFormatter;
import au.org.ala.delta.model.image.Image;
import au.org.ala.delta.rtf.RTFBuilder;
import au.org.ala.delta.rtf.RTFUtils;
import au.org.ala.delta.ui.AboutBox;
import au.org.ala.delta.ui.DeltaSingleFrameApplication;
import au.org.ala.delta.ui.help.HelpController;
import au.org.ala.delta.ui.rtf.SimpleRtfEditorKit;
import au.org.ala.delta.ui.util.IconHelper;
import au.org.ala.delta.util.Pair;

import com.l2fprod.common.swing.JFontChooser;

/**
 * Main UI Class
 * 
 * @author ChrisF
 * 
 */
public class Intkey extends DeltaSingleFrameApplication implements IntkeyUI, DirectivePopulator {

    // HELP IDs
    public static final String HELPSET_PATH = "help/Intkey";

    public static final String HELP_ID_TOPICS = "topics";
    public static final String HELP_ID_COMMANDS = "commands";

    public static final String HELP_ID_NO_MATCHING_TAXA_REMAIN = "no_taxa_match_the_specimen";
    public static final String HELP_ID_IDENTIFICATION_COMPLETE = "checking_an_identification";
    public static final String HELP_ID_NO_CHARACTERS_REMAINING = "not_enough_characters_for_identification";

    public static final String HELP_ID_CHARACTERS_TOOLBAR_RESTART = "characters_toolbar_restart";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_BEST = "characters_toolbar_best";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_SEPARATE = "characters_toolbar_separate";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_NATURAL = "characters_toolbar_natural";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_DIFF_SPECIMEN_REMAINING = "characters_toolbar_diff_specimen_remaining";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_TOLERANCE = "characters_toolbar_tolerance";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_SET_MATCH = "characters_toolbar_set_match";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_SUBSET_CHARACTERS = "characters_toolbar_subset_characters";
    public static final String HELP_ID_CHARACTERS_TOOLBAR_FIND_CHARACTERS = "characters_toolbar_find_characters";

    public static final String HELP_ID_TAXA_TOOLBAR_INFO = "taxa_toolbar_info";
    public static final String HELP_ID_TAXA_TOOLBAR_DIFF_TAXA = "taxa_toolbar_diff_taxa";
    public static final String HELP_ID_TAXA_TOOLBAR_SUBSET_TAXA = "taxa_toolbar_subset_taxa";
    public static final String HELP_ID_TAXA_TOOLBAR_FIND_TAXA = "taxa_toolbar_find_taxa";

    // Resource strings
    @Resource
    String windowTitleWithDatasetTitle;

    @Resource
    String availableCharactersCaption;

    @Resource
    String bestCharactersCaption;

    @Resource
    String separateCharactersCaption;

    @Resource
    String usedCharactersCaption;

    @Resource
    String remainingTaxaCaption;

    @Resource
    String eliminatedTaxaCaption;

    @Resource
    String calculatingBestCaption;

    @Resource
    String displayingReportCaption;

    @Resource
    String identificationCompleteCaption;

    @Resource
    String availableCharactersCannotSeparateCaption;

    @Resource
    String noMatchingTaxaRemainCaption;

    @Resource
    String charactersExcludedCannotSeparateCaption;

    @Resource
    String mismatchesAllowCannotSeparateCaption;

    @Resource
    String selectCharacterKeywordsCaption;

    @Resource
    String selectTaxonKeywordsCaption;

    @Resource
    String logDialogTitle;

    @Resource
    String errorDlgTitle;

    @Resource
    String informationDlgTitle;

    @Resource
    String badlyFormedRTFContentMessage;

    @Resource
    String separateInformationMessage;

    @Resource
    String noHelpAvailableCaption;

    @Resource
    String saveReportToFilePrompt;

    @Resource
    String errorWritingToFileError;

    @Resource
    String errorReadingRTFFileError;

    @Resource
    String rtfFileTooLargeError;

    // GUI components
    private JPanel _rootPanel;
    private JSplitPane _rootSplitPane;
    private JSplitPane _innerSplitPaneRight;
    private JSplitPane _innerSplitPaneLeft;
    private JTextField _txtFldCmdBar;

    private Map<String, JMenu> _cmdMenus;

    private IntkeyContext _context;
    private JList _listAvailableCharacters;
    private JList _listUsedCharacters;
    private JList _listRemainingTaxa;
    private JList _listEliminatedTaxa;

    private CharacterCellRenderer _availableCharactersListCellRenderer;
    private AttributeCellRenderer _usedCharactersListCellRenderer;
    private TaxonCellRenderer _availableTaxaCellRenderer;
    private TaxonWithDifferenceCountCellRenderer _eliminatedTaxaCellRenderer;

    private DefaultListModel _availableCharacterListModel;
    private DefaultListModel _usedCharacterListModel;
    private DefaultListModel _availableTaxaListModel;
    private DefaultListModel _eliminatedTaxaListModel;

    private JLabel _lblNumAvailableCharacters;
    private JLabel _lblNumUsedCharacters;

    private BusyGlassPane _busyGlassPane = null;
    private Component _defaultGlassPane;

    private List<Character> _foundAvailableCharacters = null;
    private List<Character> _foundUsedCharacters = null;
    private List<Item> _foundAvailableTaxa = null;
    private List<Item> _foundEliminatedTaxa = null;

    private List<JButton> _advancedModeOnlyDynamicButtons;
    private List<JButton> _normalModeOnlyDynamicButtons;
    private List<JButton> _activeOnlyWhenCharactersUsedButtons;
    private Map<JButton, String> _dynamicButtonsFullHelp;

    private JLabel _lblNumRemainingTaxa;
    private JLabel _lblEliminatedTaxa;
    private JButton _btnRestart;
    private JButton _btnBestOrder;
    private JButton _btnSeparate;
    private JButton _btnNaturalOrder;
    private JButton _btnDiffSpecimenTaxa;
    private JButton _btnSetTolerance;
    private JButton _btnSetMatch;
    private JButton _btnSubsetCharacters;
    private JButton _btnFindCharacter;
    private JButton _btnTaxonInfo;
    private JButton _btnDiffTaxa;
    private JButton _btnSubsetTaxa;
    private JButton _btnFindTaxon;
    private JButton _btnContextHelp;
    private JPanel _pnlAvailableCharacters;
    private JPanel _pnlAvailableCharactersButtons;
    private JPanel _pnlUsedCharacters;
    private JScrollPane _sclPnUsedCharacters;
    private JPanel _pnlUsedCharactersHeader;
    private JPanel _pnlRemainingTaxa;
    private JScrollPane _sclPnRemainingTaxa;
    private JPanel _pnlRemainingTaxaHeader;
    private JPanel _pnlRemainingTaxaButtons;
    private JPanel _pnlEliminatedTaxa;
    private JScrollPane _sclPnEliminatedTaxa;
    private JPanel _pnlEliminatedTaxaHeader;
    private JPanel _globalOptionBar;
    private JScrollPane _sclPaneAvailableCharacters;
    private JPanel _pnlAvailableCharactersHeader;
    private JPanel _pnlDynamicButtons;

    private RtfReportDisplayDialog _logDialog;

    private ItemFormatter _taxonformatter;

    private HelpController _helpController;

    /**
     * The resource path where icons are located
     */
    private static String INTKEY_ICON_PATH = "/au/org/ala/delta/intkey/resources/icons";

    /**
     * True if the user interface is in advanced mode
     */
    private boolean _advancedMode = false;

    /**
     * The initialization file to open on startup, as supplied on the command
     * line
     */
    private String _datasetInitFileToOpen = null;

    /**
     * The preferences file to execute on startup, as supplied on the command
     * line
     */
    private String _startupPreferencesFile = null;

    /**
     * If true, dataset startup images should not be displayed.
     */
    private boolean _suppressStartupImages = false;

    /**
     * The directory containing the startup file for the last dataset that was
     * opened.
     */
    private File _lastOpenedDatasetDirectory = null;

    /**
     * Calls Desktop.getDesktop on a background thread as it's slow to
     * initialise
     */
    private SwingWorker<Desktop, Void> _desktopWorker;

    /**
     * Main method.
     * 
     * @param args
     *            Command line arguments:<br/>
     *            1. filename Specifies the name of an initialization file (for
     *            example, C:\\ANGIO\\INTKEY.INI). The corresponding data set is
     *            then automatically loaded when the program starts, and the
     *            data-set selection box is not displayed.<br/>
     *            2. -A Sets Advanced mode.<br/>
     *            3. -I Suppresses display of startup images.<br/>
     *            4. -P=filename Specifies the name of a preferences file - see
     *            help for the "Preferences" command. The default directory is
     *            the one containing the program.
     */
    public static void main(String[] args) {
        setupMacSystemProperties(Intkey.class);
        launch(Intkey.class, args);
    }

    /**
     * ctor - Not called directly. This is called by the swing application
     * framework
     */
    public Intkey() {
        // Update resources bundle with desired look and feel before the swing
        // application framework can set the defaults.
        setLookAndFeel();
    }

    /**
     * Perform initialization before the GUI is contstructed. This method is
     * called by the swing application framework
     * 
     * @param args
     *            - Command line arguments from the main method
     */
    @Override
    protected void initialize(String[] args) {
        ResourceMap resourceMap = getContext().getResourceMap(Intkey.class);
        resourceMap.injectFields(this);

        // Define and parse command line arguments
        Options options = new Options();
        options.addOption("A", false, "Startup in advanced mode.");
        options.addOption("I", false, "Suppress display of startup images.");
        Option preferencesOption = OptionBuilder.withArgName("filename").hasArg()
                .withDescription("Use the specified file as the preferences file.").create("P");
        options.addOption(preferencesOption);

        boolean cmdLineParseSuccess = true;
        CommandLineParser parser = new GnuParser();
        try {
            CommandLine cmdLine = parser.parse(options, args, false);

            if (cmdLine.hasOption("A")) {
                _advancedMode = true;
            }

            if (cmdLine.hasOption("I")) {
                _suppressStartupImages = true;
            }

            if (cmdLine.hasOption("P")) {
                _startupPreferencesFile = cmdLine.getOptionValue("P");
                if (StringUtils.isEmpty(_startupPreferencesFile)) {
                    cmdLineParseSuccess = false;
                }
            }

            if (cmdLine.getArgList().size() == 1) {
                _datasetInitFileToOpen = (String) cmdLine.getArgList().get(0);
            }

            if (cmdLine.getArgList().size() > 1) {
                cmdLineParseSuccess = false;
            }

        } catch (ParseException ex) {
            cmdLineParseSuccess = false;
        }

        if (!cmdLineParseSuccess) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("Intkey [dataset-init-file] [options]", options);
            System.exit(0);
        }

        // If _startupInAdvancedMode has not already been set to true using the
        // "-A" command line option (see above),
        // Check saved application state for the mode (advanced or basic) that
        // was last used in the application.
        if (!_advancedMode) {
            _advancedMode = UIUtils.getPreviousApplicationMode();
        }

        // Get location of last opened dataset from saved application state
        _lastOpenedDatasetDirectory = UIUtils.getSavedLastOpenedDatasetDirectory();
    }

    /**
     * Creates and shows the GUI. Called by the swing application framework
     */
    @Override
    protected void startup() {
        final JFrame mainFrame = getMainFrame();
        _defaultGlassPane = mainFrame.getGlassPane();
        mainFrame.setTitle("Intkey");
        mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        mainFrame.setIconImages(IconHelper.getRedIconList());

        _helpController = new HelpController(HELPSET_PATH);

        _taxonformatter = new ItemFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE,
                true, false, true);
        _context = new IntkeyContext(new IntkeyUIInterceptor(this), new DirectivePopulatorInterceptor(this));

        _advancedModeOnlyDynamicButtons = new ArrayList<JButton>();
        _normalModeOnlyDynamicButtons = new ArrayList<JButton>();
        _activeOnlyWhenCharactersUsedButtons = new ArrayList<JButton>();
        _dynamicButtonsFullHelp = new HashMap<JButton, String>();

        ActionMap actionMap = getContext().getActionMap();

        _rootPanel = new JPanel();
        _rootPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        _rootPanel.setBackground(SystemColor.control);
        _rootPanel.setLayout(new BorderLayout(0, 0));

        _globalOptionBar = new JPanel();
        _globalOptionBar.setBorder(new EmptyBorder(0, 5, 0, 5));
        _rootPanel.add(_globalOptionBar, BorderLayout.NORTH);
        _globalOptionBar.setLayout(new BorderLayout(0, 0));

        _pnlDynamicButtons = new JPanel();
        FlowLayout flowLayout_1 = (FlowLayout) _pnlDynamicButtons.getLayout();
        flowLayout_1.setVgap(0);
        flowLayout_1.setHgap(0);
        _globalOptionBar.add(_pnlDynamicButtons, BorderLayout.WEST);

        _btnContextHelp = new JButton();
        _btnContextHelp.setMinimumSize(new Dimension(30, 30));
        _btnContextHelp.setMaximumSize(new Dimension(30, 30));
        _btnContextHelp.setAction(actionMap.get("btnContextHelp"));
        _btnContextHelp.setPreferredSize(new Dimension(30, 30));
        _btnContextHelp.setMargin(new Insets(2, 5, 2, 5));
        _btnContextHelp.addActionListener(actionMap.get("btnContextHelp"));
        _globalOptionBar.add(_btnContextHelp, BorderLayout.EAST);

        _rootSplitPane = new JSplitPane();
        _rootSplitPane.setDividerSize(3);
        _rootSplitPane.setResizeWeight(0.5);
        _rootSplitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
        _rootSplitPane.setContinuousLayout(true);
        _rootPanel.add(_rootSplitPane);

        _innerSplitPaneLeft = new JSplitPane();
        _innerSplitPaneLeft.setMinimumSize(new Dimension(25, 25));
        _innerSplitPaneLeft.setAlignmentX(Component.CENTER_ALIGNMENT);
        _innerSplitPaneLeft.setDividerSize(3);
        _innerSplitPaneLeft.setResizeWeight(0.5);

        _innerSplitPaneLeft.setContinuousLayout(true);
        _innerSplitPaneLeft.setOrientation(JSplitPane.VERTICAL_SPLIT);
        _rootSplitPane.setLeftComponent(_innerSplitPaneLeft);

        _pnlAvailableCharacters = new JPanel();
        _innerSplitPaneLeft.setLeftComponent(_pnlAvailableCharacters);
        _pnlAvailableCharacters.setLayout(new BorderLayout(0, 0));

        _sclPaneAvailableCharacters = new JScrollPane();
        _pnlAvailableCharacters.add(_sclPaneAvailableCharacters, BorderLayout.CENTER);

        _listAvailableCharacters = new JList();
        // _listAvailableCharacters.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        _listAvailableCharacters.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        _listAvailableCharacters.setCellRenderer(_availableCharactersListCellRenderer);
        _listAvailableCharacters.addMouseListener(new MouseInputAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() >= 2) {
                    int selectedIndex = _listAvailableCharacters.getSelectedIndex();
                    if (selectedIndex >= 0) {
                        try {
                            Character ch = (Character) _availableCharacterListModel.getElementAt(selectedIndex);
                            executeDirective(new UseDirective(), Integer.toString(ch.getCharacterId()));
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            }
        });

        _sclPaneAvailableCharacters.setViewportView(_listAvailableCharacters);

        _pnlAvailableCharactersHeader = new JPanel();
        _pnlAvailableCharacters.add(_pnlAvailableCharactersHeader, BorderLayout.NORTH);
        _pnlAvailableCharactersHeader.setLayout(new BorderLayout(0, 0));

        _lblNumAvailableCharacters = new JLabel();
        _lblNumAvailableCharacters.setBorder(new EmptyBorder(0, 5, 0, 0));
        _lblNumAvailableCharacters.setFont(new Font("Tahoma", Font.PLAIN, 15));
        _lblNumAvailableCharacters.setText(MessageFormat.format(availableCharactersCaption, 0));
        _pnlAvailableCharactersHeader.add(_lblNumAvailableCharacters, BorderLayout.WEST);

        _pnlAvailableCharactersButtons = new JPanel();
        FlowLayout flowLayout = (FlowLayout) _pnlAvailableCharactersButtons.getLayout();
        flowLayout.setVgap(2);
        flowLayout.setHgap(2);
        _pnlAvailableCharactersHeader.add(_pnlAvailableCharactersButtons, BorderLayout.EAST);

        // All toolbar buttons should be disabled until a dataset is loaded.
        _btnRestart = new JButton();
        _btnRestart.setAction(actionMap.get("btnRestart"));
        _btnRestart.setPreferredSize(new Dimension(30, 30));
        _btnRestart.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnRestart);

        _btnBestOrder = new JButton();
        _btnBestOrder.setAction(actionMap.get("btnBestOrder"));
        _btnBestOrder.setPreferredSize(new Dimension(30, 30));
        _btnBestOrder.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnBestOrder);

        _btnSeparate = new JButton();
        _btnSeparate.setAction(actionMap.get("btnSeparate"));
        _btnSeparate.setVisible(_advancedMode);
        _btnSeparate.setPreferredSize(new Dimension(30, 30));
        _btnSeparate.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnSeparate);

        _btnNaturalOrder = new JButton();
        _btnNaturalOrder.setAction(actionMap.get("btnNaturalOrder"));
        _btnNaturalOrder.setPreferredSize(new Dimension(30, 30));
        _btnNaturalOrder.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnNaturalOrder);

        _btnDiffSpecimenTaxa = new JButton();
        _btnDiffSpecimenTaxa.setAction(actionMap.get("btnDiffSpecimenTaxa"));
        _btnDiffSpecimenTaxa.setEnabled(false);
        _btnDiffSpecimenTaxa.setPreferredSize(new Dimension(30, 30));
        _pnlAvailableCharactersButtons.add(_btnDiffSpecimenTaxa);

        _btnSetTolerance = new JButton();
        _btnSetTolerance.setAction(actionMap.get("btnSetTolerance"));
        _btnSetTolerance.setPreferredSize(new Dimension(30, 30));
        _btnSetTolerance.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnSetTolerance);

        _btnSetMatch = new JButton();
        _btnSetMatch.setAction(actionMap.get("btnSetMatch"));
        _btnSetMatch.setVisible(_advancedMode);
        _btnSetMatch.setPreferredSize(new Dimension(30, 30));
        _btnSetMatch.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnSetMatch);

        _btnSubsetCharacters = new JButton();
        _btnSubsetCharacters.setAction(actionMap.get("btnSubsetCharacters"));
        _btnSubsetCharacters.setPreferredSize(new Dimension(30, 30));
        _btnSubsetCharacters.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnSubsetCharacters);

        _btnFindCharacter = new JButton();
        _btnFindCharacter.setAction(actionMap.get("btnFindCharacter"));
        _btnFindCharacter.setPreferredSize(new Dimension(30, 30));
        _btnFindCharacter.setEnabled(false);
        _pnlAvailableCharactersButtons.add(_btnFindCharacter);

        _pnlAvailableCharactersButtons.setEnabled(false);

        _pnlUsedCharacters = new JPanel();
        _innerSplitPaneLeft.setRightComponent(_pnlUsedCharacters);
        _pnlUsedCharacters.setLayout(new BorderLayout(0, 0));

        _sclPnUsedCharacters = new JScrollPane();
        _pnlUsedCharacters.add(_sclPnUsedCharacters, BorderLayout.CENTER);

        _listUsedCharacters = new JList();
        _listUsedCharacters.setCellRenderer(_usedCharactersListCellRenderer);
        _listUsedCharacters.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        _listUsedCharacters.addMouseListener(new MouseInputAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() >= 2) {
                    int selectedIndex = _listUsedCharacters.getSelectedIndex();
                    if (selectedIndex >= 0) {
                        try {
                            Attribute attr = (Attribute) _usedCharacterListModel.getElementAt(selectedIndex);

                            if (_context.charactersFixed() && _context.getFixedCharactersList()
                                    .contains(attr.getCharacter().getCharacterId())) {
                                return;
                            }

                            executeDirective(new ChangeDirective(),
                                    Integer.toString(attr.getCharacter().getCharacterId()));
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }
            }
        });

        _sclPnUsedCharacters.setViewportView(_listUsedCharacters);

        _pnlUsedCharactersHeader = new JPanel();
        _pnlUsedCharacters.add(_pnlUsedCharactersHeader, BorderLayout.NORTH);
        _pnlUsedCharactersHeader.setLayout(new BorderLayout(0, 0));

        _lblNumUsedCharacters = new JLabel();
        _lblNumUsedCharacters.setBorder(new EmptyBorder(7, 5, 7, 0));
        _lblNumUsedCharacters.setFont(new Font("Tahoma", Font.PLAIN, 15));
        _lblNumUsedCharacters.setText(MessageFormat.format(usedCharactersCaption, 0));
        _pnlUsedCharactersHeader.add(_lblNumUsedCharacters, BorderLayout.WEST);

        _innerSplitPaneRight = new JSplitPane();
        _innerSplitPaneRight.setMinimumSize(new Dimension(25, 25));
        _innerSplitPaneRight.setDividerSize(3);
        _innerSplitPaneRight.setResizeWeight(0.5);
        _innerSplitPaneRight.setContinuousLayout(true);
        _innerSplitPaneRight.setOrientation(JSplitPane.VERTICAL_SPLIT);
        _rootSplitPane.setRightComponent(_innerSplitPaneRight);

        _pnlRemainingTaxa = new JPanel();
        _innerSplitPaneRight.setLeftComponent(_pnlRemainingTaxa);
        _pnlRemainingTaxa.setLayout(new BorderLayout(0, 0));

        _sclPnRemainingTaxa = new JScrollPane();
        _pnlRemainingTaxa.add(_sclPnRemainingTaxa, BorderLayout.CENTER);

        _listRemainingTaxa = new JList();

        _listRemainingTaxa.addMouseListener(new MouseInputAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() >= 2) {
                    displayInfoForSelectedTaxa();
                }
            }
        });

        _sclPnRemainingTaxa.setViewportView(_listRemainingTaxa);

        _pnlRemainingTaxaHeader = new JPanel();
        _pnlRemainingTaxa.add(_pnlRemainingTaxaHeader, BorderLayout.NORTH);
        _pnlRemainingTaxaHeader.setLayout(new BorderLayout(0, 0));

        _lblNumRemainingTaxa = new JLabel();
        _lblNumRemainingTaxa.setBorder(new EmptyBorder(0, 5, 0, 0));
        _lblNumRemainingTaxa.setFont(new Font("Tahoma", Font.PLAIN, 15));
        _lblNumRemainingTaxa.setText(MessageFormat.format(remainingTaxaCaption, 0));
        _pnlRemainingTaxaHeader.add(_lblNumRemainingTaxa, BorderLayout.WEST);

        _pnlRemainingTaxaButtons = new JPanel();
        FlowLayout fl_pnlRemainingTaxaButtons = (FlowLayout) _pnlRemainingTaxaButtons.getLayout();
        fl_pnlRemainingTaxaButtons.setVgap(2);
        fl_pnlRemainingTaxaButtons.setHgap(2);
        _pnlRemainingTaxaHeader.add(_pnlRemainingTaxaButtons, BorderLayout.EAST);

        // All toolbar buttons should be disabled until a dataset is loaded.
        _btnTaxonInfo = new JButton();
        _btnTaxonInfo.setAction(actionMap.get("btnTaxonInfo"));
        _btnTaxonInfo.setPreferredSize(new Dimension(30, 30));
        _btnTaxonInfo.setEnabled(false);
        _pnlRemainingTaxaButtons.add(_btnTaxonInfo);

        _btnDiffTaxa = new JButton();
        _btnDiffTaxa.setAction(actionMap.get("btnDiffTaxa"));
        _btnDiffTaxa.setPreferredSize(new Dimension(30, 30));
        _btnDiffTaxa.setEnabled(false);
        _pnlRemainingTaxaButtons.add(_btnDiffTaxa);

        _btnSubsetTaxa = new JButton();
        _btnSubsetTaxa.setAction(actionMap.get("btnSubsetTaxa"));
        _btnSubsetTaxa.setPreferredSize(new Dimension(30, 30));
        _btnSubsetTaxa.setEnabled(false);
        _pnlRemainingTaxaButtons.add(_btnSubsetTaxa);

        _btnFindTaxon = new JButton();
        _btnFindTaxon.setAction(actionMap.get("btnFindTaxon"));
        _btnFindTaxon.setPreferredSize(new Dimension(30, 30));
        _btnFindTaxon.setEnabled(false);
        _pnlRemainingTaxaButtons.add(_btnFindTaxon);

        _pnlEliminatedTaxa = new JPanel();
        _innerSplitPaneRight.setRightComponent(_pnlEliminatedTaxa);
        _pnlEliminatedTaxa.setLayout(new BorderLayout(0, 0));

        _sclPnEliminatedTaxa = new JScrollPane();
        _pnlEliminatedTaxa.add(_sclPnEliminatedTaxa, BorderLayout.CENTER);

        _listEliminatedTaxa = new JList();

        _listEliminatedTaxa.addMouseListener(new MouseInputAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() >= 2) {
                    displayInfoForSelectedTaxa();
                }
            }
        });

        _sclPnEliminatedTaxa.setViewportView(_listEliminatedTaxa);

        _pnlEliminatedTaxaHeader = new JPanel();
        _pnlEliminatedTaxa.add(_pnlEliminatedTaxaHeader, BorderLayout.NORTH);
        _pnlEliminatedTaxaHeader.setLayout(new BorderLayout(0, 0));

        _lblEliminatedTaxa = new JLabel();
        _lblEliminatedTaxa.setBorder(new EmptyBorder(7, 5, 7, 0));
        _lblEliminatedTaxa.setFont(new Font("Tahoma", Font.PLAIN, 15));
        _lblEliminatedTaxa.setText(MessageFormat.format(eliminatedTaxaCaption, 0));
        _pnlEliminatedTaxaHeader.add(_lblEliminatedTaxa, BorderLayout.WEST);

        JMenuBar menuBar = buildMenus(_advancedMode);
        getMainView().setMenuBar(menuBar);

        _txtFldCmdBar = new JTextField();
        _txtFldCmdBar.setCaretColor(Color.WHITE);
        _txtFldCmdBar.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                String cmdStr = _txtFldCmdBar.getText();

                cmdStr = cmdStr.trim();
                if (_cmdMenus.containsKey(cmdStr)) {
                    JMenu cmdMenu = _cmdMenus.get(cmdStr);
                    cmdMenu.doClick();
                } else {
                    _context.parseAndExecuteDirective(cmdStr);
                }
                _txtFldCmdBar.setText(null);
            }
        });

        _txtFldCmdBar.setFont(new Font("Courier New", Font.BOLD, 13));
        _txtFldCmdBar.setForeground(SystemColor.text);
        _txtFldCmdBar.setBackground(Color.BLACK);
        _txtFldCmdBar.setOpaque(true);
        _txtFldCmdBar.setVisible(_advancedMode);
        _rootPanel.add(_txtFldCmdBar, BorderLayout.SOUTH);
        _txtFldCmdBar.setColumns(10);

        _logDialog = new RtfReportDisplayDialog(getMainFrame(), new SimpleRtfEditorKit(null), null, logDialogTitle);

        // Set context-sensitive help keys for toolbar buttons
        _helpController.setHelpKeyForComponent(_btnRestart, HELP_ID_CHARACTERS_TOOLBAR_RESTART);
        _helpController.setHelpKeyForComponent(_btnBestOrder, HELP_ID_CHARACTERS_TOOLBAR_BEST);
        _helpController.setHelpKeyForComponent(_btnSeparate, HELP_ID_CHARACTERS_TOOLBAR_SEPARATE);
        _helpController.setHelpKeyForComponent(_btnNaturalOrder, HELP_ID_CHARACTERS_TOOLBAR_NATURAL);
        _helpController.setHelpKeyForComponent(_btnDiffSpecimenTaxa,
                HELP_ID_CHARACTERS_TOOLBAR_DIFF_SPECIMEN_REMAINING);
        _helpController.setHelpKeyForComponent(_btnSetTolerance, HELP_ID_CHARACTERS_TOOLBAR_TOLERANCE);
        _helpController.setHelpKeyForComponent(_btnSetMatch, HELP_ID_CHARACTERS_TOOLBAR_SET_MATCH);
        _helpController.setHelpKeyForComponent(_btnSubsetCharacters, HELP_ID_CHARACTERS_TOOLBAR_SUBSET_CHARACTERS);
        _helpController.setHelpKeyForComponent(_btnFindCharacter, HELP_ID_CHARACTERS_TOOLBAR_FIND_CHARACTERS);

        _helpController.setHelpKeyForComponent(_btnTaxonInfo, HELP_ID_TAXA_TOOLBAR_INFO);
        _helpController.setHelpKeyForComponent(_btnDiffTaxa, HELP_ID_TAXA_TOOLBAR_DIFF_TAXA);
        _helpController.setHelpKeyForComponent(_btnSubsetTaxa, HELP_ID_TAXA_TOOLBAR_SUBSET_TAXA);
        _helpController.setHelpKeyForComponent(_btnFindTaxon, HELP_ID_TAXA_TOOLBAR_FIND_TAXA);

        // This mouse listener on the default glasspane is to assist with
        // context senstive help. It intercepts the mouse events,
        // determines what component was being clicked on, then takes the
        // appropriate action to provide help for the component
        _defaultGlassPane.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                // Determine what point has been clicked on
                Point glassPanePoint = e.getPoint();
                Point containerPoint = SwingUtilities.convertPoint(getMainFrame().getGlassPane(), glassPanePoint,
                        getMainFrame().getContentPane());
                Component component = SwingUtilities.getDeepestComponentAt(getMainFrame().getContentPane(),
                        containerPoint.x, containerPoint.y);

                // Get the java help ID for this component. If none has been
                // defined, this will be null
                String helpID = _helpController.getHelpKeyForComponent(component);

                // change the cursor back to the normal one and take down the
                // classpane
                mainFrame.setCursor(Cursor.getDefaultCursor());
                mainFrame.getGlassPane().setVisible(false);

                // If a help ID was found, display the related help page in the
                // help viewer
                if (_helpController.getHelpKeyForComponent(component) != null) {
                    _helpController.helpAction().actionPerformed(new ActionEvent(component, 0, null));
                    _helpController.displayHelpTopic(mainFrame, helpID);
                } else {
                    // If a dynamically-defined toolbar button was clicked, show
                    // the help for this button in the ToolbarHelpDialog.
                    if (component instanceof JButton) {
                        JButton button = (JButton) component;
                        if (_dynamicButtonsFullHelp.containsKey(button)) {
                            String fullHelpText = _dynamicButtonsFullHelp.get(button);
                            if (fullHelpText == null) {
                                fullHelpText = noHelpAvailableCaption;
                            }
                            RTFBuilder builder = new RTFBuilder();
                            builder.startDocument();
                            builder.appendText(fullHelpText);
                            builder.endDocument();
                            ToolbarHelpDialog dlg = new ToolbarHelpDialog(mainFrame, builder.toString(),
                                    button.getIcon());
                            show(dlg);
                        }
                    }
                }
            }
        });

        show(_rootPanel);
    }

    /**
     * Performs additional tasks after the GUI has been constructed and shown.
     * Called by the swing application framework
     */
    @Override
    protected void ready() {
        super.ready();
        _rootSplitPane.setDividerLocation(2.0 / 3.0);
        _innerSplitPaneLeft.setDividerLocation(2.0 / 3.0);
        _innerSplitPaneRight.setDividerLocation(2.0 / 3.0);

        loadDesktopInBackground();

        if (_advancedMode) {
            _context.setImageDisplayMode(ImageDisplayMode.MANUAL);
            _context.setCharacterOrderNatural();
            _context.setDisplayEndIdentify(false);
        }

        // If a dataset was supplied on the command line, load it
        if (_datasetInitFileToOpen != null) {
            // Need to surround file path in quotes, otherwise it may be broken
            // up into more than 1 token.
            executeDirective(new NewDatasetDirective(), "\"" + _datasetInitFileToOpen + "\"");
        }

        // If a preferences file was supplied on the command line, process it
        if (_startupPreferencesFile != null) {
            // Need to surround file path in quotes, otherwise it may be broken
            // up into more than 1 token.
            executeDirective(new PreferencesDirective(), "\"" + _startupPreferencesFile + "\"");
        }

        // Show the dataset index on startup if no dataset was supplied on the
        // command line
        if (_datasetInitFileToOpen == null) {
            executeDirective(new NewDatasetDirective(), null);
        }
    }

    /**
     * Performs cleanup and related tasks before the application is shutdown
     */
    @Override
    protected void shutdown() {
        UIUtils.savePreviousApplicationMode(_advancedMode);
        UIUtils.saveLastOpenedDatasetDirectory(_lastOpenedDatasetDirectory);
        _context.cleanupForShutdown();
        super.shutdown();
    }

    /**
     * Build the main GUI window's menus
     * 
     * @param advancedMode
     *            true if the application is in advanced mode
     * @return a JMenuBar containing the menus
     */
    private JMenuBar buildMenus(boolean advancedMode) {

        _cmdMenus = new HashMap<String, JMenu>();

        ActionMap actionMap = getContext().getActionMap();

        JMenuBar menuBar = new JMenuBar();

        menuBar.add(buildFileMenu(true, actionMap));

        if (advancedMode) {
            menuBar.add(buildQueriesMenu(actionMap));
            menuBar.add(buildBrowsingMenu(actionMap));
            menuBar.add(buildSettingsMenu(actionMap));
            menuBar.add(buildReExecuteMenu(actionMap));
        }
        menuBar.add(buildWindowMenu(actionMap));
        menuBar.add(buildHelpMenu(advancedMode, actionMap));

        return menuBar;
    }

    /**
     * Build the file menu
     * 
     * @param advancedMode
     *            true if the application is in advanced mode
     * @param actionMap
     *            The action map for the main GUI window
     * @return a JMenu for the file menu
     */
    private JMenu buildFileMenu(boolean advancedMode, ActionMap actionMap) {
        MenuBuilder mnuFileBuilder = new MenuBuilder("mnuFile", _context);

        // Some menus/menu items should be disabled if no dataset is loaded.
        boolean isDatasetLoaded = _context.getDataset() != null;

        mnuFileBuilder.addDirectiveMenuItem("mnuItNewDataSet", new NewDatasetDirective(), true);
        mnuFileBuilder.addPreconfiguredJMenu(buildRecentFilesMenu());

        if (_advancedMode) {
            mnuFileBuilder.addDirectiveMenuItem("mnuItPreferences", new PreferencesDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItContents", new ContentsDirective(), isDatasetLoaded);

            mnuFileBuilder.addSeparator();

            mnuFileBuilder.startSubMenu("mnuFileCmds", true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileInput", new FileInputDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileOutput", new FileOutputDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileDisplay", new FileDisplayDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileLog", new FileLogDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileJournal", new FileJournalDirective(), true);
            // mnuFileBuilder.addDirectiveMenuItem("mnuItFileClose", new
            // FileCloseDirective()); ** File Close is now a NO-OP
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileCharacters", new FileCharactersDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItFileTaxa", new FileTaxaDirective(), true);
            mnuFileBuilder.endSubMenu();

            mnuFileBuilder.startSubMenu("mnuOutputCmds", true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputCharacters", new OutputCharactersDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputTaxa", new OutputTaxaDirective(), isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputDescribe", new OutputDescribeDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputSummary", new OutputSummaryDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputDiagnose", new OutputDiagnoseDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputDifferences", new OutputDifferencesDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputSimilarities", new OutputSimilaritiesDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.addDirectiveMenuItem("mnuItOutputComment", new OutputCommentDirective(),
                    isDatasetLoaded);
            mnuFileBuilder.endSubMenu();
            mnuFileBuilder.addSeparator();

            mnuFileBuilder.addDirectiveMenuItem("mnuItComment", new CommentDirective(), true);
            mnuFileBuilder.addDirectiveMenuItem("mnuItShow", new ShowDirective(), true);

            mnuFileBuilder.addSeparator();

            mnuFileBuilder.addActionMenuItem(actionMap.get("mnuItNormalMode"), true);
        } else {
            mnuFileBuilder.addSeparator();
            mnuFileBuilder.addActionMenuItem(actionMap.get("mnuItAdvancedMode"), true);
        }

        if (_advancedMode) {
            mnuFileBuilder.addSeparator();
            mnuFileBuilder.addActionMenuItem(actionMap.get("mnuItEditDataSetIndex"), true);
        }

        mnuFileBuilder.addSeparator();

        mnuFileBuilder.addActionMenuItem(actionMap.get("mnuItExitApplication"), true);

        return mnuFileBuilder.getMenu();
    }

    /**
     * Build a menu of recently opened datasets
     * 
     * @return A JMenu of recently opened datasets
     */
    private JMenu buildRecentFilesMenu() {
        Map<String, String> datasetIndexMap = UIUtils.getDatasetIndexAsMap();

        JMenu mnuFileRecents = new JMenu();
        mnuFileRecents.setName("mnuFileRecents");

        List<Pair<String, String>> recentFiles = UIUtils.getPreviouslyUsedFiles();

        for (int i = 0; i < recentFiles.size(); i++) {
            Pair<String, String> recentFile = recentFiles.get(i);
            final String filePath = recentFile.getFirst();

            int fileNumber = i + 1;

            String title;

            // If the dataset at the path as listed in the most recently used
            // datasets is listed in the index,
            // use the description listed in the dataset index.
            if (datasetIndexMap.containsKey(filePath)) {
                title = fileNumber + ". " + datasetIndexMap.get(filePath);
            } else {
                title = fileNumber + ". " + recentFile.getSecond();
            }

            JMenuItem mnuItRecentFile = new JMenuItem(title);
            mnuItRecentFile.setToolTipText(filePath);

            mnuItRecentFile.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    openPreviouslyOpenedFile(filePath);
                }
            });

            mnuFileRecents.add(mnuItRecentFile);
        }

        return mnuFileRecents;
    }

    /**
     * Build the queries menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the queries menu
     */
    private JMenu buildQueriesMenu(ActionMap actionMap) {
        // Some menus/menu items should be disabled if no dataset is loaded.
        boolean isDatasetLoaded = _context.getDataset() != null;

        JMenu mnuQueries = new JMenu();
        mnuQueries.setName("mnuQueries");

        JMenuItem mnuItRestart = new JMenuItem(new DirectiveAction(new RestartDirective(), _context));
        mnuItRestart.setName("mnuItRestart");
        mnuItRestart.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItRestart);

        mnuQueries.addSeparator();

        JMenuItem mnuItDescribe = new JMenuItem(new DirectiveAction(new DescribeDirective(), _context));
        mnuItDescribe.setName("mnuItDescribe");
        mnuItDescribe.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItDescribe);

        JMenuItem mnuItDiagnose = new JMenuItem(new DirectiveAction(new DiagnoseDirective(), _context));
        mnuItDiagnose.setName("mnuItDiagnose");
        mnuItDiagnose.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItDiagnose);

        mnuQueries.addSeparator();

        JMenuItem mnuItDifferences = new JMenuItem(new DirectiveAction(new DifferencesDirective(), _context));
        mnuItDifferences.setName("mnuItDifferences");
        mnuItDifferences.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItDifferences);
        JMenuItem mnuItSimilarities = new JMenuItem(new DirectiveAction(new SimilaritiesDirective(), _context));
        mnuItSimilarities.setName("mnuItSimilarities");
        mnuItSimilarities.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItSimilarities);

        mnuQueries.addSeparator();

        JMenuItem mnuItSummary = new JMenuItem(new DirectiveAction(new SummaryDirective(), _context));
        mnuItSummary.setName("mnuItSummary");
        mnuItSummary.setEnabled(isDatasetLoaded);
        mnuQueries.add(mnuItSummary);

        return mnuQueries;
    }

    /**
     * Build the browsing menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the browsing menu
     */
    private JMenu buildBrowsingMenu(ActionMap actionMap) {
        // Some menus/menu items should be disabled if no dataset is loaded.
        boolean isDatasetLoaded = _context.getDataset() != null;

        JMenu mnuBrowsing = new JMenu();
        mnuBrowsing.setName("mnuBrowsing");

        JMenuItem mnuItCharacters = new JMenuItem(new DirectiveAction(new CharactersDirective(), _context));
        mnuItCharacters.setName("mnuItCharacters");
        mnuItCharacters.setEnabled(isDatasetLoaded);
        mnuBrowsing.add(mnuItCharacters);
        JMenuItem mnuItTaxa = new JMenuItem(new DirectiveAction(new TaxaDirective(), _context));
        mnuItTaxa.setName("mnuItTaxa");
        mnuItTaxa.setEnabled(isDatasetLoaded);
        mnuBrowsing.add(mnuItTaxa);

        mnuBrowsing.addSeparator();

        JMenu mnuFind = new JMenu();
        mnuFind.setName("mnuFind");
        JMenuItem mnuItFindCharacters = new JMenuItem(new DirectiveAction(new FindCharactersDirective(), _context));
        mnuItFindCharacters.setName("mnuItFindCharacters");
        mnuItFindCharacters.setEnabled(isDatasetLoaded);
        mnuFind.add(mnuItFindCharacters);
        JMenuItem mnuItFindTaxa = new JMenuItem(new DirectiveAction(new FindTaxaDirective(), _context));
        mnuItFindTaxa.setName("mnuItFindTaxa");
        mnuItFindTaxa.setEnabled(isDatasetLoaded);
        mnuFind.setEnabled(isDatasetLoaded);
        mnuFind.add(mnuItFindTaxa);

        mnuBrowsing.add(mnuFind);

        mnuBrowsing.addSeparator();

        JMenu mnuIllustrate = new JMenu();
        mnuIllustrate.setName("mnuIllustrate");
        JMenuItem mnuItIllustrateCharacters = new JMenuItem(
                new DirectiveAction(new IllustrateCharactersDirective(), _context));
        mnuItIllustrateCharacters.setName("mnuItIllustrateCharacters");
        mnuItIllustrateCharacters.setEnabled(isDatasetLoaded);
        mnuIllustrate.add(mnuItIllustrateCharacters);
        JMenuItem mnuItIllustrateTaxa = new JMenuItem(new DirectiveAction(new IllustrateTaxaDirective(), _context));
        mnuItIllustrateTaxa.setName("mnuItIllustrateTaxa");
        mnuItIllustrateTaxa.setEnabled(isDatasetLoaded);
        mnuIllustrate.setEnabled(isDatasetLoaded);
        mnuIllustrate.add(mnuItIllustrateTaxa);

        mnuBrowsing.add(mnuIllustrate);

        mnuBrowsing.addSeparator();

        JMenuItem mnuItInformation = new JMenuItem(new DirectiveAction(new InformationDirective(), _context));
        mnuItInformation.setName("mnuItInformation");
        mnuItInformation.setEnabled(isDatasetLoaded);
        mnuBrowsing.add(mnuItInformation);

        return mnuBrowsing;
    }

    /**
     * Build the settings menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the settings menu
     */
    private JMenu buildSettingsMenu(ActionMap actionMap) {
        // Some menus/menu items should be disabled if no dataset is loaded.
        boolean isDatasetLoaded = _context.getDataset() != null;

        JMenu mnuSettings = new JMenu();
        mnuSettings.setName("mnuSettings");

        // "Set" submenu
        MenuBuilder mnuSetBuilder = new MenuBuilder("mnuSet", _context);

        mnuSetBuilder.startSubMenu("mnuAutotolerance", true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItAutotoleranceOn", new SetAutoToleranceDirective(), true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItAutotoleranceOff", new SetAutoToleranceDirective(), false);
        mnuSetBuilder.endSubMenu();

        mnuSetBuilder.startSubMenu("mnuDemonstration", true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItDemonstrationOn", new SetDemonstrationDirective(), true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItDemonstrationOff", new SetDemonstrationDirective(), false);
        mnuSetBuilder.endSubMenu();

        mnuSetBuilder.addDirectiveMenuItem("mnuItDiagLevel", new SetDiagLevelDirective(), true);

        mnuSetBuilder.startSubMenu("mnuDiagType", true);
        mnuSetBuilder.addDirectiveMenuItem("mnuItDiagTypeSpecimens", new SetDiagTypeSpecimensDirective(), true);
        mnuSetBuilder.addDirectiveMenuItem("mnuItDiagTypeTaxa", new SetDiagTypeTaxaDirective(), true);
        mnuSetBuilder.endSubMenu();

        mnuSetBuilder.addDirectiveMenuItem("mnuItExact", new SetExactDirective(), true);

        mnuSetBuilder.startSubMenu("mnuFix", true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItFixOn", new SetFixDirective(), true);
        mnuSetBuilder.addOnOffDirectiveMenuItem("mnuItFixOff", new SetFixDirective(), false);
        mnuSetBuilder.endSubMenu();

        mnuSetBuilder.addDirectiveMenuItem("mnuItImagePath", new SetImagePathDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItInfoPath", new SetInfoPathDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItMatch", new SetMatchDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItRbase", new SetRBaseDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItReliabilities", new SetReliabilitiesDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItStopBest", new SetStopBestDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItTolerance", new SetToleranceDirective(), true);

        mnuSetBuilder.addDirectiveMenuItem("mnuItVaryWt", new SetVaryWtDirective(), true);

        mnuSettings.add(mnuSetBuilder.getMenu());

        // "Display" submenu
        MenuBuilder mnuDisplayBuilder = new MenuBuilder("mnuDisplay", _context);
        mnuDisplayBuilder.startSubMenu("mnuCharacterOrder", true);
        mnuDisplayBuilder.addDirectiveMenuItem("mnuItCharacterOrderBest", new DisplayCharacterOrderBestDirective(),
                true);
        mnuDisplayBuilder.addDirectiveMenuItem("mnuItCharacterOrderNatural",
                new DisplayCharacterOrderNaturalDirective(), true);
        mnuDisplayBuilder.addDirectiveMenuItem("mnuItCharacterOrderSeparate",
                new DisplayCharacterOrderSeparateDirective(), true);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuComments", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItCommentsOn", new DisplayCommentsDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItCommentsOff", new DisplayCommentsDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuContinuous", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItContinuousOn", new DisplayContinuousDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItContinuousOff", new DisplayContinuousDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuEndIdentify", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItEndIdentifyOn", new DisplayEndIdentifyDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItEndIdentifyOff", new DisplayEndIdentifyDirective(),
                false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.addDirectiveMenuItem("mnuItImages", new DisplayImagesDirective(), true);

        mnuDisplayBuilder.startSubMenu("mnuInapplicables", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItInapplicablesOn", new DisplayInapplicablesDirective(),
                true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItInapplicablesOff", new DisplayInapplicablesDirective(),
                false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuInput", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItInputOn", new DisplayInputDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItInputOff", new DisplayInputDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuKeywords", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItKeywordsOn", new DisplayKeywordsDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItKeywordsOff", new DisplayKeywordsDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuLog", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItLogOn", new DisplayLogDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItLogOff", new DisplayLogDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuNumbering", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItNumberingOn", new DisplayNumberingDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItNumberingOff", new DisplayNumberingDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuScaled", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItScaledOn", new DisplayScaledDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItScaledOff", new DisplayScaledDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuDisplayBuilder.startSubMenu("mnuUnknowns", true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItUnknownsOn", new DisplayUnknownsDirective(), true);
        mnuDisplayBuilder.addOnOffDirectiveMenuItem("mnuItUnknownsOff", new DisplayUnknownsDirective(), false);
        mnuDisplayBuilder.endSubMenu();

        mnuSettings.add(mnuDisplayBuilder.getMenu());

        // "Define" submenu
        MenuBuilder mnuDefineBuilder = new MenuBuilder("mnuDefine", _context);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineCharacters", new DefineCharactersDirective(),
                isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineTaxa", new DefineTaxaDirective(), isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineNames", new DefineNamesDirective(), isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineButton", new DefineButtonDirective(), isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineEndIdentify", new DefineEndIdentifyDirective(),
                isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineInformation", new DefineInformationDirective(),
                isDatasetLoaded);
        mnuDefineBuilder.addDirectiveMenuItem("mnuItDefineSubjects", new DefineSubjectsDirective(),
                isDatasetLoaded);

        JMenu mnuDefine = mnuDefineBuilder.getMenu();
        mnuDefine.setEnabled(isDatasetLoaded);
        mnuSettings.add(mnuDefineBuilder.getMenu());

        // "Include" submenu
        MenuBuilder mnuIncludeBuilder = new MenuBuilder("mnuInclude", _context);
        mnuIncludeBuilder.addDirectiveMenuItem("mnuItIncludeCharacters", new IncludeCharactersDirective(),
                isDatasetLoaded);
        mnuIncludeBuilder.addDirectiveMenuItem("mnuItIncludeTaxa", new IncludeTaxaDirective(), isDatasetLoaded);
        JMenu mnuInclude = mnuIncludeBuilder.getMenu();
        mnuInclude.setEnabled(isDatasetLoaded);
        mnuSettings.add(mnuInclude);

        // "Exclude" submenu
        MenuBuilder mnuExcludeBuilder = new MenuBuilder("mnuExclude", _context);
        mnuExcludeBuilder.addDirectiveMenuItem("mnuItExcludeCharacters", new ExcludeCharactersDirective(),
                isDatasetLoaded);
        mnuExcludeBuilder.addDirectiveMenuItem("mnuItExcludeTaxa", new ExcludeTaxaDirective(), isDatasetLoaded);
        JMenu mnuExclude = mnuExcludeBuilder.getMenu();
        mnuExclude.setEnabled(isDatasetLoaded);
        mnuSettings.add(mnuExclude);

        // "Status" submenu
        MenuBuilder mnuStatusBuilder = new MenuBuilder("mnuStatus", _context);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusDisplay", new StatusDisplayDirective(), isDatasetLoaded);

        mnuStatusBuilder.startSubMenu("mnuStatusInclude", true);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusIncludeCharacters",
                new StatusIncludeCharactersDirective(), isDatasetLoaded);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusIncludeTaxa", new StatusIncludeTaxaDirective(),
                isDatasetLoaded);
        mnuStatusBuilder.endSubMenu();

        mnuStatusBuilder.startSubMenu("mnuStatusExclude", true);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusExcludeCharacters",
                new StatusExcludeCharactersDirective(), isDatasetLoaded);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusExcludeTaxa", new StatusExcludeTaxaDirective(),
                isDatasetLoaded);
        mnuStatusBuilder.endSubMenu();

        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusFiles", new StatusFilesDirective(), isDatasetLoaded);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusSet", new StatusSetDirective(), isDatasetLoaded);
        mnuStatusBuilder.addDirectiveMenuItem("mnuItStatusAll", new StatusAllDirective(), isDatasetLoaded);
        mnuSettings.add(mnuStatusBuilder.getMenu());

        return mnuSettings;
    }

    /**
     * Build the re-execute menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the re-execute menu
     */
    private JMenu buildReExecuteMenu(ActionMap actionMap) {
        JMenu mnuReExecute = new JMenu();
        mnuReExecute.setName("mnuReExecute");

        JMenuItem mnuItReExecute = new JMenuItem();
        mnuItReExecute.setAction(actionMap.get("mnuItReExecute"));
        mnuReExecute.add(mnuItReExecute);

        return mnuReExecute;
    }

    /**
     * Build the window menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the window menu
     */
    private JMenu buildWindowMenu(ActionMap actionMap) {
        JMenu mnuWindow = new JMenu();
        mnuWindow.setName("mnuWindow");

        JMenuItem mnuItCascade = new JMenuItem();
        mnuItCascade.setAction(actionMap.get("mnuItCascadeWindows"));
        mnuItCascade.setEnabled(true);
        mnuWindow.add(mnuItCascade);

        JMenuItem mnuItTile = new JMenuItem();
        mnuItTile.setAction(actionMap.get("mnuItTileWindows"));
        mnuItTile.setEnabled(true);
        mnuWindow.add(mnuItTile);

        mnuWindow.addSeparator();

        JMenuItem mnuItCloseAll = new JMenuItem();
        mnuItCloseAll.setAction(actionMap.get("mnuItCloseAllWindows"));
        mnuItCloseAll.setEnabled(true);
        mnuWindow.add(mnuItCloseAll);

        mnuWindow.addSeparator();

        JMenu mnuLF = new JMenu();
        mnuLF.setName("mnuLF");
        mnuWindow.add(mnuLF);

        JMenuItem mnuItMetalLF = new JMenuItem();
        mnuItMetalLF.setAction(actionMap.get("metalLookAndFeel"));
        mnuLF.add(mnuItMetalLF);

        JMenuItem mnuItWindowsLF = new JMenuItem();
        mnuItWindowsLF.setAction(actionMap.get("systemLookAndFeel"));
        mnuLF.add(mnuItWindowsLF);

        try {
            // Nimbus L&F was added in update java 6 update 10.
            Class.forName("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel").newInstance();
            JMenuItem mnuItNimbusLF = new JMenuItem();
            mnuItNimbusLF.setAction(actionMap.get("nimbusLookAndFeel"));
            mnuLF.add(mnuItNimbusLF);
        } catch (Exception e) {
            // The Nimbus L&F is not available, no matter.
        }

        JMenuItem mnuItSetFont = new JMenuItem();
        mnuItSetFont.setAction(actionMap.get("chooseFont"));
        mnuItSetFont.setEnabled(true);
        mnuWindow.add(mnuItSetFont);

        JMenuItem mnuItSetMainWindowSize = new JMenuItem();
        mnuItSetMainWindowSize.setAction(actionMap.get("mnuItSetMainWindowSize"));
        mnuItSetMainWindowSize.setEnabled(true);
        mnuWindow.add(mnuItSetMainWindowSize);

        return mnuWindow;
    }

    /**
     * Build the help menu
     * 
     * @param actionMap
     *            The action map for the main GUI window
     * @return The JMenu for the help menu
     */
    private JMenu buildHelpMenu(boolean advancedMode, ActionMap actionMap) {
        JMenu mnuHelp = new JMenu();
        mnuHelp.setName("mnuHelp");
        JMenuItem mnuItHelpTopics = new JMenuItem();
        mnuItHelpTopics.setName("mnuItHelpTopics");
        mnuItHelpTopics.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                UIUtils.displayHelpTopic(HELP_ID_TOPICS, getMainFrame(), e);
            }
        });
        mnuHelp.add(mnuItHelpTopics);

        if (advancedMode) {
            JMenuItem mnuItHelpCommands = new JMenuItem();
            mnuItHelpCommands.setName("mnuItHelpCommands");
            mnuItHelpCommands.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    UIUtils.displayHelpTopic(HELP_ID_COMMANDS, getMainFrame(), e);
                }

            });
            mnuHelp.add(mnuItHelpCommands);
        }

        if (isMac()) {
            configureMacAboutBox(actionMap.get("mnuItHelpAbout"));
        } else {
            JMenuItem mnuItAbout = new JMenuItem();
            mnuItAbout.setAction(actionMap.get("mnuItHelpAbout"));
            mnuHelp.add(mnuItAbout);
        }

        return mnuHelp;
    }

    // ============== File menu actions ==============================

    /**
     * Edit the dataset index
     */
    @Action
    public void mnuItEditDataSetIndex() {
        EditDatasetIndexDialog dlg = new EditDatasetIndexDialog(getMainFrame(), UIUtils.readDatasetIndex());
        show(dlg);
        List<Pair<String, String>> modifiedDatasetIndex = dlg.getModifiedDatasetIndex();
        if (modifiedDatasetIndex != null) {
            UIUtils.writeDatasetIndex(modifiedDatasetIndex);
        }

        // Rebuild menus as need to refresh the recent datasets menu so that the
        // descriptions of the datasets match the
        // descriptions of the corresponding datasets in the dataset index, if
        // applicable.
        JMenuBar menuBar = buildMenus(_advancedMode);
        getMainFrame().setJMenuBar(menuBar);
        ResourceMap resourceMap = getContext().getResourceMap(Intkey.class);
        resourceMap.injectComponents(getMainFrame());
    }

    /**
     * Switch to normal mode
     */
    @Action
    public void mnuItNormalMode() {
        toggleAdvancedMode();
    }

    /**
     * Switch to advanced mode
     */
    @Action
    public void mnuItAdvancedMode() {
        toggleAdvancedMode();
    }

    /**
     * Toggles between normal and advanced mode
     */
    private void toggleAdvancedMode() {
        _advancedMode = !_advancedMode;

        if (_advancedMode) {
            JMenuBar menuBar = buildMenus(true);
            getMainFrame().setJMenuBar(menuBar);
            _btnSeparate.setVisible(true);
            _btnSetMatch.setVisible(true);
            _txtFldCmdBar.setVisible(true);
            _context.setImageDisplayMode(ImageDisplayMode.MANUAL);
            _context.setDisplayEndIdentify(false);
            btnNaturalOrder();
        } else {
            JMenuBar menuBar = buildMenus(false);
            getMainFrame().setJMenuBar(menuBar);
            _btnSeparate.setVisible(false);
            _btnSetMatch.setVisible(false);
            _txtFldCmdBar.setVisible(false);
            _context.setImageDisplayMode(ImageDisplayMode.AUTO);
            _context.setDisplayEndIdentify(true);
            btnBestOrder();
        }

        // Need to update available characters because character separating
        // powers
        // are only shown in best ordering when in advanced mode.
        if (_context.getDataset() != null) {
            updateAvailableCharacters();
        }

        // Update button toolbar - some buttons are only shown in normal or
        // advanced mode
        updateDynamicButtons();

        ResourceMap resourceMap = getContext().getResourceMap(Intkey.class);
        resourceMap.injectComponents(getMainFrame());
        _rootPanel.revalidate();
    }

    /**
     * Exits the application
     */
    @Action
    public void mnuItExitApplication() {
        exit();
    }

    // ============================ ReExecute menu actions
    // ===========================

    /**
     * Opens the re-execute dialog
     */
    @Action
    public void mnuItReExecute() {
        ReExecuteDialog dlg = new ReExecuteDialog(getMainFrame(), _context.getExecutedDirectives(), _context);
        dlg.setVisible(true);
    }

    // ============================= Window menu actions
    // ==============================

    /**
     * Cascade windows
     */
    @Action
    public void mnuItCascadeWindows() {
        IntKeyDialogController.cascadeWindows();
    }

    /**
     * Tile windows
     */
    @Action
    public void mnuItTileWindows() {
        IntKeyDialogController.tileWindows();
    }

    /**
     * Close all windows
     */
    @Action
    public void mnuItCloseAllWindows() {
        IntKeyDialogController.closeWindows();
    }

    // ====================== Help menu actions
    // ====================================

    /**
     * Displays the about box
     */
    @Action
    public void mnuItHelpAbout() {
        AboutBox aboutBox = new AboutBox(getMainFrame(), IconHelper.createRed32ImageIcon());
        show(aboutBox);
    }

    // ============================== Global option buttons
    // ================================

    /**
     * Set up the GUI to launch contextual help for the next widget that is
     * clicked on by the user
     */
    @Action
    public void btnContextHelp() {
        // Get the HelpOnItemCursor. This is installed by the java help library.
        Cursor onItemCursor = (Cursor) UIManager.get("HelpOnItemCursor");
        getMainFrame().setCursor(onItemCursor);

        // display the (default) glasspane. This is used to intercept mouse
        // events and determine how to handle help
        // for the component that has been clicked on. See the lister defined
        // for the default glasspane in the
        // startup() method
        getMainFrame().getGlassPane().setVisible(true);
    }

    // ========================= Character toolbar button actions
    // ===================

    /**
     * Restart the investigation
     */
    @Action
    public void btnRestart() {
        executeDirective(new RestartDirective(), null);
    }

    /**
     * Switch to best character ordering
     */
    @Action
    public void btnBestOrder() {
        executeDirective(new DisplayCharacterOrderBestDirective(), null);
    }

    /**
     * Switch to separate character ordering for the currently selected taxon
     */
    @Action
    public void btnSeparate() {
        Object[] selectedRemainingTaxa = _listRemainingTaxa.getSelectedValues();
        if (selectedRemainingTaxa.length != 1) {
            displayInformationMessage(separateInformationMessage);
            return;
        }

        Item selectedTaxon = (Item) selectedRemainingTaxa[0];

        executeDirective(new DisplayCharacterOrderSeparateDirective(),
                Integer.toString(selectedTaxon.getItemNumber()));
    }

    /**
     * Switch to natural character ordering
     */
    @Action
    public void btnNaturalOrder() {
        executeDirective(new DisplayCharacterOrderNaturalDirective(), null);
    }

    /**
     * Display a differences report between the current specimen and the
     * remaining taxa
     */
    @Action
    public void btnDiffSpecimenTaxa() {
        executeDirective(new DifferencesDirective(), "/E (specimen remaining) all");
    }

    /**
     * Set the tolerance
     */
    @Action
    public void btnSetTolerance() {
        executeDirective(new SetToleranceDirective(), null);
    }

    /**
     * Set the match settings
     */
    @Action
    public void btnSetMatch() {
        executeDirective(new SetMatchDirective(), null);
    }

    /**
     * Set the included characters
     */
    @Action
    public void btnSubsetCharacters() {
        executeDirective(new IncludeCharactersDirective(), null);
    }

    /**
     * Launches the find characters dialog
     */
    @Action
    public void btnFindCharacter() {
        new FindInCharactersDialog(this, _context).setVisible(true);
    }

    // ============================= Taxon toolbar button actions
    // ===========================

    /**
     * Launches the taxon information dialog for the currently selected taxa
     */
    @Action
    public void btnTaxonInfo() {
        displayInfoForSelectedTaxa();
    }

    /**
     * Launches the taxon information dialog for the currently selected taxa
     */
    private void displayInfoForSelectedTaxa() {
        List<Item> selectedTaxa = new ArrayList<Item>();

        for (int i : _listRemainingTaxa.getSelectedIndices()) {
            selectedTaxa.add((Item) _availableTaxaListModel.getElementAt(i));
        }

        for (int i : _listEliminatedTaxa.getSelectedIndices()) {
            selectedTaxa.add((Item) _eliminatedTaxaListModel.getElementAt(i));
        }

        // If no taxa were selected, show the information for all available taxa
        if (selectedTaxa.isEmpty()) {
            selectedTaxa.addAll(_context.getAvailableTaxa());
        }

        TaxonInformationDialog dlg = new TaxonInformationDialog(getMainFrame(), selectedTaxa, _context,
                _context.getImageDisplayMode() != ImageDisplayMode.OFF);
        show(dlg);
    }

    /**
     * Display a differences report for two or more selected taxa
     */
    @Action
    public void btnDiffTaxa() {
        List<Item> selectedTaxa = new ArrayList<Item>();

        for (int i : _listRemainingTaxa.getSelectedIndices()) {
            selectedTaxa.add((Item) _availableTaxaListModel.getElementAt(i));
        }

        for (int i : _listEliminatedTaxa.getSelectedIndices()) {
            selectedTaxa.add((Item) _eliminatedTaxaListModel.getElementAt(i));
        }

        // Ensure that at least two taxa are selected
        if (selectedTaxa.size() >= 2) {
            StringBuilder directiveTextBuilder = new StringBuilder();
            directiveTextBuilder.append("/E /I /U /X (");
            for (Item taxon : selectedTaxa) {
                directiveTextBuilder.append(" ");
                directiveTextBuilder.append(taxon.getItemNumber());
            }
            directiveTextBuilder.append(") all");

            executeDirective(new DifferencesDirective(), directiveTextBuilder.toString());
        } else {
            displayInformationMessage(UIUtils.getResourceString("SelectTwoOrMoreTaxa.caption"));
        }

    }

    /**
     * Set the included taxa
     */
    @Action
    public void btnSubsetTaxa() {
        executeDirective(new IncludeTaxaDirective(), null);
    }

    /**
     * Launches the find taxa dialog
     */
    @Action
    public void btnFindTaxon() {
        new FindInTaxaDialog(this).setVisible(true);
    }

    // =========================================================================================

    /**
     * Load the Desktop in the background. We do this because
     * Desktop.getDesktop() can be very slow
     */
    private void loadDesktopInBackground() {
        _desktopWorker = new SwingWorker<Desktop, Void>() {

            protected Desktop doInBackground() {
                if (Desktop.isDesktopSupported()) {
                    return Desktop.getDesktop();
                } else {
                    return null;
                }
            }
        };
        _desktopWorker.execute();
    }

    /**
     * Execute a directive
     * 
     * @param dir
     *            the directive to execute
     * @param data
     *            the data (arguments) for the directive
     */
    private void executeDirective(AbstractDirective<IntkeyContext> dir, String data) {
        try {
            dir.parseAndProcess(_context, data);
        } catch (Exception ex) {
            Logger.error(ex);
            String msg;
            if (ex instanceof IntkeyDirectiveParseException) {
                msg = ex.getMessage();
            } else {
                msg = UIUtils.getResourceString("ErrorWhileProcessingCommand.error",
                        StringUtils.join(dir.getControlWords()).toUpperCase(), ex.getMessage());
            }
            displayErrorMessage(msg);
            Logger.error(msg);
        }
    }

    /**
     * Called to initialize a new identification
     */
    private void initializeIdentification() {
        handleUpdateAll();
    }

    /**
     * Update the view of available characters
     */
    private void updateAvailableCharacters() {

        IntkeyCharacterOrder charOrder = _context.getCharacterOrder();

        Item taxonToSeparate = null;
        String formattedTaxonToSeparateName = null;

        switch (charOrder) {
        case SEPARATE:
            taxonToSeparate = _context.getDataset().getItem(_context.getTaxonToSeparate());
            formattedTaxonToSeparateName = _taxonformatter.formatItemDescription(taxonToSeparate);
            if (!_context.getAvailableTaxa().contains(taxonToSeparate)) {
                _listAvailableCharacters.setModel(new DefaultListModel());
                _lblNumAvailableCharacters
                        .setText(MessageFormat.format(separateCharactersCaption, formattedTaxonToSeparateName, 0));
                break;
            }

            // If taxon to separate has not been eliminated, drop through and
            // display the best characters for taxon separation
        case BEST:
            LinkedHashMap<Character, Double> bestCharactersMap = _context.getBestOrSeparateCharacters();
            if (bestCharactersMap != null) {
                if (charOrder == IntkeyCharacterOrder.BEST) {
                    _lblNumAvailableCharacters.setText(
                            MessageFormat.format(bestCharactersCaption, bestCharactersMap.keySet().size()));
                } else {
                    _lblNumAvailableCharacters.setText(MessageFormat.format(separateCharactersCaption,
                            formattedTaxonToSeparateName, bestCharactersMap.keySet().size()));
                }
                if (bestCharactersMap.isEmpty()) {
                    handleNoAvailableCharacters();
                    return;
                } else {
                    _availableCharacterListModel = new DefaultListModel();
                    for (Character ch : bestCharactersMap.keySet()) {
                        _availableCharacterListModel.addElement(ch);
                    }
                    _availableCharacterListModel.copyInto(bestCharactersMap.keySet().toArray());

                    // Only display character separating powers if in advanced
                    // mode.
                    if (_advancedMode) {
                        _availableCharactersListCellRenderer = new BestCharacterCellRenderer(bestCharactersMap,
                                _context.displayNumbering());
                    } else {
                        _availableCharactersListCellRenderer = new CharacterCellRenderer(
                                _context.displayNumbering());
                    }
                    _listAvailableCharacters.setCellRenderer(_availableCharactersListCellRenderer);
                    _listAvailableCharacters.setModel(_availableCharacterListModel);
                }
            } else {
                _availableCharacterListModel = null;

                // The best characters list is not cached and needs to be
                // calculated. This is a
                // long-running operation so use a
                // SwingWorker to do it on a different thread, and update
                // the
                // available characters list when
                // it is complete.
                GetBestCharactersWorker worker = new GetBestCharactersWorker(_context);
                worker.execute();

                // Show the busy glass pane with a message if worker has not
                // completed within
                // 250 milliseconds. This avoids "flickering" of the
                // glasspane
                // when it takes a
                // very short time to calculate the best characters.
                try {
                    Thread.sleep(250);
                    if (!worker.isDone()) {
                        displayBusyMessage(calculatingBestCaption);
                    }
                } catch (InterruptedException ex) {
                    // do nothing
                }

                return;
            }

            break;
        case NATURAL:
            int lastSelectedIndex = _listAvailableCharacters.getSelectedIndex();

            List<Character> availableCharacters = new ArrayList<Character>(_context.getAvailableCharacters());
            _lblNumAvailableCharacters
                    .setText(MessageFormat.format(availableCharactersCaption, availableCharacters.size()));
            if (availableCharacters.size() == 0) {
                handleNoAvailableCharacters();
                return;
            } else {
                _availableCharacterListModel = new DefaultListModel();
                for (Character ch : availableCharacters) {
                    _availableCharacterListModel.addElement(ch);
                }
                _availableCharactersListCellRenderer = new CharacterCellRenderer(_context.displayNumbering());
                _listAvailableCharacters.setCellRenderer(_availableCharactersListCellRenderer);
                _listAvailableCharacters.setModel(_availableCharacterListModel);

                // Select the same index that was previously selected. This will
                // have the effect of selecting the character after the
                // previously used character.
                _listAvailableCharacters.setSelectedIndex(lastSelectedIndex);
            }
            break;
        default:
            throw new RuntimeException("Unrecognized character order");
        }

        // The viewport of the available characters scroll pane may be
        // displaying a
        // message due to an investigation finishing, or no characters being
        // available
        // previously. Ensure that the available characters list is now
        // displayed again.
        if (!_sclPaneAvailableCharacters.getViewport().getView().equals(_listAvailableCharacters)) {
            _sclPaneAvailableCharacters.setViewportView(_listAvailableCharacters);
            _sclPaneAvailableCharacters.revalidate();
        }
    }

    /**
     * Called to handle the case that no characters are available
     */
    private void handleNoAvailableCharacters() {
        String message = null;

        if (_context.getIncludedCharacters().size() < _context.getDataset().getNumberOfCharacters()) { // characters
            message = charactersExcludedCannotSeparateCaption;
        } else {
            if (_context.getTolerance() > 0) {
                message = mismatchesAllowCannotSeparateCaption;
            } else {
                message = availableCharactersCannotSeparateCaption;
            }
        }

        MessagePanel messagePanel = new MessagePanel(message, HELP_ID_NO_CHARACTERS_REMAINING);
        _sclPaneAvailableCharacters.setViewportView(messagePanel);
        _sclPaneAvailableCharacters.revalidate();
    }

    /**
     * Used to calculate the best characters in a separate thread, then update
     * the UI accordingly when the operation is finished
     * 
     * @author ChrisF
     * 
     */
    private class GetBestCharactersWorker extends SwingWorker<Void, Void> {

        private IntkeyContext _context;

        public GetBestCharactersWorker(IntkeyContext context) {
            super();
            _context = context;
        }

        @Override
        protected Void doInBackground() throws Exception {
            _context.calculateBestOrSeparateCharacters();
            return null;
        }

        @Override
        protected void done() {
            updateAvailableCharacters();
            removeBusyMessage();

            // select the first character every time best (or separate)
            // characters are selected.
            _listAvailableCharacters.setSelectedIndex(0);
        }
    }

    /**
     * Update the view of used characters
     */
    private void updateUsedCharacters() {

        Specimen specimen = _context.getSpecimen();
        List<Character> usedCharacters = specimen.getUsedCharacters();

        List<Attribute> usedCharacterValues = new ArrayList<Attribute>();
        for (Character ch : usedCharacters) {
            usedCharacterValues.add(specimen.getAttributeForCharacter(ch));
        }

        _usedCharacterListModel = new DefaultListModel();
        for (Attribute attr : usedCharacterValues) {
            _usedCharacterListModel.addElement(attr);
        }
        _usedCharactersListCellRenderer = new AttributeCellRenderer(_context.displayNumbering(),
                _context.getDataset().getOrWord());
        _listUsedCharacters.setCellRenderer(_usedCharactersListCellRenderer);
        _listUsedCharacters.setModel(_usedCharacterListModel);

        _lblNumUsedCharacters
                .setText(MessageFormat.format(usedCharactersCaption, _usedCharacterListModel.getSize()));
    }

    /**
     * Update the view of available taxa
     * 
     * @param availableTaxa
     *            the available taxa
     * @param taxaDifferingCharacters
     *            The differing characters for each taxa. Used when the
     *            tolerance is greater than zero to display a count of differing
     *            characters against each taxon
     */
    private void updateAvailableTaxa(List<Item> availableTaxa, Map<Item, Set<Character>> taxaDifferingCharacters) {
        _availableTaxaListModel = new DefaultListModel();

        if (_context.getTolerance() > 0 && taxaDifferingCharacters != null) {
            // sort available taxa by difference count
            Collections.sort(availableTaxa, new DifferenceCountComparator(taxaDifferingCharacters));
            _availableTaxaCellRenderer = new TaxonWithDifferenceCountCellRenderer(taxaDifferingCharacters,
                    _context.displayNumbering(), _context.displayComments());
        } else {
            _availableTaxaCellRenderer = new TaxonCellRenderer(_context.displayNumbering(),
                    _context.displayComments());
        }

        for (Item taxon : availableTaxa) {
            _availableTaxaListModel.addElement(taxon);
        }

        _listRemainingTaxa.setCellRenderer(_availableTaxaCellRenderer);
        _listRemainingTaxa.setModel(_availableTaxaListModel);

        _lblNumRemainingTaxa.setText(MessageFormat.format(remainingTaxaCaption, _availableTaxaListModel.getSize()));

        _listRemainingTaxa.repaint();
    }

    /**
     * Update the view of available taxa
     * 
     * @param availableTaxa
     *            the available taxa
     * @param taxaDifferingCharacters
     *            The differing characters for each taxa. Used when the
     *            tolerance is greater than zero to display a count of differing
     *            characters against each taxon
     */
    private void updateEliminatedTaxa(List<Item> eliminatedTaxa,
            Map<Item, Set<Character>> taxaDifferingCharacters) {
        // sort eliminated taxa by difference count
        Collections.sort(eliminatedTaxa, new DifferenceCountComparator(taxaDifferingCharacters));

        _eliminatedTaxaListModel = new DefaultListModel();

        for (Item taxon : eliminatedTaxa) {
            _eliminatedTaxaListModel.addElement(taxon);
        }

        _eliminatedTaxaCellRenderer = new TaxonWithDifferenceCountCellRenderer(taxaDifferingCharacters,
                _context.displayNumbering(), _context.displayComments());

        _listEliminatedTaxa.setCellRenderer(_eliminatedTaxaCellRenderer);
        _listEliminatedTaxa.setModel(_eliminatedTaxaListModel);

        _lblEliminatedTaxa.setText(MessageFormat.format(eliminatedTaxaCaption, _eliminatedTaxaListModel.getSize()));

        _listEliminatedTaxa.repaint();
    }

    // ================================== IntkeyUI methods
    // ===========================================================

    @Override
    public void handleNewDataset(IntkeyDataset dataset) {
        _lastOpenedDatasetDirectory = _context.getDatasetStartupFile().getParentFile();

        getMainFrame()
                .setTitle(MessageFormat.format(windowTitleWithDatasetTitle, dataset.getHeadingWithoutFormatting()));

        // enable toolbar buttons
        IntkeyCharacterOrder characterOrder = _context.getCharacterOrder();
        _btnRestart.setEnabled(true);
        _btnBestOrder.setEnabled(characterOrder != IntkeyCharacterOrder.BEST);
        _btnSeparate.setEnabled(true);
        _btnNaturalOrder.setEnabled(characterOrder != IntkeyCharacterOrder.NATURAL);
        _btnSetTolerance.setEnabled(true);
        _btnSetMatch.setEnabled(true);
        _btnSubsetCharacters.setEnabled(true);
        _btnFindCharacter.setEnabled(true);

        _btnTaxonInfo.setEnabled(true);
        _btnDiffTaxa.setEnabled(true);
        _btnSubsetTaxa.setEnabled(true);
        _btnFindTaxon.setEnabled(true);

        // display startup images
        if (!_suppressStartupImages) {
            List<Image> startupImages = dataset.getStartupImages();
            if (!startupImages.isEmpty()) {
                ImageUtils.displayStartupScreen(startupImages, _context.getImageSettings(), getMainFrame());
            }
        }

        // Need to refresh the menus as some menus/menu items are disabled when
        // the dataset is not loaded. Also the
        // list of recent datasets may need to be refreshed after the closing of
        // the previous dataset (if applicable)
        JMenuBar menuBar = buildMenus(_advancedMode);
        getMainFrame().setJMenuBar(menuBar);
        ResourceMap resourceMap = getContext().getResourceMap(Intkey.class);
        resourceMap.injectComponents(getMainFrame());

        initializeIdentification();

        _rootPanel.revalidate();
    }

    @Override
    public void handleDatasetClosed() {
        if (_context.getDataset() != null) {
            saveCurrentlyOpenedDataset();
        }
    }

    @Override
    public void handleUpdateAll() {
        if (_context.getDataset() != null) { // Only update if we have a dataset
                                             // loaded.
            List<Item> availableTaxa = _context.getAvailableTaxa();
            List<Item> eliminatedTaxa = _context.getEliminatedTaxa();

            _btnDiffSpecimenTaxa.setEnabled(availableTaxa.size() > 0 && eliminatedTaxa.size() > 0);

            // Disable button for selected best or natural order.
            _btnNaturalOrder.setEnabled(true);
            _btnBestOrder.setEnabled(true);
            switch (_context.getCharacterOrder()) {
            case NATURAL:
                _btnNaturalOrder.setEnabled(false);
                break;
            case BEST:
                _btnBestOrder.setEnabled(false);
                break;
            case SEPARATE:
                // do nothing
                break;
            default:
                throw new RuntimeException("Unrecognized character order");
            }

            // Need to display a message in place of the list of available
            // characters
            // if there are no remaining taxa (no matching taxa remain), or only
            // 1
            // remaining taxon (identification complete)
            if (availableTaxa.size() > 1) {
                updateAvailableCharacters();
            } else {
                JPanel messagePanel = null;

                if (availableTaxa.size() == 0) {
                    messagePanel = new AllowMismatchMessagePanel(noMatchingTaxaRemainCaption,
                            HELP_ID_NO_MATCHING_TAXA_REMAIN, _context);
                } else {
                    // 1 available taxon
                    messagePanel = new MessagePanel(identificationCompleteCaption, HELP_ID_IDENTIFICATION_COMPLETE);
                }

                _sclPaneAvailableCharacters.setViewportView(messagePanel);
                _sclPaneAvailableCharacters.revalidate();

                switch (_context.getCharacterOrder()) {
                case NATURAL:
                    _lblNumAvailableCharacters.setText(MessageFormat.format(availableCharactersCaption, 0));
                    break;
                case BEST:
                    _lblNumAvailableCharacters.setText(MessageFormat.format(bestCharactersCaption, 0));
                    break;
                case SEPARATE:
                    Item taxonToSeparate = _context.getDataset().getItem(_context.getTaxonToSeparate());
                    String formattedTaxonName = _taxonformatter.formatItemDescription(taxonToSeparate);
                    _lblNumAvailableCharacters
                            .setText(MessageFormat.format(separateCharactersCaption, formattedTaxonName, 0));
                    break;
                default:
                    throw new RuntimeException("Unrecognized character order");
                }
            }

            updateUsedCharacters();
            updateAvailableTaxa(availableTaxa, _context.getSpecimen().getTaxonDifferences());
            updateEliminatedTaxa(eliminatedTaxa, _context.getSpecimen().getTaxonDifferences());

            updateDynamicButtons();
        }
    }

    @Override
    public void handleIdentificationRestarted() {
        _btnDiffSpecimenTaxa.setEnabled(false);
        handleUpdateAll();
        if (_context.isDemonstrationMode()) {
            IntKeyDialogController.closeWindows();
        }
    }

    @Override
    public void displayRTFReportFromFile(File rtfFile, String title) {
        long mbInBytes = 1024 * 1024;

        long fileSizeInMB = rtfFile.length() / mbInBytes;

        // give the user the option of saving the file instead if the file is
        // 5MB or more in size
        if (fileSizeInMB >= 5) {
            boolean saveFile = promptForYesNoOption(MessageFormat.format(saveReportToFilePrompt, fileSizeInMB));
            if (saveFile) {
                List<String> fileExtensions = new ArrayList<String>();
                fileExtensions.add("rtf");
                try {
                    File destinationFile = promptForFile(fileExtensions, null,
                            UIUtils.getResourceString("RtfReportDisplayDialog.fileFilterDescription"), true);
                    if (destinationFile == null) {
                        // user hit cancel.
                        return;
                    }
                    FileUtils.copyFile(rtfFile, destinationFile);
                } catch (IOException ex) {
                    displayErrorMessage(errorWritingToFileError);
                }
                return;
            }
        }

        // If the file has not been saved, display its contents.
        try {
            String rtfSource = FileUtils.readFileToString(rtfFile);
            displayRTFReport(rtfSource, title);
        } catch (OutOfMemoryError err) {
            displayErrorMessage(rtfFileTooLargeError);
        } catch (Exception ex) {
            displayErrorMessage(errorWritingToFileError);
        }
    }

    @Override
    public void displayRTFReport(String rtfSource, String title) {
        DisplayRTFWorker worker = new DisplayRTFWorker(rtfSource, title);
        worker.execute();

        // Show the busy glass pane with a message if worker has not
        // completed within
        // 250 milliseconds. This avoids "flickering" of the glasspane
        // when it takes a
        // very short time to display the RTF report
        try {
            Thread.sleep(250);
            if (!worker.isDone()) {
                displayBusyMessageAllowCancelWorker(displayingReportCaption, worker);
            }
        } catch (InterruptedException ex) {
            // do nothing
        }
    }

    private class DisplayRTFWorker extends SwingWorker<Void, Void> {

        private String _rtfSource;
        private String _title;
        private RtfReportDisplayDialog _dlg;

        public DisplayRTFWorker(String rtfSource, String title) {
            _rtfSource = rtfSource;
            _title = title;
        }

        @Override
        public Void doInBackground() {
            _dlg = new RtfReportDisplayDialog(getMainFrame(), new SimpleRtfEditorKit(null), _rtfSource, _title);
            return null;
        }

        @Override
        protected void done() {
            try {
                get();
                Intkey.this.show(_dlg);
            } catch (CancellationException ex) {
                // display of RTF content was cancelled - no action required.
            } catch (Exception ex) {
                // A runtime exception is thrown by the RtfReportDisplayDialog
                // if
                // the RTF was invalid. This will result in the dialog being
                // null.
                displayErrorMessage(badlyFormedRTFContentMessage);
            } finally {
                removeBusyMessage();
            }
        }

    }

    @Override
    public void displayErrorMessage(String message) {
        JOptionPane.showMessageDialog(getMainFrame(), message, errorDlgTitle, JOptionPane.ERROR_MESSAGE);
    }

    @Override
    public void displayInformationMessage(String message) {
        JOptionPane.showMessageDialog(getMainFrame(), message, informationDlgTitle,
                JOptionPane.INFORMATION_MESSAGE);
    }

    @Override
    public void displayBusyMessage(String message) {
        if (_busyGlassPane == null) {
            _busyGlassPane = new BusyGlassPane(message);
            getMainFrame().setGlassPane(_busyGlassPane);
            _busyGlassPane.setVisible(true);
            getMainFrame().validate();
            _busyGlassPane.paintImmediately(getMainFrame().getBounds());
        } else {
            _busyGlassPane.setMessage(message);
            _busyGlassPane.paintImmediately(getMainFrame().getBounds());
        }
    }

    @Override
    public void displayBusyMessageAllowCancelWorker(String message, SwingWorker<?, ?> worker) {
        displayBusyMessage(message);
        _busyGlassPane.setWorkerForCancellation(worker);
    }

    @Override
    public void removeBusyMessage() {
        if (_busyGlassPane != null) {
            _busyGlassPane.setVisible(false);
            _busyGlassPane = null;
            getMainFrame().setGlassPane(_defaultGlassPane);
        }
    }

    @Override
    public void displayTaxonInformation(List<Item> taxa, String imagesAutoDisplayText,
            String otherItemsAutoDisplayText, boolean closePromptAfterAutoDisplay) {
        TaxonInformationDialog dlg = new TaxonInformationDialog(getMainFrame(), taxa, _context,
                _context.getImageDisplayMode() != ImageDisplayMode.OFF);

        if (imagesAutoDisplayText != null) {
            dlg.displayImagesWithTextInSubject(imagesAutoDisplayText);
        }

        if (otherItemsAutoDisplayText != null) {
            dlg.displayOtherItemsWithTextInDescription(otherItemsAutoDisplayText);
        }

        // Don't bother showing the information dialog if it is just going to be
        // closed again straight away.
        if (!closePromptAfterAutoDisplay || (imagesAutoDisplayText == null && otherItemsAutoDisplayText == null)) {
            show(dlg);
        }
    }

    @Override
    public void addToolbarButton(boolean advancedModeOnly, boolean normalModeOnly,
            boolean inactiveUnlessUsedCharacters, String imageFileName, List<String> commands, String shortHelp,
            String fullHelp) {
        Icon icon = null;

        // Is the image file an absolute file?
        File iconFile = new File(imageFileName);
        if (iconFile.exists() && iconFile.isAbsolute()) {
            try {
                icon = readImageIconFromFile(iconFile);
            } catch (IOException ex) {
                displayErrorMessage(UIUtils.getResourceString("ErrorReadingIconImageFromFile.error",
                        iconFile.getAbsolutePath()));
            }
        }

        // Is the image file relative to the dataset directory?
        if (icon == null) {
            File relativeIconFile = new File(_context.getDatasetDirectory(), imageFileName);
            if (relativeIconFile.exists() && relativeIconFile.isAbsolute()) {
                try {
                    icon = readImageIconFromFile(relativeIconFile);
                } catch (IOException ex) {
                    displayErrorMessage(UIUtils.getResourceString("ErrorReadingIconImageFromFile.error",
                            iconFile.getAbsolutePath()));
                }
            }
        }

        // try getting an icon with the exact image name from the icon resources
        if (icon == null) {
            try {
                icon = IconHelper.createImageIconFromAbsolutePath(INTKEY_ICON_PATH + "/" + imageFileName);
            } catch (Exception ex) {
                // do nothing
            }
        }

        if (icon == null && imageFileName.toLowerCase().endsWith(".bmp")) {
            // try substituting ".bmp" for ".png" and reading from the icon
            // resources. All the default
            // icons that come with Intkey have been convert to png format.
            String modifiedImageFileName = imageFileName.replaceFirst(".bmp$", ".png");

            try {
                icon = IconHelper.createImageIconFromAbsolutePath(INTKEY_ICON_PATH + "/" + modifiedImageFileName);
            } catch (Exception ex) {
                // do nothing
            }
        }

        if (icon == null) {
            displayErrorMessage(UIUtils.getResourceString("CouldNotFromImage.error", imageFileName));
            return;
        }

        JButton button = new JButton(icon);
        button.setToolTipText(shortHelp);
        button.setMargin(new Insets(0, 0, 0, 0));
        _pnlDynamicButtons.add(button);

        final List<String> commandsCopy = new ArrayList<String>(commands);

        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                for (String command : commandsCopy) {
                    _context.parseAndExecuteDirective(command);
                }

            }
        });

        if (advancedModeOnly && !normalModeOnly) {
            _advancedModeOnlyDynamicButtons.add(button);
        }

        if (normalModeOnly && !advancedModeOnly) {
            _normalModeOnlyDynamicButtons.add(button);
        }

        if (inactiveUnlessUsedCharacters) {
            _activeOnlyWhenCharactersUsedButtons.add(button);
        }

        _dynamicButtonsFullHelp.put(button, fullHelp);

        updateDynamicButtons();
    }

    private void updateDynamicButtons() {
        for (JButton b : _advancedModeOnlyDynamicButtons) {
            b.setVisible(_advancedMode);
        }

        for (JButton b : _normalModeOnlyDynamicButtons) {
            b.setVisible(!_advancedMode);
        }

        for (JButton b : _activeOnlyWhenCharactersUsedButtons) {
            if (_usedCharacterListModel != null) {
                b.setEnabled(_usedCharacterListModel.size() > 0);
            } else {
                b.setEnabled(false);
            }
        }

        _rootPanel.revalidate();
    }

    private ImageIcon readImageIconFromFile(File iconFile) throws IOException {
        BufferedImage img = ImageIO.read(iconFile);
        ImageIcon imgIcon = new ImageIcon(img);
        return imgIcon;
    }

    @Override
    public void addToolbarSpace() {
        JPanel spacerPanel = new JPanel();
        spacerPanel.setMinimumSize(new Dimension(20, 1));
        _pnlDynamicButtons.add(spacerPanel);
        _rootPanel.revalidate();
    }

    @Override
    public void clearToolbar() {
        _advancedModeOnlyDynamicButtons.clear();
        _normalModeOnlyDynamicButtons.clear();
        _activeOnlyWhenCharactersUsedButtons.clear();
        _pnlDynamicButtons.removeAll();
        _rootPanel.revalidate();
    }

    @Override
    public void illustrateCharacters(List<Character> characters) {
        if (_context.getImageDisplayMode() == ImageDisplayMode.OFF) {
            displayErrorMessage(UIUtils.getResourceString("ImageDisplayDisabled.error"));
        } else {
            try {
                CharacterImageDialog dlg = new CharacterImageDialog(getMainFrame(), characters, null,
                        _context.getImageSettings(), false, false, _context.displayScaled());
                dlg.displayImagesForCharacter(characters.get(0));
                show(dlg);

            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
            }
        }
    }

    @Override
    public void illustrateTaxa(List<Item> taxa) {
        if (_context.getImageDisplayMode() == ImageDisplayMode.OFF) {
            displayErrorMessage(UIUtils.getResourceString("ImageDisplayDisabled.error"));
        } else {
            try {
                TaxonImageDialog dlg = new TaxonImageDialog(getMainFrame(), _context.getImageSettings(), taxa,
                        false, !_context.displayContinuous(), _context.displayScaled(), _context.getImageSubjects(),
                        this);
                dlg.displayImagesForTaxon(taxa.get(0), 0);
                show(dlg);
            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
            }
        }
    }

    @Override
    public void displayContents(LinkedHashMap<String, String> contentsMap) {
        final ContentsDialog dlg = new ContentsDialog(getMainFrame(), contentsMap, _context);
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                show(dlg);
            }
        });
    }

    @Override
    public void displayFile(URL fileURL, String description) {
        try {
            UIUtils.displayFileFromURL(fileURL, description, _desktopWorker.get());
        } catch (UnsupportedOperationException ex) {
            Logger.error(ex);
            promptForString(UIUtils.getResourceString("CouldNotDisplayFileDesktopError.error", fileURL.toString()),
                    fileURL.toString(), "");
        } catch (Exception ex) {
            Logger.error(ex);
            displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayFile.error", fileURL.toString()));
        }
    }

    @Override
    public boolean isLogVisible() {
        return _logDialog.isVisible();
    }

    @Override
    public void setLogVisible(boolean visible) {
        if (visible) {
            show(_logDialog);
        } else {
            _logDialog.setVisible(false);
        }
    }

    @Override
    public void updateLog() {
        List<String> logEntries = _context.getLogEntries();
        RTFBuilder builder = new RTFBuilder();
        builder.startDocument();
        builder.setFont(1);
        for (String line : logEntries) {
            // directive calls are identified by a leading asterisk. They should
            // be colored blue.
            // All other lines should be colored red.
            if (line.trim().startsWith("*")) {
                builder.setTextColor(Color.BLUE);
            } else {
                builder.setTextColor(Color.RED);
            }
            String escapedLine = RTFUtils.escapeRTF(line);
            builder.appendText(escapedLine);
        }
        builder.endDocument();

        _logDialog.setContent(builder.toString());
    }

    @Override
    public void quitApplication() {
        exit();
    }

    @Override
    public List<Item> getSelectedTaxa() {
        List<Item> retList = new ArrayList<Item>();

        for (Object oTaxon : _listRemainingTaxa.getSelectedValues()) {
            retList.add((Item) oTaxon);
        }

        for (Object oTaxon : _listEliminatedTaxa.getSelectedValues()) {
            retList.add((Item) oTaxon);
        }

        return retList;
    }

    @Override
    public List<Character> getSelectedCharacters() {
        List<Character> retList = new ArrayList<Character>();

        for (Object oCh : _listAvailableCharacters.getSelectedValues()) {
            retList.add((Character) oCh);
        }

        for (Object oAttr : _listUsedCharacters.getSelectedValues()) {
            retList.add(((Attribute) oAttr).getCharacter());
        }

        return retList;
    }

    @Override
    public void setDemonstrationMode(boolean demonstrationMode) {
        if (demonstrationMode) {
            // If in advanced mode, switch to basic mode
            if (_advancedMode) {
                toggleAdvancedMode();
            }
        }

        getMainFrame().getJMenuBar().setVisible(!demonstrationMode);
    }

    @Override
    public void displayHelpTopic(String topicID) {
        _helpController.displayHelpTopic(getMainFrame(), topicID);
    }

    @Override
    public boolean isAdvancedMode() {
        return _advancedMode;
    }

    // ================================== DirectivePopulator methods
    // ===================================================================

    @Override
    public List<Character> promptForCharactersByKeyword(String directiveName,
            boolean permitSelectionFromIncludedCharactersOnly, boolean noneKeywordAvailable,
            List<String> returnSelectedKeywords) {
        List<Image> characterKeywordImages = _context.getDataset().getCharacterKeywordImages();
        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && characterKeywordImages != null
                && !characterKeywordImages.isEmpty()) {
            ImageDialog dlg = new ImageDialog(getMainFrame(), _context.getImageSettings(), true,
                    _context.displayScaled());
            dlg.setImages(characterKeywordImages);
            dlg.showImage(0);
            dlg.setTitle(MessageFormat.format(selectCharacterKeywordsCaption, directiveName));

            show(dlg);

            if (!dlg.okButtonPressed()) {
                // user cancelled
                return null;
            }

            Set<String> keywords = dlg.getSelectedKeywords();

            if (!noneKeywordAvailable) {
                keywords.remove(IntkeyContext.CHARACTER_KEYWORD_NONE);
            }

            List<Character> selectedCharacters = new ArrayList<Character>();

            for (String keyword : keywords) {
                selectedCharacters.addAll(_context.getCharactersForKeyword(keyword));
                returnSelectedKeywords.add(keyword);
            }

            if (permitSelectionFromIncludedCharactersOnly) {
                selectedCharacters.retainAll(_context.getIncludedCharacters());
            }

            return selectedCharacters;
        } else {
            CharacterKeywordSelectionDialog dlg = new CharacterKeywordSelectionDialog(getMainFrame(), _context,
                    directiveName.toUpperCase(), permitSelectionFromIncludedCharactersOnly);
            show(dlg);
            returnSelectedKeywords.addAll(dlg.getSelectedKeywords());
            return dlg.getSelectedCharacters();
        }
    }

    @Override
    public List<Character> promptForCharactersByList(String directiveName,
            boolean selectFromAvailableCharactersOnly, List<String> returnSelectedKeywords) {
        List<Character> charactersToSelect;

        String keyword = null;
        if (selectFromAvailableCharactersOnly) {
            charactersToSelect = _context.getCharactersForKeyword(IntkeyContext.CHARACTER_KEYWORD_AVAILABLE);
            keyword = IntkeyContext.CHARACTER_KEYWORD_AVAILABLE;
        } else {
            charactersToSelect = _context.getCharactersForKeyword(IntkeyContext.CHARACTER_KEYWORD_ALL);
            keyword = IntkeyContext.CHARACTER_KEYWORD_ALL;

        }
        CharacterSelectionDialog dlg = new CharacterSelectionDialog(getMainFrame(), charactersToSelect,
                directiveName.toUpperCase(), keyword, _context.getImageSettings(), _context.displayNumbering(),
                _context);
        show(dlg);
        returnSelectedKeywords.addAll(dlg.getSelectedKeywords());
        return dlg.getSelectedCharacters();
    }

    @Override
    public List<Item> promptForTaxaByKeyword(String directiveName, boolean permitSelectionFromIncludedTaxaOnly,
            boolean noneKeywordAvailable, boolean includeSpecimenAsOption, MutableBoolean returnSpecimenSelected,
            List<String> returnSelectedKeywords) {

        List<Image> taxonKeywordImages = _context.getDataset().getTaxonKeywordImages();
        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && taxonKeywordImages != null
                && !taxonKeywordImages.isEmpty()) {
            ImageDialog dlg = new ImageDialog(getMainFrame(), _context.getImageSettings(), true,
                    _context.displayScaled());
            dlg.setImages(taxonKeywordImages);
            dlg.setTitle(MessageFormat.format(selectTaxonKeywordsCaption, directiveName));
            dlg.showImage(0);
            show(dlg);

            if (!dlg.okButtonPressed()) {
                // user cancelled
                return null;
            }

            Set<String> keywords = dlg.getSelectedKeywords();

            if (!noneKeywordAvailable) {
                keywords.remove(IntkeyContext.TAXON_KEYWORD_NONE);
            }

            List<Item> selectedTaxa = new ArrayList<Item>();

            for (String keyword : keywords) {
                selectedTaxa.addAll(_context.getTaxaForKeyword(keyword));
                returnSelectedKeywords.add(keyword);
            }

            if (permitSelectionFromIncludedTaxaOnly) {
                selectedTaxa.retainAll(_context.getIncludedTaxa());
            }

            return selectedTaxa;
        } else {
            TaxonKeywordSelectionDialog dlg = new TaxonKeywordSelectionDialog(getMainFrame(), _context,
                    directiveName.toUpperCase(), permitSelectionFromIncludedTaxaOnly, includeSpecimenAsOption,
                    returnSpecimenSelected);
            show(dlg);
            returnSelectedKeywords.addAll(dlg.getSelectedKeywords());
            return dlg.getSelectedTaxa();
        }
    }

    @Override
    public List<Item> promptForTaxaByList(String directiveName, boolean selectFromRemainingTaxaOnly,
            boolean autoSelectSingleValue, boolean singleSelect, boolean includeSpecimenAsOption,
            MutableBoolean returnSpecimenSelected, List<String> returnSelectedKeywords) {
        List<Item> taxaToSelect;

        String keyword = null;
        if (selectFromRemainingTaxaOnly) {
            taxaToSelect = _context.getTaxaForKeyword(IntkeyContext.TAXON_KEYWORD_REMAINING);
            keyword = IntkeyContext.TAXON_KEYWORD_REMAINING;
        } else {
            taxaToSelect = _context.getTaxaForKeyword(IntkeyContext.TAXON_KEYWORD_ALL);
            keyword = IntkeyContext.TAXON_KEYWORD_ALL;
        }

        if (taxaToSelect.size() == 1 && autoSelectSingleValue) {
            return taxaToSelect;
        } else {
            TaxonSelectionDialog dlg = new TaxonSelectionDialog(getMainFrame(), taxaToSelect,
                    directiveName.toUpperCase(), keyword, _context.displayNumbering(), singleSelect, _context,
                    includeSpecimenAsOption, returnSpecimenSelected);
            show(dlg);
            returnSelectedKeywords.addAll(dlg.getSelectedKeywords());
            return dlg.getSelectedTaxa();
        }
    }

    @Override
    public Boolean promptForYesNoOption(String message) {
        int selectedOption = JOptionPane.showConfirmDialog(getMainFrame(), message, null,
                JOptionPane.YES_NO_OPTION);
        if (selectedOption == JOptionPane.YES_OPTION) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public String promptForString(String message, String initialValue, String directiveName) {
        return (String) JOptionPane.showInputDialog(getMainFrame(), message, directiveName,
                JOptionPane.PLAIN_MESSAGE, null, null, initialValue);
    }

    @Override
    public List<String> promptForTextValue(TextCharacter ch, List<String> currentValues) {
        List<String> inputValues = null;

        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && !ch.getImages().isEmpty()) {
            try {
                CharacterImageDialog dlg = new CharacterImageDialog(getMainFrame(),
                        Arrays.asList(new Character[] { ch }), null, _context.getImageSettings(), true, true,
                        _context.displayScaled());
                dlg.setInitialTextValues(currentValues);
                dlg.displayImagesForCharacter(ch);
                show(dlg);
                if (dlg.okButtonPressed()) {
                    inputValues = dlg.getInputTextValues();
                } else if (dlg.cancelButtonPressed()) {
                    return null;
                }
            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
                return null;
            }
        }

        if (inputValues == null) {
            TextInputDialog dlg = new TextInputDialog(getMainFrame(), ch, currentValues,
                    _context.getImageSettings(), _context.displayNumbering(),
                    _context.getImageDisplayMode() != ImageDisplayMode.OFF, _context.displayScaled(),
                    _advancedMode);
            UIUtils.showDialog(dlg);
            if (dlg.okPressed()) {
                inputValues = dlg.getInputData();
            }
        }

        return inputValues;
    }

    @Override
    public Set<Integer> promptForIntegerValue(IntegerCharacter ch, Set<Integer> currentValues) {
        Set<Integer> returnValues = new HashSet<Integer>();
        Set<Integer> rawInputValues = null;

        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && !ch.getImages().isEmpty()) {
            try {
                CharacterImageDialog dlg = new CharacterImageDialog(getMainFrame(),
                        Arrays.asList(new Character[] { ch }), null, _context.getImageSettings(), true, true,
                        _context.displayScaled());
                dlg.setInitialIntegerValues(currentValues);
                dlg.displayImagesForCharacter(ch);
                show(dlg);
                if (dlg.okButtonPressed()) {
                    rawInputValues = dlg.getInputIntegerValues();
                } else if (dlg.cancelButtonPressed()) {
                    return null;
                }
            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
                return null;
            }
        }

        if (rawInputValues == null) {
            IntegerInputDialog dlg = new IntegerInputDialog(getMainFrame(), ch, currentValues,
                    _context.getImageSettings(), _context.displayNumbering(),
                    _context.getImageDisplayMode() != ImageDisplayMode.OFF, _context.displayScaled(),
                    _advancedMode);
            UIUtils.showDialog(dlg);
            if (dlg.okPressed()) {
                rawInputValues = dlg.getInputData();
            }
        }

        // The only acceptable values for an integer character are minimum - 1,
        // any values in the range minimum-maximum, or maximum + 1. Modify the
        // raw input values
        // in accordance with this.
        if (rawInputValues != null) {
            for (int value : rawInputValues) {
                if (value <= ch.getMinimumValue() - 1) {
                    returnValues.add(ch.getMinimumValue() - 1);
                } else if (value >= ch.getMaximumValue() + 1) {
                    returnValues.add(ch.getMaximumValue() + 1);
                } else {
                    returnValues.add(value);
                }
            }

            return returnValues;
        } else {
            return null;
        }
    }

    @Override
    public FloatRange promptForRealValue(RealCharacter ch, FloatRange currentValues) {
        FloatRange selectedValue = null;

        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && !ch.getImages().isEmpty()) {
            try {
                CharacterImageDialog dlg = new CharacterImageDialog(getMainFrame(),
                        Arrays.asList(new Character[] { ch }), null, _context.getImageSettings(), true, true,
                        _context.displayScaled());
                dlg.setInitialRealValues(currentValues);
                dlg.displayImagesForCharacter(ch);
                show(dlg);
                if (dlg.okButtonPressed()) {
                    selectedValue = dlg.getInputRealValues();
                } else if (dlg.cancelButtonPressed()) {
                    return null;
                }
            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
                return null;
            }
        }

        if (selectedValue == null) {
            RealInputDialog dlg = new RealInputDialog(getMainFrame(), ch, currentValues,
                    _context.getImageSettings(), _context.displayNumbering(),
                    _context.getImageDisplayMode() != ImageDisplayMode.OFF, _context.displayScaled(),
                    _advancedMode);
            UIUtils.showDialog(dlg);
            if (dlg.okPressed()) {
                selectedValue = dlg.getInputData();
            }
        }

        return selectedValue;
    }

    @Override
    public Set<Integer> promptForMultiStateValue(MultiStateCharacter ch, Set<Integer> currentSelectedStates,
            Character dependentCharacter) {
        Set<Integer> selectedStates = null;

        if (_context.getImageDisplayMode() == ImageDisplayMode.AUTO && !ch.getImages().isEmpty()) {
            try {
                CharacterImageDialog dlg = new CharacterImageDialog(getMainFrame(),
                        Arrays.asList(new Character[] { ch }), dependentCharacter, _context.getImageSettings(),
                        true, true, _context.displayScaled());
                dlg.setInitialSelectedStates(currentSelectedStates);
                dlg.displayImagesForCharacter(ch);
                show(dlg);
                if (dlg.okButtonPressed()) {
                    selectedStates = dlg.getSelectedStates();
                } else if (dlg.cancelButtonPressed()) {
                    return null;
                }
            } catch (IllegalArgumentException ex) {
                // Display error message if unable to display
                displayErrorMessage(UIUtils.getResourceString("CouldNotDisplayImage.error", ex.getMessage()));
                return null;
            }
        }

        if (selectedStates == null) {
            MultiStateInputDialog dlg = new MultiStateInputDialog(getMainFrame(), ch, currentSelectedStates,
                    dependentCharacter, _context.getImageSettings(), _context.displayNumbering(),
                    _context.getImageDisplayMode() != ImageDisplayMode.OFF, _context.displayScaled(),
                    _advancedMode);
            UIUtils.showDialog(dlg);
            if (dlg.okPressed()) {
                return dlg.getInputData();
            } else {
                return null;
            }
        }

        return selectedStates;
    }

    @Override
    public File promptForFile(List<String> fileExtensions, List<String> filePrefixes, String description,
            boolean createFileIfNonExistant) throws IOException {
        return UIUtils.promptForFile(fileExtensions, filePrefixes, description, createFileIfNonExistant,
                _lastOpenedDatasetDirectory, getMainFrame());
    }

    @Override
    public Boolean promptForOnOffValue(String directiveName, boolean initialValue) {
        OnOffPromptDialog dlg = new OnOffPromptDialog(getMainFrame(), directiveName.toUpperCase(), initialValue);
        show(dlg);
        if (dlg.isOkButtonPressed()) {
            return dlg.getSelectedValue();
        } else {
            return null;
        }
    }

    @Override
    public List<Object> promptForMatchSettings() {
        List<Object> retList = new ArrayList<Object>();

        SetMatchPromptDialog dlg = new SetMatchPromptDialog(getMainFrame(), true, _context.getMatchInapplicables(),
                _context.getMatchUnknowns(), _context.getMatchType());
        show(dlg);
        if (dlg.wasOkButtonPressed()) {
            boolean matchUnknowns = dlg.getMatchUnknowns();
            boolean matchInapplicables = dlg.getMatchInapplicables();
            MatchType matchType = dlg.getMatchType();
            retList.add(matchUnknowns);
            retList.add(matchInapplicables);
            retList.add(matchType);
        } else {
            return null;
        }

        return retList;
    }

    @Override
    public List<Object> promptForButtonDefinition() {
        List<Object> returnValues = new ArrayList<Object>();

        DefineButtonDialog dlg = new DefineButtonDialog(getMainFrame(), true);
        show(dlg);

        if (dlg.wasOkButtonPressed()) {
            returnValues.add(dlg.isInsertSpace());
            returnValues.add(dlg.isRemoveAllButtons());
            returnValues.add(dlg.getImageFilePath());
            returnValues.add(dlg.getCommands());
            returnValues.add(dlg.getBriefHelp());
            returnValues.add(dlg.getDetailedHelp());
            returnValues.add(dlg.enableIfUsedCharactersOnly());
            returnValues.add(dlg.enableInNormalModeOnly());
            returnValues.add(dlg.enableInAdvancedModeOnly());
            return returnValues;
        } else {
            // cancelled
            return null;
        }
    }

    @Override
    public Pair<ImageDisplayMode, DisplayImagesReportType> promptForImageDisplaySettings() {
        DisplayImagesDialog dlg = new DisplayImagesDialog(getMainFrame(), _context.getImageDisplayMode());
        show(dlg);

        if (dlg.wasOkButtonPressed()) {
            return new Pair<ImageDisplayMode, DisplayImagesReportType>(dlg.getSelectedImageDisplayMode(),
                    dlg.getSelectedReportType());
        } else {
            return null;
        }
    }

    @Override
    public String promptForDataset() {
        OpenDataSetDialog dlg = new OpenDataSetDialog(getMainFrame(), UIUtils.readDatasetIndex(),
                _lastOpenedDatasetDirectory);
        show(dlg);
        return dlg.getSelectedDatasetPath();
    }

    // ======== Methods for "find in characters" and "find in taxa" functions
    // ====================

    /**
     * Find taxa in the main UI window whose names and or synonyms match the
     * supplied search text. Highlight these taxa with a different color.
     * 
     * @param searchText
     *            the text to search with
     * @param searchSynonyms
     *            if true, search synonym text for taxa
     * @param searchEliminatedTaxa
     *            if true, include eliminated taxa in the search
     * @return the number of matching taxa.
     */
    public int findTaxa(String searchText, boolean searchSynonyms, boolean searchEliminatedTaxa) {

        IntkeyDataset dataset = _context.getDataset();

        List<Item> availableTaxa = _context.getAvailableTaxa();
        List<Item> eliminatedTaxa = _context.getEliminatedTaxa();
        _foundAvailableTaxa = new ArrayList<Item>();
        _foundEliminatedTaxa = new ArrayList<Item>();

        Map<Item, List<TextAttribute>> taxaSynonymyAttributes = dataset.getSynonymyAttributesForTaxa();

        for (Item taxon : availableTaxa) {
            if (SearchUtils.taxonMatches(searchText, taxon,
                    SearchUtils.getSynonymyStringsForTaxon(taxon, taxaSynonymyAttributes))) {
                _foundAvailableTaxa.add(taxon);
            }
        }

        for (Item taxon : eliminatedTaxa) {
            if (SearchUtils.taxonMatches(searchText, taxon,
                    SearchUtils.getSynonymyStringsForTaxon(taxon, taxaSynonymyAttributes))) {
                _foundEliminatedTaxa.add(taxon);
            }
        }

        // found available taxa must be sorted by difference count if the
        // tolerance has been
        // set to greater than zero - this mirrors the ordering in which the
        // available taxa are
        // displayed in this situation
        if (_context.getTolerance() > 0) {
            Collections.sort(_foundAvailableTaxa,
                    new DifferenceCountComparator(_context.getSpecimen().getTaxonDifferences()));
        }

        // eliminated taxa must always be sorted by difference count. This
        // mirrors the order in which the
        // eliminated taxa are displayed.
        Collections.sort(_foundEliminatedTaxa,
                new DifferenceCountComparator(_context.getSpecimen().getTaxonDifferences()));

        _availableTaxaCellRenderer.setTaxaToColor(new HashSet<Item>(_foundAvailableTaxa));
        _eliminatedTaxaCellRenderer.setTaxaToColor(new HashSet<Item>(_foundEliminatedTaxa));

        _listRemainingTaxa.repaint();
        _listEliminatedTaxa.repaint();

        return _foundAvailableTaxa.size() + _foundEliminatedTaxa.size();
    }

    /**
     * Select one of the taxa matched by the findTaxa() method. Used to iterate
     * over the matched taxa
     * 
     * @param matchedTaxonIndex
     *            the index of the matched taxon in the list of matched taxa..
     */
    public void selectCurrentMatchedTaxon(int matchedTaxonIndex) {

        if (matchedTaxonIndex < _foundAvailableTaxa.size()) {
            Item taxon = _foundAvailableTaxa.get(matchedTaxonIndex);
            _listRemainingTaxa.setSelectedValue(taxon, true);
            _listEliminatedTaxa.clearSelection();
        } else if (!_foundEliminatedTaxa.isEmpty()) {
            int offsetIndex = matchedTaxonIndex - _foundAvailableTaxa.size();
            if (offsetIndex < _foundEliminatedTaxa.size()) {
                Item taxon = _foundEliminatedTaxa.get(offsetIndex);
                _listEliminatedTaxa.setSelectedValue(taxon, true);
                _listRemainingTaxa.clearSelection();
            }
        }
    }

    /**
     * Select all taxa matched by the findTaxa() method
     */
    public void selectAllMatchedTaxa() {

        int[] availableTaxaSelectedIndices = new int[_foundAvailableTaxa.size()];
        for (int i = 0; i < _foundAvailableTaxa.size(); i++) {
            Item taxon = _foundAvailableTaxa.get(i);
            availableTaxaSelectedIndices[i] = _availableTaxaListModel.indexOf(taxon);
        }

        int[] eliminatedTaxaSelectedIndices = new int[_foundEliminatedTaxa.size()];
        for (int i = 0; i < _foundEliminatedTaxa.size(); i++) {
            Item taxon = _foundEliminatedTaxa.get(i);
            eliminatedTaxaSelectedIndices[i] = _eliminatedTaxaListModel.indexOf(taxon);
        }

        _listRemainingTaxa.setSelectedIndices(availableTaxaSelectedIndices);
        _listEliminatedTaxa.setSelectedIndices(eliminatedTaxaSelectedIndices);
    }

    /**
     * Find characters in the main UI window whose names and or state
     * descriptions match the supplied search text. Highlight these characters
     * with a different color.
     * 
     * @param searchText
     *            the text to search with
     * @param searchStates
     *            if true, search synonym text for taxa
     * @param searchUsedCharacters
     *            if true, include used characers in the search
     * @return the number of matching characters.
     */
    public int findCharacters(String searchText, boolean searchStates, boolean searchUsedCharacters) {
        List<Character> availableCharacters;

        IntkeyCharacterOrder charOrder = _context.getCharacterOrder();
        switch (charOrder) {
        case NATURAL:
            availableCharacters = _context.getAvailableCharacters();
            break;
        case BEST:
            availableCharacters = new ArrayList<Character>(_context.getBestOrSeparateCharacters().keySet());
            break;
        case SEPARATE:
            throw new NotImplementedException();
        default:
            throw new RuntimeException("Unrecognised character order");
        }

        List<Character> usedCharacters = _context.getUsedCharacters();

        _foundAvailableCharacters = new ArrayList<Character>();
        _foundUsedCharacters = new ArrayList<Character>();

        for (Character ch : availableCharacters) {
            if (SearchUtils.characterMatches(ch, searchText, searchStates)) {
                _foundAvailableCharacters.add(ch);
            }
        }

        if (searchUsedCharacters) {
            for (Character ch : usedCharacters) {
                if (SearchUtils.characterMatches(ch, searchText, searchStates)) {
                    _foundUsedCharacters.add(ch);
                }
            }
        }

        _availableCharactersListCellRenderer
                .setCharactersToColor(new HashSet<Character>(_foundAvailableCharacters));
        _usedCharactersListCellRenderer.setCharactersToColor(new HashSet<Character>(_foundUsedCharacters));

        _listAvailableCharacters.repaint();
        _listUsedCharacters.repaint();

        return _foundAvailableCharacters.size() + _foundUsedCharacters.size();
    }

    /**
     * Select one of the characters matched by the findCharacters() method. Used
     * to iterate over the matched characters
     * 
     * @param matchedCharacterIndex
     *            the index of the matched character in the list of matched
     *            characters
     */
    public void selectCurrentMatchedCharacter(int matchedCharacterIndex) {

        if (matchedCharacterIndex < _foundAvailableCharacters.size()) {
            Character ch = _foundAvailableCharacters.get(matchedCharacterIndex);
            _listAvailableCharacters.setSelectedValue(ch, true);
            _listUsedCharacters.clearSelection();
        } else if (!_foundUsedCharacters.isEmpty()) {
            int offsetIndex = matchedCharacterIndex - _foundAvailableCharacters.size();
            if (offsetIndex < _foundUsedCharacters.size()) {
                Character ch = _foundUsedCharacters.get(offsetIndex);
                Attribute attr = _context.getSpecimen().getAttributeForCharacter(ch);
                _listUsedCharacters.setSelectedValue(attr, true);
                _listAvailableCharacters.clearSelection();
            }
        }
    }

    /**
     * Comparator to sort taxa by the number of character values (attributes)
     * differing from the specimen, then by taxon number
     * 
     * @author ChrisF
     * 
     */
    private class DifferenceCountComparator implements Comparator<Item> {

        private Map<Item, Set<Character>> _taxaDifferingCharacters;

        /**
         * Constructor
         * 
         * @param taxaDifferingCharacters
         *            A map containing the characters for each taxa whose values
         *            differ from the current specimen.
         */
        public DifferenceCountComparator(Map<Item, Set<Character>> taxaDifferingCharacters) {
            _taxaDifferingCharacters = taxaDifferingCharacters;
        }

        @Override
        public int compare(Item t1, Item t2) {
            int diffT1 = _taxaDifferingCharacters.get(t1).size();
            int diffT2 = _taxaDifferingCharacters.get(t2).size();

            if (diffT1 == diffT2) {
                return t1.compareTo(t2);
            } else {
                return Integer.valueOf(diffT1).compareTo(Integer.valueOf(diffT2));
            }
        }
    }

    /**
     * Used to open a previously opened dataset via its initialization file
     * 
     * @param fileName
     */
    private void openPreviouslyOpenedFile(String fileName) {
        executeDirective(new NewDatasetDirective(), "\"" + fileName + "\"");
    }

    /**
     * This method saves information about the currently opened dataset:<br/>
     * 1. If the dataset was downloaded from a remote location, the user will be
     * given the option to save it to disk<br/>
     * 2. The dataset is added to the list of most recently used datasets<br/>
     * 3. If the dataset is not currently saved in the dataset index, the user
     * will be given the option to do this.
     */
    private void saveCurrentlyOpenedDataset() {
        String datasetTitle = _context.getDataset().getHeadingWithoutFormatting();

        // if the dataset was downloaded, ask the user if they wish to save it
        StartupFileData startupFileData = _context.getStartupFileData();

        boolean remoteDatasetSavedLocally = false;

        // Always use the path to the startup file supplied to the NEWDATASET
        // directive
        // Ignore the "inkFile" URL listed in the startup file.
        String datasetPath;
        URL startupFileURL = _context.getDatasetStartupURL();
        if (startupFileURL.getProtocol().equalsIgnoreCase("file")) {
            try {
                datasetPath = new File(startupFileURL.toURI()).getAbsolutePath();
            } catch (URISyntaxException ex) {
                datasetPath = startupFileURL.toString();
            }
        } else {
            datasetPath = startupFileURL.toString();
        }

        if (startupFileData != null && startupFileData.isRemoteDataset()) {
            int chosenOption = JOptionPane.showConfirmDialog(getMainFrame(),
                    UIUtils.getResourceString("SaveDownloadedDatasetPrompt.caption", datasetTitle),
                    UIUtils.getResourceString("Save.caption"), JOptionPane.YES_NO_OPTION);
            if (chosenOption == JOptionPane.YES_OPTION) {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

                int returnVal = fileChooser.showOpenDialog(getMainFrame());

                if (returnVal == JFileChooser.APPROVE_OPTION) {
                    File saveDir = fileChooser.getSelectedFile();

                    try {
                        File newInkFile = StartupUtils.saveRemoteDataset(_context, saveDir);
                        datasetPath = newInkFile.getAbsolutePath();
                        remoteDatasetSavedLocally = true;

                        // Remove the current startup file from the MRU as a new
                        // file will go in its
                        // place.
                        UIUtils.removeFileFromMRU(_context.getDatasetStartupFile().getAbsolutePath());
                    } catch (IOException ex) {
                        Logger.error("Error saving downloaded dataset", ex);
                        displayErrorMessage(
                                UIUtils.getResourceString("ErrorSavingDownloadedDataset.error", ex.getMessage()));
                        // not much we can do here, just abort saving/adding to
                        // recents list.
                        return;
                    }
                }
            }
        }

        // Add to list of most recently used datasets.
        if (_context.getDataset() != null) {
            UIUtils.addFileToMRU(datasetPath, datasetTitle, UIUtils.getPreviouslyUsedFiles());
        }

        // If the dataset is not present in the dataset index, give the user the
        // option to add it
        String promptMessage = null;
        if (startupFileData != null && startupFileData.isRemoteDataset()) {
            if (remoteDatasetSavedLocally) {
                promptMessage = UIUtils.getResourceString("AddSavedCopyOfDatasetToIndexPrompt.caption",
                        datasetTitle);
            } else {
                promptMessage = UIUtils.getResourceString("AddStartupFileForRemoteDatasetToIndexPrompt.caption",
                        datasetTitle);
            }
        } else {
            promptMessage = UIUtils.getResourceString("AddDatasetToIndexPrompt.caption", datasetTitle);
        }

        // check if the datasetPath is already present in the index
        if (!UIUtils.getDatasetIndexAsMap().containsKey(datasetPath)) {
            int chosenOption = JOptionPane.showConfirmDialog(getMainFrame(), promptMessage,
                    UIUtils.getResourceString("AddToDataset.caption"), JOptionPane.YES_NO_OPTION);
            if (chosenOption == JOptionPane.YES_OPTION) {
                addToDatasetIndex(datasetTitle, datasetPath);
            }
        }
    }

    /**
     * Add a dataset to the dataset index
     * 
     * @param datasetTitle
     *            the dataset's title
     * @param datasetPath
     *            the path to the dataset's startup file. This may be a local
     *            file path or a URL to a remotely hosted file.
     */
    private void addToDatasetIndex(String datasetTitle, String datasetPath) {
        EditDatasetIndexDialog dlg = new EditDatasetIndexDialog(getMainFrame(), UIUtils.readDatasetIndex(),
                datasetTitle, datasetPath);
        show(dlg);
        List<Pair<String, String>> modifiedDatasetIndex = dlg.getModifiedDatasetIndex();
        if (modifiedDatasetIndex != null) {
            UIUtils.writeDatasetIndex(modifiedDatasetIndex);
        }
    }

    /**
     * Get the bounds of the main window
     * 
     * @return the bounds of the main window
     */
    public Rectangle getClientBounds() {
        Rectangle r = _rootSplitPane.getBounds();
        // Rectangle outer = getMainFrame().getBounds();
        // r.x = r.x + outer.x;
        // r.y = r.y + _pnlAvailableCharactersHeader.getHeight();
        Point p1 = new Point(0, 0);
        SwingUtilities.convertPointToScreen(p1, _rootSplitPane);
        r.x = p1.x;
        r.y = p1.y + _pnlAvailableCharactersHeader.getHeight();
        r.height = r.height - _pnlAvailableCharactersHeader.getHeight();

        return r;
    }

    /**
     * Set the preferred look and feel as saved in preferences.
     */
    private void setLookAndFeel() {
        // To avoid setting the look and feel twice, we are updating the
        // resource bundle before the Swing
        // Application Framework sets the look and feel.
        try {
            getContext().setApplicationClass(Intkey.class);

            ResourceMap resources = getContext().getResourceMap();
            Method method = ResourceMap.class.getDeclaredMethod("getBundlesMap");
            method.setAccessible(true);
            Map<String, Object> resourceMap = (Map<String, Object>) method.invoke(resources);
            resourceMap.put("Application.lookAndFeel", UIUtils.getPreferredLookAndFeel());

        } catch (Throwable t) {
            // Doesn't matter if we fail, going with the defaults is fine.
        }
    }

    /**
     * Use the system look and feel
     */
    @Action
    public void systemLookAndFeel() {
        au.org.ala.delta.ui.util.UIUtils.systemLookAndFeel(getMainFrame());
        UIUtils.setPreferredLookAndFeel(UIUtils.SYSTEM_LOOK_AND_FEEL);
    }

    /**
     * Use the metal look and feel
     */
    @Action
    public void metalLookAndFeel() {
        au.org.ala.delta.ui.util.UIUtils.metalLookAndFeel(getMainFrame());
        UIUtils.setPreferredLookAndFeel(UIUtils.METAL_LOOK_AND_FEEL);
    }

    /**
     * Use the nimbus look and feel
     */
    @Action
    public void nimbusLookAndFeel() {
        au.org.ala.delta.ui.util.UIUtils.nimbusLookAndFeel(getMainFrame());
        UIUtils.setPreferredLookAndFeel(UIUtils.NIMBUS_LOOK_AND_FEEL);
    }

    /**
     * Prompt the user to set the size of the main window
     */
    @Action
    public void mnuItSetMainWindowSize() {
        JFrame mainFrame = getMainFrame();
        int currentWidth = mainFrame.getWidth();
        int currentHeight = mainFrame.getHeight();
        SetMainWindowSizeDialog dlg = new SetMainWindowSizeDialog(mainFrame, currentWidth, currentHeight);
        show(dlg);
        try {
            Pair<Integer, Integer> newWidthHeight = dlg.getWidthAndHeight();
            int newWidth = newWidthHeight.getFirst();
            int newHeight = newWidthHeight.getSecond();
            mainFrame.setSize(newWidth, newHeight);
        } catch (NumberFormatException ex) {
            displayErrorMessage(UIUtils.getResourceString("InvalidWidthOrHeight.error"));
        }
    }

    /**
     * Prompt the user to select the font to use in the application
     */
    @Action
    public void chooseFont() {
        Font f = UIManager.getFont("Label.font");
        Font newFont = JFontChooser.showDialog(getMainFrame(),
                UIUtils.getResourceString("SelectFontPrompt.caption"), f);
        if (newFont != null) {
            FontUIResource fontResource = new FontUIResource(newFont);
            Enumeration<Object> keys = UIManager.getDefaults().keys();
            while (keys.hasMoreElements()) {
                Object key = keys.nextElement();
                Object value = UIManager.get(key);
                if (value instanceof javax.swing.plaf.FontUIResource) {
                    UIManager.put(key, fontResource);
                }
            }
            SwingUtilities.updateComponentTreeUI(getMainFrame());
        }
    }
}