Package org.openquark.gems.client

Source Code of org.openquark.gems.client.GemCutter$LintOptionChooser

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* GemCutter.java
* Creation date: ?
* By: Luke Evans
*/
package org.openquark.gems.client;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InvocationEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicToolBarUI;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;

import org.openquark.cal.caldoc.CALDocTool;
import org.openquark.cal.caldoc.HTMLDocumentationGeneratorConfiguration;
import org.openquark.cal.compiler.CALSourceGenerator;
import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.Compiler;
import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.Refactorer;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.SourceMetrics;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.TypeChecker;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.filter.AcceptAllModulesFilter;
import org.openquark.cal.filter.AcceptAllQualifiedNamesFilter;
import org.openquark.cal.filter.ExcludeTestModulesFilter;
import org.openquark.cal.filter.ModuleFilter;
import org.openquark.cal.filter.QualifiedNameFilter;
import org.openquark.cal.filter.RegExpBasedUnqualifiedNameFilter;
import org.openquark.cal.machine.StatusListener;
import org.openquark.cal.metadata.ArgumentMetadata;
import org.openquark.cal.metadata.CALFeatureMetadata;
import org.openquark.cal.metadata.FunctionalAgentMetadata;
import org.openquark.cal.metadata.MetadataStore;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.CALSourcePathMapper;
import org.openquark.cal.services.CALWorkspace;
import org.openquark.cal.services.CarBuilder;
import org.openquark.cal.services.DefaultWorkspaceDeclarationProvider;
import org.openquark.cal.services.GemDesign;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.LocaleUtilities;
import org.openquark.cal.services.LocalizedResourceName;
import org.openquark.cal.services.MetaModule;
import org.openquark.cal.services.ModulePackager;
import org.openquark.cal.services.ModuleRevision;
import org.openquark.cal.services.Perspective;
import org.openquark.cal.services.ResourceIdentifier;
import org.openquark.cal.services.ResourceManager;
import org.openquark.cal.services.ResourceName;
import org.openquark.cal.services.RevisionHistory;
import org.openquark.cal.services.SimpleCALFileVault;
import org.openquark.cal.services.StandardVault;
import org.openquark.cal.services.Status;
import org.openquark.cal.services.StoredVaultElement;
import org.openquark.cal.services.Vault;
import org.openquark.cal.services.VaultElementInfo;
import org.openquark.cal.services.VaultRegistry;
import org.openquark.cal.services.VaultStatus;
import org.openquark.cal.services.VaultWorkspaceDeclarationProvider;
import org.openquark.cal.services.WorkspaceConfiguration;
import org.openquark.cal.services.WorkspaceDeclaration;
import org.openquark.cal.services.WorkspaceManager;
import org.openquark.cal.services.WorkspaceResource;
import org.openquark.cal.valuenode.Target;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.cal.valuenode.ValueNodeBuilderHelper;
import org.openquark.gems.client.Gem.PartInput;
import org.openquark.gems.client.browser.BrowserTree;
import org.openquark.gems.client.browser.GemBrowser;
import org.openquark.gems.client.explorer.TableTopExplorer;
import org.openquark.gems.client.generators.GemGenerator;
import org.openquark.gems.client.internal.EnterpriseSupport;
import org.openquark.gems.client.internal.EnterpriseSupportFactory;
import org.openquark.gems.client.utilities.ExtendedUndoManager;
import org.openquark.gems.client.utilities.ExtendedUndoableEditSupport;
import org.openquark.gems.client.utilities.PreferencesHelper;
import org.openquark.gems.client.valueentry.ValueEditor;
import org.openquark.gems.client.valueentry.ValueEditorHierarchyManager;
import org.openquark.gems.client.valueentry.ValueEditorManager;
import org.openquark.gems.client.valueentry.ValueEntryException;
import org.openquark.util.Pair;
import org.openquark.util.SimpleConsoleHandler;
import org.openquark.util.TextEncodingUtilities;
import org.openquark.util.UnsafeCast;
import org.openquark.util.ui.DetailsDialog;
import org.openquark.util.ui.ExtensionFileFilter;
import org.openquark.util.ui.SwingWorker;
import org.openquark.util.ui.UIUtilities;
import org.openquark.util.xml.XMLPersistenceConstants;


/**
* This type was generated by a SmartGuide.
*/
public final class GemCutter extends JFrame {

    private static final long serialVersionUID = 8549446034063034475L;

    /** The namespace for log messages from the gems client packages. */
    public static final String CLIENT_LOGGER_NAMESPACE = "org.openquark.gems.client";
   
    /** An instance of a Logger for gems client messages. */
    static final Logger CLIENT_LOGGER = Logger.getLogger(CLIENT_LOGGER_NAMESPACE);

    static {
        CLIENT_LOGGER.setLevel(Level.FINEST);
    }
   
    /** System property which causes GemCutter to load its workspace from the file system path named by the value */
    public static final String GEMCUTTER_PROP_WORKSPACE_FILE = "org.openquark.gems.client.gemcutter.workspace";
   
    /** System property which causes GemCutter to load its workspace from the file on the classpath named by the value
     * (unqualified file name - searches within folders called "Workspace Declarations" on the class path)
     * */
    public static final String GEMCUTTER_PROP_DEFAULT_STANDARD_VAULT_WORKSPACE = "org.openquark.gems.client.gemcutter.default.standardvault.workspace";
   
    /** System property which causes GemCutter to set its initial current module to the value */
    public static final String GEMCUTTER_PROP_MODULE = "org.openquark.gems.client.gemcutter.module";

    /** The default StandardVault workspace file. */
    private static final String DEFAULT_WORKSPACE_FILE = "gemcutter.default.cws";
   

    /** The default file extension for exported .jar files. */
    public static final String JAR_FILE_EXTENSION = "jar";

    /** The default file extension for exported .car files. */
    public static final String CAR_FILE_EXTENSION = "car";

    /** Whether or not to use a nullary workspace. */
    private static final boolean USE_NULLARY_WORKSPACE = true;
   
    /** The default workspace client id. */
    public static final String DEFAULT_WORKSPACE_CLIENT_ID = USE_NULLARY_WORKSPACE ? null : "gemcutter";
   
    /** List of pairs containing the name of the background(fst) and the corresponding file name (snd). */
    static List<Pair<String, String>> backgrounds = new ArrayList<Pair<String, String>>(4);
    static{
        backgrounds.add(new Pair<String, String>((getResourceString("CherryBackground") + " " + getResourceString("Photolook")), "/Resources/Cherry.jpg"));
        backgrounds.add(new Pair<String, String>((getResourceString("MapleBackground") + " " + getResourceString("Photolook")), "/Resources/Maple.jpg"));
        backgrounds.add(new Pair<String, String>((getResourceString("BirchBackground") + " " + getResourceString("Photolook")), "/Resources/Birch.jpg"));
        backgrounds.add(new Pair<String, String>(getResourceString("NoBackground"), ""));
    }

    /** Map from compile status to the property key to look up the localized status string for that status.
     *  Used by the status message displayer */
    private static final Map<StatusListener.Status, String> statusToPropertyKeyMap = new HashMap<StatusListener.Status, String>();
    static {
        statusToPropertyKeyMap.put(StatusListener.SM_COMPILED, "SM_Compiled");
        statusToPropertyKeyMap.put(StatusListener.SM_NEWMODULE, "SM_NewModule");
        statusToPropertyKeyMap.put(StatusListener.SM_GENCODE, "SM_GenCode");
        statusToPropertyKeyMap.put(StatusListener.SM_GENCODE_DONE, "SM_GenCode_Done");
        statusToPropertyKeyMap.put(StatusListener.SM_ENTITY_GENERATED, "SM_Entity_Generated");
        statusToPropertyKeyMap.put(StatusListener.SM_ENTITY_GENERATED_FILE_WRITTEN, "SM_Entity_Generated_File_Written");
        statusToPropertyKeyMap.put(StatusListener.SM_START_COMPILING_GENERATED_SOURCE, "SM_Start_Compiling_Generated_Source");
        statusToPropertyKeyMap.put(StatusListener.SM_END_COMPILING_GENERATED_SOURCE, "SM_End_Compiling_Generated_Source");
        statusToPropertyKeyMap.put(StatusListener.SM_LOADED, "SM_Loaded");
    }

    /* Preference key names. */
    public static final String BACKGROUND_FILE_NAME_PREF_KEY    = "backgroundFileName";
    public static final String ADD_MODULE_DIRECTORY_PREF_KEY    = "addModuleDirectory";
    public static final String EXPORT_MODULE_DIRECTORY_PREF_KEY = "exportModuleDirectory";
    public static final String OPEN_DESIGN_DIRECTORY_PREF_KEY   = "openDesignDirectory";
    public static final String WINDOW_PROPERTIES_PREF_KEY       = "windowProperties";
    public static final String FILTER_TEST_MODULES_PREF_KEY     = "filterTestModules";
    public static final String EXCLUDE_FUNCTIONS_BY_REGEXP_PREF_KEY = "excludeFunctionsByRegexp";
    public static final String EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY = "excludeFunctionsByRegexpArgument";
    public static final String INCLUDE_REDUNDANT_LAMBDAS_PREF_KEY = "includeRedundantLambdas";
    public static final String INCLUDE_UNPLINGED_PRIMITIVE_ARGS_PREF_KEY = "includeUnplingedPrimitiveArgs";
    public static final String INCLUDE_MISMATCHED_WRAPPER_PLINGS_PREF_KEY = "includeMismatchedWrapperPlings";
    public static final String INCLUDE_UNUSED_PRIVATE_FUNCTIONS_PREF_KEY = "includeUnusedPrivateFunctions";
    public static final String INCLUDE_REFERENCED_LET_VARIABLES_PREF_KEY = "includeUnreferenceLetVariables";
    public static final String TRACE_SKIPPED_PREF_KEY           = "traceSkipped";
   
    // this key is private since all access to the value should go through the get/set methods in this class
    private static final String LOCALE_PREF_KEY                 = "locale";
   
    /* Default preference values. */
    public static final String BACKGROUND_FILE_NAME_DEFAULT  = backgrounds.get(0).snd();
    public static final String ADD_MODULE_DIRECTORY_DEFAULT  = CALSourcePathMapper.INSTANCE.getBaseResourceFolder().getPathElements()[0];
    public static final String OPEN_DESIGN_DIRECTORY_DEFAULT = "Designs";
    public static final boolean FILTER_TEST_MODULES_DEFAULT  = true;
    public static final boolean EXCLUDE_FUNCTIONS_BY_REGEXP_DEFAULT = false;
    public static final String EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT = "(.*Example.*)|(.*example.*)|(.*test.*)|(.*Test.*)";
    public static final boolean INCLUDE_REDUNDANT_LAMBDAS_DEFAULT = true;
    public static final boolean INCLUDE_UNPLINGED_PRIMITIVE_ARGS_DEFAULT = true;
    public static final boolean INCLUDE_MISMATCHED_WRAPPER_PLINGS_DEFAULT = true;
    public static final boolean INCLUDE_UNUSED_PRIVATE_FUNCTIONS_DEFAULT = true;
    public static final boolean INCLUDE_REFERENCED_LET_VARIABLES_DEFAULT = true;
    public static final boolean TRACE_SKIPPED_DEFAULT = false;
   
    /** The maximum number of undo's to show in undo/redo drop downs */
    private static final int MAX_DISPLAYED_UNDOS = 10;

    /** The intellicut manager */
    private IntellicutManager intellicutManager;

    /** The dialog that handles all of the preferences settings */
    private PreferencesDialog preferencesDialog;
   
    /** The design manager responsible for loading/saving gems. */
    private GemCutterPersistenceManager persistenceManager;

    /** The support class for connecting to enterprise. */
    private final EnterpriseSupport enterpriseSupport = EnterpriseSupportFactory.getInstance(CLIENT_LOGGER);
   
    /**
     * If an action has this value set a button for this action is assumed to be
     * the drop down button in a drop down button arrangement. This will cause the
     * toolbar to raise the border of the parent button on mouse over.
     */
    public static final String ACTION_BUTTON_IS_DROP_CHILD_KEY = "ButtonIsDropChild";
   
    /**
     * If an action has this value set a button for this action is assumed to be
     * the main button in a drop down button arrangement. This will cause the toolbar
     * to raise the border of the child button on mouse over.
     */
    public static final String ACTION_BUTTON_IS_DROP_PARENT_KEY = "ButtonIsDropParent";
   
    /** Time that a timed status message will last */
    private static final int STATUS_MESSAGE_TIME = 5000;

    /** The Navigation ToolBar. */
    private NavigationToolBar navigationToolBar = null;
   
    /** This field is used to store tab selection state when entering run mode.
     *   If the target is run, the currently-selected tab is stored in this field and the currently-selected tab is set to the arguments tab.
     *   If a non-target is run, this field is set to null. */
    private Component explorerArgumentsPaneEditModeSelectedTab;

    /** This is the gem that should be run if the run button is clicked */
    private Gem currentGemToRun;
   
    /** Listener for the current gem to run that updates it if the gem graph or table top change. */
    private final CurrentGemToRunListener currentGemToRunListener = new CurrentGemToRunListener();
   
    /** Listener that updates the debug menu if the target becomes runnable or not runnable. */
    private final TargetRunnableListener targetRunnableListener = new TargetRunnableListener();

    /**
     * This is the collector gem for which more reflectors should be added
     * when the add reflector button is pressed.
     */
    private CollectorGem currentCollectorForAddingReflector;
   
    /** Listener for the current collector for adding emitters that updates it if the gem graph or table top change. */
    private final CurrentCollectorListener currentCollectorListener = new CurrentCollectorListener();

    /** The undoable edit to be undone when the GemCutter is not in the dirty state. 
     *  If null, the GemCutter is not in the dirty state if there are no edits to undo. */
    private UndoableEdit editToUndoWhenNonDirty = null;
   
    /** Splash screen for Gem Cutter. */
    // This is a member because it needs to be accessible by the StatusListener in the
    // compileWorkspace method that listens for messages from the CAL compiler.
    private GemCutterSplashScreen gemCutterSplashScreen = null;
   
    // Other members
    private TypeColourManager typeColours;
    private GUIState guiState = GUIState.EDIT;
    private DisplayedGem displayedGemToAdd;
    private StatusMessageManager statusMessageManager;
   
    /** the current background image (null if none) */
    private BufferedImage backgroundImage;
   
    /** The undo manager for the GemCutter. */
    private ExtendedUndoManager extendedUndoManager;

    /** The displayed gem runner performs the work of actually executing the gem. */
    private DisplayedGemRunner displayedGemRunner;

    /** The valueRunner is used to convert a CAL definition to a value (in the form of a value node). */
    private ValueRunner valueRunner;

    /** The ValueEditorManager manages the creation and consistency of the value editors in a given session. */
    private ValueEditorManager valueEditorManager;
   
    /** A manager for hierarchies spawned by tabletop VEPs. */
    private ValueEditorHierarchyManager tableTopEditorHierarchyManager;
   
    /** A manager for hierarchies spawned by output panels. */
    private ValueEditorHierarchyManager outputPanelHierarchyManager;

    /** The output display manager*/
    private OutputDisplayManager outputDisplayManager;

    /** The owner of the CAL navigator and metadata viewer/editor. */
    private NavigatorAdapter navigatorOwner;

    /** The clipboard used to store the cut/copied gems in the tabletop */
    private Clipboard clipboard;

    /** The workspace manager to manage the workspace we're building. */
    private WorkspaceManager workspaceManager;

    /** The perspective that represents the current point of view. */
    private Perspective perspective;
   
    /** The name of the preferred working module.
     *  In the event of a compile failure, the current module may no longer exist if it is a dependent of the module with the failure.
     *  In this case, the current module is changed to another module.
     *  If the failure is later fixed, and recompilation occurs, we attempt to change back. */
    private ModuleName preferredWorkingModuleName = null;
   
    private TableTop tableTop = null;  
    private TableTopExplorerAdapter tableTopExplorerAdapter = null;

    // GUI Elements
    private JPanel jFrameContentPane = null;
    private JPanel statusBarPane = null;
    private JLabel statusMsgLabel1 = null;
    private JLabel statusMsgLabel2 = null;
    private JToolBar toolBarPane = null;
    private JScrollPane tableTopScrollPane = null;
    private GemBrowser gemBrowser = null;   
    private JPanel gemBrowserPanel = null;
    private JTabbedPane explorerArgumentsPane = null;
    private JSplitPane explorerBrowserSplit = null;
    private JSplitPane browserOverviewSplit = null;
    private OverviewPanel overviewPanel = null;
    private ArgumentExplorer argumentExplorer = null;
   

    private JMenuBar gemCutterJMenuBar = null;

    private JMenu editMenu = null;
    private JMenu fileMenu = null;
    private JMenu debugMenu = null;
    private JMenu helpMenu = null;
    private JMenu viewMenu = null;
    private JMenu insertMenu = null;
    private JMenu generateMenu = null;
    private JMenu workspaceMenu = null;
    private JMenu runMenu = null;

    // It is important to have access to these buttons so that the associated popup menus
    // can be placed correctly on the screen
    private JButton addReflectorGemDropDownButton = null;
    private JButton runDropDownButton = null;
    private JButton undoButton = null;
    private JButton redoButton = null;

    // Keep these around so that we can enable or disable depending on context
    private JButton undoDropDownButton = null;
    private JButton redoDropDownButton = null;
   
    // Keep these around so we can change the text/tooltip depending on context
    private JMenuItem redoMenuItem = null;
    private JMenuItem undoMenuItem = null;
    private JMenu copySpecialMenu = null;
    private JButton addReflectorGemButton = null;
    private JMenuItem addReflectorGemMenuItem = null;
   
    // Hopefully a temporary thing - allows swapping of Run and Resume menu items and buttons
    private JMenuItem runMenuItem = null;
    private JMenu runSubMenu = null;
    private JMenuItem resumeMenuItem = null;
    private JButton resumeRunButton = null;
    private JButton runButton = null;
   
    /** A reference to the Play button popup menu so that we can close it if necessary. */
    private JPopupMenu runDropDownMenu = null;
   
    // Actions used by the menubar and toolbar
    private Action newAction = null;
    private Action openAction = null;
    private Action saveGemAction = null;
    private Action exitAction = null;
   
    private Action undoAction = null;
    private Action undoDropDownAction = null;
    private Action redoAction = null;
    private Action redoDropDownAction = null;
    private Action cutAction = null;
    private Action copyAction = null;
    private Action copySpecialImageAction = null;
    private Action copySpecialTargetSourceAction = null;
    private Action copySpecialTextAction = null;
    private Action pasteAction = null;
    private Action deleteAction = null;
    private Action selectAllAction = null;
    private Action searchAction = null;
   
    private Action viewToolbarAction = null;
    private Action viewStatusbarAction = null;
    private Action viewOverviewAction = null;
    private Action viewArgumentExplorerAction = null;
    private Action viewExplorerAction = null;
    private Action targetDockingAction = null;
    private Action arrangeGraphAction = null;
    private Action fitTableTopAction = null;
    private Action debugOutputAction = null;
   
    private Action dumpDefinitionAction = null;
    private Action allowPreludeRenamingAction = null;
    private Action allowDuplicateRenamingAction = null;
   
    private Action addGemAction = null;
    private Action addValueGemAction = null;
    private Action addCodeGemAction = null;
    private Action addCollectorGemAction = null;
    private Action addReflectorGemAction = null;
    private Action addReflectorGemDropDownAction = null;
    private Action addRecordCreationGemAction = null;
    private Action addRecordSelectionGemAction = null;
   
    // Add module submenu
    private JMenu addModuleSubMenu = null;
    private Action addModuleFromStdVaultAction = null;
    private Action addModuleFromSourceFileAction = null;
    private Action addEnterpriseModuleAction = null;

    // Export module submenu
    private JMenu exportModuleSubMenu = null;
    private Action exportModuleToCEAction = null;
    private Action exportModuleToJarAction = null;
   
    // Sync submenu
    private JMenu syncSubMenu = null;
    private Action syncToHeadAction = null;
    private Action syncToEnterpriseDeclarationAction = null;
   
    private Action removeModuleAction = null;
    private Action workspaceVaultStatusAction = null;
    private Action switchWorkspaceAction = null;
    private Action deployWorkspaceToEnterpriseAction = null;
    private Action exportWorkspaceToCarsAction = null;
    private JMenu renameSubMenu = null;   
    private Action renameGemAction = null;
    private Action renameTypeAction = null;
    private Action renameClassAction = null;
    private Action renameModuleAction = null;
    private Action recompileAction = null;
    private Action compileModifiedAction = null;
    private Action createMinimalWorkspaceAction = null;
    private Action workspaceInfoAction = null;

    private Action runAction = null;
    private Action runDropDownAction = null;
    private Action resumeRunAction = null;
    private Action stopAction = null;
    private Action resetAction = null;
    private final JProgressBar progressBar = new JProgressBar();
   
    private Action helpTopicsAction = null;
    private Action aboutBoxAction = null;
    private Action preferencesAction = null;
   
    /** The action listener that launches the JavaHelp help system. */
    private ActionListener helpSystemLauncher = null;
       
    /** The search dialog currently being displayed */
    private SearchDialog searchDialog = null;
   
    // Cursors for use when adding gems using menu items or buttons
    static final Cursor addValueGemCursor;
    static final Cursor addFunctionGemCursor;
    static final Cursor addCodeGemCursor;
    static final Cursor addCollectorGemCursor;
    static final Cursor addTriangularReflectorGemCursor;
    static final Cursor addOvalReflectorGemCursor;
    static final Cursor addRecordFieldSelectionGemCursor;
    static final Cursor addRecordCreationGemCursor;
   
    // Initialize the cursors
    static {
        // First get some useful reference objects.
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension bestsize = toolkit.getBestCursorSize(1,1);

        // Do we support custom cursors?
        if (bestsize.width > 1){
            // Get the images.  Use ImageIcon to ensure that they're loaded.
            ImageIcon addValueCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddValue.gif"));
            ImageIcon addFunctionCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddFunction.gif"));
            ImageIcon addCodeCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddCode.gif"));
            ImageIcon addCollectorCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddCollector.gif"));
            ImageIcon addTriangularReflectorCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddReflector.gif"));
            ImageIcon addOvalReflectorCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddEmitter.gif"));
            ImageIcon addRecordFieldSelectionCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddRecordSelectionGem.gif"));
            ImageIcon addRecordCreationCursorImage = new ImageIcon(GemCutter.class.getResource("/Resources/cursorAddRecordCreationGem.gif"));
           
            // Scale the images to the cursor size.
            BufferedImage scaledValueCursorImage = GemCutterPaintHelper.getResizedImage(addValueCursorImage.getImage(), bestsize);
            BufferedImage scaledFunctionCursorImage = GemCutterPaintHelper.getResizedImage(addFunctionCursorImage.getImage(), bestsize);
            BufferedImage scaledCodeCursorImage = GemCutterPaintHelper.getResizedImage(addCodeCursorImage.getImage(), bestsize);
            BufferedImage scaledCollectorCursorImage = GemCutterPaintHelper.getResizedImage(addCollectorCursorImage.getImage(), bestsize);
            BufferedImage scaledTriangularReflectorCursorImage = GemCutterPaintHelper.getResizedImage(addTriangularReflectorCursorImage.getImage(), bestsize);
            BufferedImage scaledOvalReflectorCursorImage = GemCutterPaintHelper.getResizedImage(addOvalReflectorCursorImage.getImage(), bestsize);
            BufferedImage scaledRecordFieldSelectionGemCursorImage = GemCutterPaintHelper.getResizedImage(addRecordFieldSelectionCursorImage.getImage(), bestsize);
            BufferedImage scaledRecordCreationGemCursorImage = GemCutterPaintHelper.getResizedImage(addRecordCreationCursorImage.getImage(), bestsize);
           
            // Define the cursors so that their hot spots are at (0, 0) of the image.
            Point hotSpot = new Point(0, 0);
            addValueGemCursor = toolkit.createCustomCursor(scaledValueCursorImage, hotSpot, "AddValueGemCursor");
            addFunctionGemCursor = toolkit.createCustomCursor(scaledFunctionCursorImage, hotSpot, "AddFunctionGemCursor");
            addCodeGemCursor = toolkit.createCustomCursor(scaledCodeCursorImage, hotSpot, "AddCodeGemCursor");
            addCollectorGemCursor = toolkit.createCustomCursor(scaledCollectorCursorImage, hotSpot, "AddCollectorGemCursor");
            addTriangularReflectorGemCursor = toolkit.createCustomCursor(scaledTriangularReflectorCursorImage, hotSpot, "AddTriangularReflectorGemCursor");
            addOvalReflectorGemCursor = toolkit.createCustomCursor(scaledOvalReflectorCursorImage, hotSpot, "AddOvalReflectorGemCursor");
            addRecordFieldSelectionGemCursor = toolkit.createCustomCursor(scaledRecordFieldSelectionGemCursorImage, hotSpot, "AddRecordFieldSelectionGemCursor");
            addRecordCreationGemCursor = toolkit.createCustomCursor(scaledRecordCreationGemCursorImage, hotSpot, "AddRecordCreationGemCursor");
                           
        } else {
            // We don't support custom cursors so use the plain crosshair cursor instead.
            Cursor crosshairs = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
            addValueGemCursor = crosshairs;
            addFunctionGemCursor = crosshairs;
            addCodeGemCursor = crosshairs;
            addCollectorGemCursor = crosshairs;
            addTriangularReflectorGemCursor = crosshairs;
            addOvalReflectorGemCursor = crosshairs;
            addRecordFieldSelectionGemCursor = crosshairs;
            addRecordCreationGemCursor = crosshairs;
        }
    }

    /**
     * Customized JToolBar for handling the Navigation buttons.
     * @author Michael Cheng?
     */
    private static class NavigationToolBar extends JToolBar {
        private static final long serialVersionUID = -6026389712592950262L;
        protected JFrame frame = null;

        private NavigationToolBar() {
               
            setRollover(true);
           
            setUI(new BasicToolBarUI() {
               
                protected JFrame createFloatingFrame(JToolBar tb) {
                   
                    frame = super.createFloatingFrame(tb);
                    frame.setTitle("Nav:");
                   
                    // Give the frame the GemCutter icon               
                    frame.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/Resources/gemcutter_16.gif")));
                    return frame;
                }
            });
        }  
    }

    /**
     * The action listener on the navigation buttons on the navigation toolbar.
     * @author Michael Cheng (probably)
     */
    private class ParameterNavigationActionListener implements ActionListener {
   
        /** The editor on which this listener will act. */
        private final ValueEditor editor;

        /**
         * Constructor for a ParameterNavigationActionListener.
         * @param editor the editor on which this listener will act.
         */
        public ParameterNavigationActionListener(ValueEditor editor) {
            this.editor = editor;
        }
   
        /**
         * {@inheritDoc}
         */
        public void actionPerformed(ActionEvent evt) {
   
            // Scroll the TableTopPanel such that the assigned ValueEntryPanel is as center as possible.
            TableTopPanel tableTopPanel = getTableTopPanel();
   
            Rectangle vepBounds = editor.getBounds();
   
            int centerX = vepBounds.getLocation().x + vepBounds.width/2;
            int centerY = vepBounds.getLocation().y + vepBounds.height/2;
       
            Point centerPoint = new Point(centerX, centerY);
   
            JViewport viewPort = getTableTopScrollPane().getViewport();
   
            Point rectPoint = new Point(centerPoint.x - viewPort.getWidth() / 2, centerPoint.y - viewPort.getHeight() / 2);
       
            Rectangle scrollRect = new Rectangle(rectPoint, new Dimension(viewPort.getWidth(), viewPort.getHeight()));
           
            tableTopPanel.scrollRectToVisible(scrollRect);
           
            tableTopPanel.scrollRectToVisible(editor.getBounds());
   
            // Only activate the editor if the focus is somewhere in GemCutter.
            // Else, no activation (the Focus could be in the NavToolBar, and messiness occurs if we try to activate,
            //  since the NavToolBar will not relinquish the focus).
            Component focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
            if (isAncestorOf(focusedComponent)) {
                editor.getValueEditorHierarchyManager().activateEditor(editor);
            }
        }
    }
   
    /**
     * A listener that updates the current collector for adding emitters if the gem graph or the
     * table top change. Also updates widget labels & tooltips if the name changes.
     */
    private class CurrentCollectorListener implements DisplayedGemStateListener, GemGraphChangeListener, NameChangeListener {
        
        /** Update the widgets if the gem name changes. */         
        public void nameChanged(NameChangeEvent e) {
            updateReflectorWidgets();
        }
       
        /**
         * {@inheritDoc}
         * If there is no collector for which we should add emitters and the user selects a collector
         * then make that the new collector for which to add emitters and enable the add emitter button.
         */
        public void selectionStateChanged(DisplayedGemStateEvent e) {

            DisplayedGem displayedGem = (DisplayedGem)e.getSource();
            Gem gem = displayedGem.getGem();
           
            if (gem instanceof CollectorGem && tableTop.isSelected(displayedGem)) {

                if (currentCollectorForAddingReflector == null) {
                    setReflectorCollector((CollectorGem)gem);
                }
            }
        }

        /**
         * {@inheritDoc}
         * Reset the widgets if the run state changes since they might become disabled.
         */
        public void runStateChanged(DisplayedGemStateEvent e) {
            updateReflectorWidgets();
        }

        /**
         * If the collector for which we're supposed to add emitters
         * is removed then disable the add emitter button. If there is
         * one other collector then automatically use it as the new collector
         * for adding emitters, otherwise make the user select one.
         */
        public void gemRemoved(GemGraphRemovalEvent e) {
            Gem removedGem = (Gem) e.getSource ();

            if (removedGem.equals(currentCollectorForAddingReflector)) {
                selectNewReflectorCollector();
            }
        }

        /**
         * Get a collector from the GemGraph (if any)
         * @return a reflecting collector from the GemGraph, or null if there are no reflecting collectors in the GemGraph.
         */
        private CollectorGem getNextCollector() {
            Set<CollectorGem> tableTopCollectorSet = getTableTop().getGemGraph().getCollectors();
            return tableTopCollectorSet.isEmpty() ? null : tableTopCollectorSet.iterator().next();
        }

        /**
         * If there is no current collector for adding emitters then make the newly added
         * one the default. But only if there are no other collectors on the table top,
         * otherwise make the user specifically select the collector they want.
         * Also if a new emitter is added make the current collector the one that the
         * emitter is from.
         */
        public void gemAdded(GemGraphAdditionEvent e) {
            Gem addedGem = (Gem) e.getSource();

            if (addedGem instanceof CollectorGem && currentCollectorForAddingReflector == null) {
                selectNewReflectorCollector();
               
            } else if (addedGem instanceof ReflectorGem) {
                setReflectorCollector(((ReflectorGem)addedGem).getCollector());
            }
        }

        public void gemConnected(GemGraphConnectionEvent e) {
        }
               
        public void gemDisconnected(GemGraphDisconnectionEvent e) {
        }
       
        /**
         * Set a new collector as the current collector for adding reflectors.
         * @param newCollector the new collector
         */
        public void setReflectorCollector(CollectorGem newCollector) {
           
            if (currentCollectorForAddingReflector != null) {
                currentCollectorForAddingReflector.removeNameChangeListener(this);
            }
           
            currentCollectorForAddingReflector = newCollector;
            if (currentCollectorForAddingReflector != null) {
                currentCollectorForAddingReflector.addNameChangeListener(this);
            }
            updateReflectorWidgets();
        }
       
        /**
         * Selects a new collector for adding reflectors if a default selection can be made.
         */
        public void selectNewReflectorCollector() {
            setReflectorCollector(getNextCollector());
        }           

        /**
         * Updates the action and tooltips for the add reflector button and menu items.
         */
        private void updateReflectorWidgets() {
           
            // Check if there are any more reflectors to select from. If there are then
            // keep the popup menu enabled. Otherwise disable it.
            CollectorGem collectorGem = getNextCollector();
            boolean reflectingCollectorsRemain = collectorGem != null;
            getAddReflectorGemAction().setEnabled(reflectingCollectorsRemain);
            getAddReflectorGemDropDownAction().setEnabled(reflectingCollectorsRemain);
           
            // Now update the tooltips and the menu item. The menu item is
            // only enabled if a specific collector for adding reflectors it selected.
            if (currentCollectorForAddingReflector != null) {          
                getAddReflectorGemMenuItem().setEnabled(true);
                getAddReflectorGemMenuItem().setText(getResourceString("AddReflectorGemForMenu") + " " + currentCollectorForAddingReflector.getUnqualifiedName());
                getAddReflectorGemButton().setToolTipText(getResourceString("AddReflectorGemForToolTip") + " " + currentCollectorForAddingReflector.getUnqualifiedName());           
            } else {
                getAddReflectorGemMenuItem().setEnabled(false);
                getAddReflectorGemMenuItem().setText(getResourceString("AddReflectorGemMenu"));
                getAddReflectorGemButton().setToolTipText(getResourceString("AddReflectorGemToolTip"));
            }
        }     
       
    }
   
    /** A listener that listens for when the target becomes runnable or non-runnable and
     *  updates the debug menu accordingly.
     */
    private class TargetRunnableListener implements GemConnectionListener, GemStateListener {
       
        /**
         * {@inheritDoc}
         * A broken gem can potentially cause the target to be non-runnable, so we
         *  need to watch for it.        
         */
        public void brokenStateChanged(GemStateEvent e) {
            updateDumpDefinitionAction();
        }
       
        /** Update the dumpDefinitionAction in case the target has now become runnable */
        public void connectionOccurred(GemConnectionEvent e) {
            updateDumpDefinitionAction();
        }
       
        /** Update the dumpDefinitionAction in case the target has now become non-runnable */
        public void disconnectionOccurred(GemConnectionEvent e) {
            updateDumpDefinitionAction();
        }
       
        /** Enable or disable the dumpDefinition action according to whether
         *  the target is runnable or not.
         */
        private void updateDumpDefinitionAction() {
            CollectorGem targetGem = getTableTop().getTargetCollector();           
            getDumpDefinitionAction().setEnabled(targetGem.isRunnable());           
        }
}

    /**
     * A listener that updates the current gem to run if the gem graph or the table top selection changes.
     * Also takes care of updating widget labels & tooltips if the gem name changes.
     */
    private class CurrentGemToRunListener implements DisplayedGemStateListener, GemGraphChangeListener, NameChangeListener {
       
        /** Update widgets if the gem name changes. */
        public void nameChanged(NameChangeEvent e) {
            updateRunWidgets();
        }
       
        /**
         * If there is no current gem to run then make the selected gem
         * the one to run. This will also make any newly added gem the
         * one to run since newly added gems become selected immediately.
         */
        public void selectionStateChanged(DisplayedGemStateEvent e) {
            DisplayedGem changedGem = (DisplayedGem) e.getSource ();
                   
            if (currentGemToRun == null &&
                changedGem.getGem().isRunnable() &&
                tableTop.isSelected(changedGem)) {
                           
                currentGemToRun = changedGem.getGem();
                currentGemToRun.addNameChangeListener(this);
                   
                updateRunWidgets ();
            }
        }

        public void runStateChanged(DisplayedGemStateEvent e) {
        }

        /**
         * If the gem we are supposed to run is removed then automatically move
         * to the next runnable gem if there is only one runnable gem on the table top.
         */                   
        public void gemRemoved(GemGraphRemovalEvent e) {
           
            Gem removedGem = (Gem) e.getSource ();

            if (removedGem.equals (currentGemToRun)) {
                selectNewGem();
            } else {
                updateRunWidgets();
            }
        }

        /**
         * If a new gem is added and there are no other gems to run then make
         * it the default gem to run.
         */
        public void gemAdded(GemGraphAdditionEvent e) {
            Gem addedGem = (Gem) e.getSource ();

            if (currentGemToRun == null && addedGem.isRunnable() &&
                getTableTop().getGemGraph().getRunnableGems().size() == 1) {

                currentGemToRun = addedGem;
                currentGemToRun.addNameChangeListener(this);                           
                       
                // updateRunWidgets depends on the DisplayedGem of the currentGemToRun
                // to be available. The gemAdded event gets delivered before the DisplayedGem
                // is ready. Therefore we invoke updateRunWidgets later, once the DisplayedGem
                // is actually on the TableTop.
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        updateRunWidgets ();
                    }
                });
            }
        }

        /**
         * Check if the current gem is still runnable.
         * Or if another gem became runnable then make it the current gem.
         */
        public void gemConnected(GemGraphConnectionEvent e) {
                   
            if ((currentGemToRun != null && !currentGemToRun.isRunnable()) || currentGemToRun == null) {
                selectNewGem();
            }
        }

        /**
         * Check if the current gem is still runnable.
         * Or if another gem became runnable then make it the current gem.
         */                   
        public void gemDisconnected(GemGraphDisconnectionEvent e) {
           
            if ((currentGemToRun != null && !currentGemToRun.isRunnable()) || currentGemToRun == null) {
                selectNewGem();
            }
        }
       
        /**
         * Selects a new Gem to run if a default selection can be made.
         */
        public void selectNewGem() {
           
            if (currentGemToRun != null) {
                currentGemToRun.removeNameChangeListener(this);
                currentGemToRun = null;
            }
           
            GemGraph gemGraph = getTableTop().getGemGraph();                  
            if (gemGraph.getRunnableGems().size() == 1) {
                currentGemToRun = gemGraph.getRunnableGems().iterator().next();
                currentGemToRun.addNameChangeListener(this);
            }
           
            updateRunWidgets();
        }
       
        /**
         * Sets a new gem as the current gem to run.
         * @param newGemToRun the new gem
         */
        public void setGem(Gem newGemToRun) {
            if (currentGemToRun != null) {
                currentGemToRun.removeNameChangeListener(this);
            }
           
            currentGemToRun = newGemToRun;
            currentGemToRun.addNameChangeListener(this);
            updateRunWidgets();
        }
       
        /**
         * Updates the actions and tooltips for the run button and menu items
         * depending on which gem should be run.
         */
        private void updateRunWidgets() {

            // Enable the drop down action if there is any runnable gem or collector.       
            GemGraph gemGraph = getTableTop().getGemGraph();
            boolean runnableGemExists = gemGraph.getRunnableGems().size() > 0 || gemGraph.getCollectors().size() > 0;
            getRunAction().setEnabled(runnableGemExists);
            getRunDropDownAction().setEnabled(runnableGemExists);
            getRunSubMenu().setEnabled(runnableGemExists);
       
            // Update the tooltips and the menu item. The menu item is only
            // enabled if a specific gem to run is selected.
            DisplayedGem displayedGem = getTableTop().getDisplayedGem(currentGemToRun);
            if (displayedGem != null && currentGemToRun.isRunnable()) {
                getRunMenuItem().setEnabled(true);
                getRunMenuItem().setText(getResourceString("RunGem") + " " + displayedGem.getDisplayText());
                getRunMenuItem().setToolTipText(getResourceString("RunGemToolTip") + " " + displayedGem.getDisplayText());
                getRunButton().setToolTipText(getResourceString("RunGemToolTip") + " " + displayedGem.getDisplayText());
            } else {
                getRunMenuItem().setEnabled(false);
                getRunMenuItem().setText(getResourceString("RunGem"));
                getRunMenuItem().setToolTipText(getResourceString("RunGemToolTip"));
                getRunButton().setToolTipText(getResourceString("RunGemToolTip"));                   
            }
           
        }       
    }       

    /**
     * Mouse listener for toolbar buttons that makes sure that both buttons in
     * a drop down button group receive the raised border during the rollover effect.
     */
    private class ToolBarButtonMouseListener extends MouseAdapter {

        /** The toolbar button this listener is attached to. */
        private final JButton toolBarButton;
       
        /** True if the toolbar button is the child in a drop down button arrangement. */
        private boolean isDropChild = false;
       
        /** True if the toolbar button is the parent in a drop down button arrangement. */
        private boolean isDropParent = false;


        public ToolBarButtonMouseListener (JButton toolBarButton) {
            this.toolBarButton = toolBarButton;
           
            //check if this button is a drop parent or child.            
           
            Object value = toolBarButton.getAction().getValue(ACTION_BUTTON_IS_DROP_CHILD_KEY);
            if (value != null) {
                isDropChild = ((Boolean)value).booleanValue();
            }
           
            value = toolBarButton.getAction().getValue(ACTION_BUTTON_IS_DROP_PARENT_KEY);
            if (value != null) {
                isDropParent = ((Boolean)value).booleanValue();
            }          
        }
           
        public void mouseEntered(MouseEvent e) {

            if (!toolBarButton.isEnabled()) {
                return;
            }
              
            if (isDropChild) {           
                // If this button is a drop child make sure the parent button to
                // its left gets the raised border.
               
                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton parent = (JButton) getToolBarPane().getComponentAtIndex(index - 1);
                parent.getModel().setRollover(true);

            } else if (isDropParent) {
                // If this button is a drop parent make sure the child button to
                // its right gets the raised border.
               
                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton child = (JButton) getToolBarPane().getComponentAtIndex(index + 1);
                child.getModel().setRollover(true);
            }    
        }

        public void mouseExited(MouseEvent e) {

            if (!toolBarButton.isEnabled()) {
                return;
            }
       
            if (isDropChild) {
                // Undo the raised border for the parent.

                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton parent = (JButton) getToolBarPane().getComponentAtIndex(index - 1);
                parent.getModel().setRollover(false);

            } else if (isDropParent) {
                // Undo the raised border for the child.

                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton child = (JButton) getToolBarPane().getComponentAtIndex(index + 1);
                child.getModel().setRollover(false);
            }
        }
       
        public void mouseClicked(MouseEvent e) {
           
            if (!toolBarButton.isEnabled()) {
                return;
            }
       
            // If this button is clicked make sure it looses its rollover
            // raised border. Sometimes Swing doesn't do this right.
            toolBarButton.getModel().setRollover(false);
       
            if (isDropChild) {
                // Make sure the parent also looses its border.

                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton parent = (JButton) getToolBarPane().getComponentAtIndex(index - 1);
                parent.getModel().setRollover(false);

            } else if (isDropParent) {
                // Make sure the child also looses its border.

                int index = getToolBarPane().getComponentIndex(toolBarButton);
                JButton child = (JButton) getToolBarPane().getComponentAtIndex(index + 1);
                child.getModel().setRollover(false);
            }
        }
    }

    /**
     * A listener for a popup menu with run gem items.  The listener handles both menu item and popup menu
     * events.  The existing selected gems will be cleared when the PlayButton popup is opened and then reset
     * when the popup closes.  As menu items are highlighted by the user, the respective Gem will be selected and
     * brought into view on the Table Top.
     * @author Steve Norton
     */
    private class RunDropDownMenuListener implements ActionListener, ChangeListener, PopupMenuListener {
       
        /* Due to unfortunate timing when the popup menu is created, the first menu item does not
         * automatically cause its associated Gem to be selected and brought into view.
         *
         * What Happens:  The first menu item is made the default selection for the menu (so the appropriate
         * gem is highlighted and brought into view) and then popupMenuWillBecomeVisible() is called
         * (which clears all selections from the table top). 
         *
         * Workaround:  With knowledge of the default gem, popupMenuWillBecomeVisible() can reselect the appropriate
         * gem after clearing all the selections.
         */
        
        /** Gems that were selected when the menu was opened. */
        private DisplayedGem[] selectedGems;
       
        /** Gem that had focus when the menu was opened. */
        private DisplayedGem focusedGem;
       
        /** View before the menu was opened - restore this view when menu is canceled. */
        private Rectangle originalView;
       
        /** Automatically selects this gem when menu is opened. */
        private DisplayedGem defaultGem;
       
        /** Keep track of the gems to run when a menu item is clicked. */
        private final Map<JMenuItem, DisplayedGem> menuItemToGemMap = new HashMap<JMenuItem, DisplayedGem>();
       
        /** If true the default gem will be focused as soon as the menu becomes visible. */
        private boolean focusOnPopup;

        /**
         * Constructor for the RunDropDownMenuListener.
         */
        RunDropDownMenuListener() {
            defaultGem = null;
            focusOnPopup = true;
        }

        public void setFocusOnPopup (boolean focusOnPopup) {
            this.focusOnPopup = focusOnPopup;
        }
       
        /**
         * Tell the listener which Gem to run when the JMenuItem is selected.
         * NOTE: this should be called whenever the listener is added to a JMenuItem.
         * @param menuItem JMenuItem - a menu item in the Popup Menu
         * @param gem DisplayedGem - the Gem to run when the associated menu item is selected
         */
        public void addMenuItem(JMenuItem menuItem, DisplayedGem gem) {
            // If the menuItemToGemMap is empty (this is the first item in the menu) and the menu item is
            // enabled then set the default gem to be this gem
            if (menuItemToGemMap.isEmpty() && menuItem.isEnabled()) {
                defaultGem = gem;
            }
           
            // Add the menuItem and gem combo to the map
            menuItemToGemMap.put(menuItem, gem);
        }
       
        /**
         * Determines if the popup menu is being closed because of a cancellation.
         * This is a BIG TIME HACK (that seems to work!) to work around the problem of
         * popupMenuCanceled() not being called.
         * @return boolean
         */
        private boolean isPopupCanceled() {
            EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
            AWTEvent event = queue.peekEvent();
           
            if (event == null) {
                return true;
            } else {
                if (event instanceof InvocationEvent) {
                    return true;
                } else if (event instanceof KeyEvent) {
                    if (((KeyEvent)event).getKeyChar() == 27) {
                        return true;
                    }
                }
            }
           
            return false;
        }
       
        /*
         * The following functions satisfy the PopupMenuListener interface
         */
       
        /**
         * Called just before the popup menu is displayed to the user
         * @param e PopupMenuEvent
         */
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            TableTop tableTop = getTableTop();

            // Remember which gem had focus, which were selected and which part of the TableTop was viewable
            // when the popup opened
            selectedGems = tableTop.getSelectedDisplayedGems();
            focusedGem = tableTop.getFocusedDisplayedGem();
            tableTop.setFocusedDisplayedGem(null);
            originalView = getTableTopPanel().getVisibleRect();

            // Clear any gems that are currently selected
            tableTop.selectDisplayedGem(null, false);

            // If a default gem was provided then select it and make sure the user can see it
            if (defaultGem != null && focusOnPopup) {
                tableTop.selectDisplayedGem(defaultGem, true);
                getTableTopPanel().scrollRectToVisible(defaultGem.getBounds());  
            }
        }
       
        /**
         * Called just before the popup menu is removed from the screen, whether a menu item
         * was selected or the menu was canceled.
         * @param e PopupMenuEvent
         */
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {

            TableTop tableTop = getTableTop();

            // Restore the focused gem and re-select all the gems that were selected
            // before the popup was opened
            for (int i = 0; selectedGems != null && i < selectedGems.length; i++) {
                tableTop.selectDisplayedGem(selectedGems[i], false);
            }

            tableTop.setFocusedDisplayedGem(focusedGem);

            // Restore the original view if the popup menu was canceled
            if (isPopupCanceled() && originalView != null) {
                getTableTopPanel().scrollRectToVisible(originalView);
            }          
        }
       
        /**
         * Called when the popup menu has been canceled and is being removed from the screen
         * NOTE: this function does not ACTUALLY get called in java 1.3.1
         * @param e PopupMenuEvent
         */
        public void popupMenuCanceled(PopupMenuEvent e) {
            // nothing
        }
       
        /*
         * The following function satisfies the ActionListener interface
         */
        
        /**
         * Called when a popup menu item is clicked and runs the appropriate gem
         * @param e ActionEvent
         */
        public void actionPerformed(ActionEvent e){
            runTarget(menuItemToGemMap.get(e.getSource()));
        }
       
        /*
         * The following function satisfies the ChangeListener interface
         */
        
        /**
         * Called whenever the state of a menu item changes.  Selects/highlights the appropriate
         * gem on the TableTop and brings it into view when a menu item is highlighted.
         * @param e ChangeEvent
         */
        public void stateChanged(ChangeEvent e) {
            JMenuItem source = (JMenuItem)e.getSource();
           
            // Only perform the selections when the popup menu is actually
            // visible to the user
            if (source.isShowing()) {
                if (source.isArmed()) {
                    // Select the gem whose menu item has been highlighted and make sure it is visible to the user
                    DisplayedGem gem = menuItemToGemMap.get(e.getSource());
                    if (gem != null) {
                        getTableTop().selectDisplayedGem(gem, true);
                        getTableTopPanel().scrollRectToVisible(gem.getBounds());
                    }
                } else {
                    // Deselect the gem since its menu item lost highlighting
                    getTableTop().selectDisplayedGem(null,false);   
                }
            }
        }
    }
   
    /**
     * A class to handle where and how the output frame is displayed.
     * @author Edward Lam
     */
    private class OutputDisplayManager {
        private final Map<DisplayedGem, GemResultDisplayer> displayedGemToResultDisplayerMap;

        /**
         * A class to handle result display for individual displayed gems.
         * @author Edward Lam
         */
        private class GemResultDisplayer extends ResultValueDisplayer {
            /** The displayed gem for which this manager displays output */
            private final DisplayedGem displayedGem;

            /**
             * Constructor for a GemResultDisplayer.
             * @param displayedGem the displayed gem for which this manager displays output
             */
            GemResultDisplayer(DisplayedGem displayedGem) {
                super(GemCutter.this);
                this.displayedGem = displayedGem;
            }

            /**
             * @see ResultValueDisplayer#showResultFrame(ValueEditor, String, String)
             */
            public JInternalFrame showResultFrame(ValueEditor resultEditor,
                                                  String messageTitle,
                                                  String errorMessage) {
                // Put the target name at the start of the title.
                StringBuilder frameTitleBuffer = new StringBuilder();
                frameTitleBuffer.append(displayedGem.getDisplayText());
                frameTitleBuffer.append(":");
                if (messageTitle != null && !messageTitle.trim().equals("")) {
                    frameTitleBuffer.append(" " + messageTitle);
                }

                return super.showResultFrame(resultEditor, frameTitleBuffer.toString(), errorMessage);
            }

            /**
             * @see javax.swing.event.InternalFrameListener#internalFrameClosed(javax.swing.event.InternalFrameEvent)
             */
            public void internalFrameClosed(InternalFrameEvent e) {
                super.internalFrameClosed(e);

                // if this is the last open frame for this target, remove this manager from the targetToDisplayManager map
                // (to prevent holding on to a reference to a deleted target)
                if (getOpenFrameSet().isEmpty()) {
                    displayedGemToResultDisplayerMap.remove(displayedGem);
                }
            }

            /**
             * @see ResultValueDisplayer#getTargetBounds()
             */
            protected Rectangle getTargetBounds() {
                // Convert the tabletop coordinates to frame coordinates.
                return SwingUtilities.convertRectangle(getTableTopPanel(), displayedGem.getBounds(), getLayeredPane());
            }
        }

        /**
         * Constructor for an OutputDisplayManager.
         */
        OutputDisplayManager() {
            displayedGemToResultDisplayerMap = new HashMap<DisplayedGem, GemResultDisplayer>();
        }
        /**
         * Close all open result frames.
         */
        private void closeAllResultFrames() {
            // iterate over all result displayers.
            // Note that closing result frames will cause the displayer to remove itself from the map
            // and thus would cause a concurrent modification exception if iterating over values() directly.
            Set<GemResultDisplayer> displayManagers = new HashSet<GemResultDisplayer>(displayedGemToResultDisplayerMap.values());
            for (final GemResultDisplayer trm : displayManagers) {
                trm.closeAllResultFrames();
            }
        }
        /**
         * Show the result frame for a target.
         * @param resultEditor the value editor to display
         * @param displayedGem the displayed gem  for which to show the result
         * @param messageTitle the title of the message to display
         * @param errorMessage an error message to display
         */
        JInternalFrame showResultFrame(ValueEditor resultEditor, DisplayedGem displayedGem, String messageTitle, String errorMessage){
            GemResultDisplayer trm = displayedGemToResultDisplayerMap.get(displayedGem);
            if (trm == null) {
                trm = new GemResultDisplayer(displayedGem);
                displayedGemToResultDisplayerMap.put(displayedGem, trm);
            }
           
            return trm.showResultFrame(resultEditor, messageTitle, errorMessage);
           
        }
    }
    /**
     * Class to handle displaying status messages.
     * @author Edward Lam
     */
    final class StatusMessageManager implements StatusMessageDisplayer {

        /** List of messages requested, ordered from earlier to later */
        private final List<String> requestedMessageList;

        /** Map from requestor to the message it is currently requesting be shown */
        private final Map<Object, String> requestorToMessageMap;
       
        /** The message expiry handler.  Non-null if a deferential or transient message is currently displayed. */
        private MessageExpiryHandler messageExpiryHandler;

        /**
         * Class to handle expiry of status messages.
         * This class holds info for deferential or transient messages.
         * @author Edward Lam
         */
        private class MessageExpiryHandler implements ActionListener {
            /** The object requesting the message to be displayed */
            private final Object requestor;

            /** The message held by this handler */
            private final String message;
           
            /** The timer that fires when the message should be expired. */
            private final Timer timer = new Timer(STATUS_MESSAGE_TIME, this);

            /**
             * Constructor for a message expiry handler
             * @param requestor Object the object requesting the message to clear.
             * @param message String the message to expire
             * @param timed boolean whether the message expires with a timer
             */
            MessageExpiryHandler(Object requestor, String message, boolean timed) {
                this.requestor = requestor;
                this.message = message;

                if (timed) {
                    // start the expiry timer
                    timer.setRepeats(false);
                    timer.start();
                }
            }
            /**
             * Get the message that's expiring.
             * @return the message to expire
             */
            String getMessage() {
                return message;
            }
            /**
             * Get the requestor for the message that's expiring.
             * @return Object the object requesting the message to be displayed
             */
            Object getRequestor() {
                return requestor;
            }

            /** Get the timer that fires when the message should be expired.
             * @return Timer
             */           
            Timer getTimer() {
                return timer;
            }
           
            /**
             * Called when the timer expires
             * @param ae ActionEvent the event fired upon timer expiry
             */
            public void actionPerformed(ActionEvent ae) {
                // expire the message
                displayMessage(requestor, message, null, false);
            }
        }

        /**
         * Constructor for a Status Message Manager
         */
        StatusMessageManager() {
            // use a nice synchronized vector
            requestedMessageList = new Vector<String>();

            requestorToMessageMap = new HashMap<Object, String>();
           
            messageExpiryHandler = null;
        }
        /**
         * {@inheritDoc}
         */
        public void clearMessage(Object requestor){
            displayMessage(requestor, null, null, false);
        }
        /**
         * {@inheritDoc}
         */
        public void setMessage(Object requestor, String message, MessageType messageType) {
            // display the string..
            displayMessage(requestor, message, messageType, true);
        }
        /**
         * {@inheritDoc}
         */
        public void setMessageFromResource(Object requestor, String resourceName, MessageType messageType){

            setMessageFromResource(requestor, resourceName, null, messageType);
        }
        /**
         * {@inheritDoc}
         */
        public void setMessageFromResource(Object requestor, String resourceName, String resourceArgument, MessageType messageType){

            String displayString = resourceArgument == null ? getResourceString(resourceName) : getResourceString(resourceName, resourceArgument);

            // display the string..
            displayMessage(requestor, displayString, messageType, true);
        }
        /**
         * Set or clear the status message on the status bar.
         * @param requestor Object the object requesting the message to clear.
         * @param messageString String the message to set or clear (Null always clears the message)
         * @param messageType the message type.  Ignored if clearing the message.
         * @param set boolean true to set the message, false to clear it.
         */
        private synchronized void displayMessage(Object requestor, String messageString, MessageType messageType, boolean set) {
            JLabel statusLabel = getStatusMsg2();
       
            if (!set || messageString == null) {
                // Clear the message

                // get the previous message requested by this object
                String oldMessage = requestorToMessageMap.get(requestor);

                // check for clearing a message held by the expiry handler
                if (messageExpiryHandler != null && messageExpiryHandler.getMessage() == oldMessage) {
                    // the handler can go away now
                    messageExpiryHandler = null;
                }

                // check for the case where a message to be removed is gone already
                if (messageString != null && !messageString.equals(oldMessage)) {
                    return;
                }
               
                // remove the message
                requestorToMessageMap.remove(requestor);
                requestedMessageList.remove(oldMessage);

                // display the next best message
                if (requestedMessageList.isEmpty()) {
                    // No status left.  Clear the status message.
                    statusLabel.setText("");

                } else {
                    // Display the new latest message
                    statusLabel.setText(requestedMessageList.get(0));
                }

            } else {
                // Set the message

                // get the previous message requested by this object
                String oldMessage = requestorToMessageMap.remove(requestor);

                // if there's a message held by the expiry handler, the message should no longer be displayed
                if (messageExpiryHandler != null) {
                    requestorToMessageMap.remove(messageExpiryHandler.getRequestor());
                    requestedMessageList.remove(messageExpiryHandler.getMessage());
                    messageExpiryHandler.getTimer().stop();
                }

                // if there's an old message, remove it from the list of messages to display
                if (oldMessage != null) {
                    requestedMessageList.remove(oldMessage);
                }

                // update new message info
                requestorToMessageMap.put(requestor, messageString);
                requestedMessageList.add(0, messageString);

                // display the message
                statusLabel.setText(messageString);
               
                // if timed, set a handler to expire the message
                if (messageType == MessageType.TRANSIENT) {
                    messageExpiryHandler = new MessageExpiryHandler(requestor, messageString, true);

                } else if (messageType == MessageType.DEFERENTIAL) {
                    messageExpiryHandler = new MessageExpiryHandler(requestor, messageString, false);
                }
            }
        }
    }

    /**
     * A progress monitor for the export to CAL archive actions.
     *
     * @author Joseph Wong
     */
    private final class ExportCarProgressMonitor implements CarBuilder.Monitor {
       
        /** The modal progress monitor dialog that pops up. */
        private final ModalProgressMonitor progressMonitor;

        /** The timestamp at the start of the operation, in milliseconds. */
        private long startTime;

        /** The index of the current Car out of all the Cars in the job. */
        private int currentCar;

        /** The total number of Cars in the job. */
        private int nCarsInJob;
       
        /** Constructs a ExportCarProgressMonitor. */
        private ExportCarProgressMonitor() {
            progressMonitor = new ModalProgressMonitor(GemCutter.this, null, 3, 0, 10);
            progressMonitor.setPreferredWidth(480);
            currentCar = 0;
            nCarsInJob = 1;
        }

        /**
         * {@inheritDoc}
         */
        public boolean isCanceled() {
            return progressMonitor.isCanceled();
        }

        /**
         * {@inheritDoc}
         */
        public void showMessages(String[] messages) {
           
            StringBuilder details = new StringBuilder();
            for (final String message : messages) {
                details.append(message).append('\n');
            }
           
            DetailsDialog dialog = new DetailsDialog(
                GemCutter.this,
                getResourceString("ExportCarMessagesDialogTitle"),
                getResourceString("ExportCarMessagesDialogMessage"),
                details.toString(),
                DetailsDialog.MessageType.INFORMATION);
           
            dialog.doModal();
        }
       
        /**
         * {@inheritDoc}
         */
        public void operationStarted(int nCars, final int nTotalModules) {
            startTime = System.currentTimeMillis();
            nCarsInJob = nCars;
           
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    progressMonitor.setMaximum(nTotalModules);
                }
            });
        }

        /**
         * {@inheritDoc}
         */
        public void carBuildingStarted(String carName, int nModules) {
            currentCar++;
            if (nCarsInJob > 1) {
                progressMonitor.setTitle(GemCutterMessages.getString("ExportingCarXOfY", new Integer(currentCar), new Integer(nCarsInJob)));
            } else {
                progressMonitor.setTitle(getResourceString("ExportingCar", carName));
            }
        }

        /**
         * {@inheritDoc}
         */
        public void processingModule(final String carName, final ModuleName moduleName) {
            final long elapsedTime = System.currentTimeMillis() - startTime;
           
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    // Update the progress bar
                    progressMonitor.incrementProgress();
                   
                    // Estimate the time remaining given the elapsed time and the progress made does far
                    int currentProgress = progressMonitor.getProgress();
                    int progressToGo = Math.max(0, progressMonitor.getMaximum() - currentProgress);
                   
                    long estimatedTimeLeft = (elapsedTime * progressToGo) / currentProgress;
                   
                    progressMonitor.setMessage(0, GemCutterMessages.getString("ExportingModuleToCar", moduleName, carName));
                    progressMonitor.setMessage(1, GemCutterMessages.getString("ExportWorkspaceToCarElapsedTime", getTimeString(elapsedTime)));
                    progressMonitor.setMessage(2, GemCutterMessages.getString("ExportWorkspaceToCarEstimatedTimeLeft", getTimeString(estimatedTimeLeft)));
                }
            });
        }

        /**
         * Formats the duration, given in milliseconds, into a nice string.
         * @param durationInMilliseconds
         * @return a formatted string for the time duration.
         */
        private String getTimeString(long durationInMilliseconds) {
            // Formats the given duration as either "x min y sec" or just "y sec". Values over
            // 1 hour are displayed with x > 60.
           
            long durationInSeconds = durationInMilliseconds / 1000L;
            long secondsComponent = durationInSeconds % 60L;
            long minutesComponent = durationInSeconds / 60L;
           
            if (minutesComponent == 0L) {
                return GemCutterMessages.getString("ExportWorkspaceToCarSecondsOnly", new Long(secondsComponent));
            } else {
                return GemCutterMessages.getString("ExportWorkspaceToCarMinutesAndSeconds", new Long(minutesComponent), new Long(secondsComponent));
            }
        }

        /**
         * {@inheritDoc}
         */
        public void carBuildingDone(String carName) {}

        /**
         * {@inheritDoc}
         */
        public void operationDone() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    progressMonitor.done();
                }
            });
        }

        /**
         * Shows the modal progress monitor dialog.
         */
        void showDialog() {
            progressMonitor.showDialog();
        }
    }

    /**
     * GUI state enum pattern.
     * @author Edward Lam
     */
    public static final class GUIState {
        private final String typeString;

        private GUIState(String s) {
            typeString = s;
        }
        public String toString() {
            return typeString;
        }

        /** Edit mode. */
        public static final GUIState EDIT = new GUIState("EDIT");
       
        /** Run mode. */
        public static final GUIState RUN = new GUIState("RUN");
       
        /** Lock down mode. All controls that modify the workspace/tabletop are disabled. */
        public static final GUIState LOCKED_DOWN = new GUIState("LOCKED_DOWN");
       
        /** Add gem mode. */
        public static final GUIState ADD_GEM = new GUIState("ADD_GEM");
    }

    /**
     * Starts the application.
     * The following system properties can be used:
     *   org.openquark.gems.client.gemcutter.workspace - the path to a workspace file to use
     * @param args an array of command-line arguments.
     */
    public static void main(String[] args) {
        appMain(args, DEFAULT_WORKSPACE_FILE);
    }
   
    /**
     * Starts the application.
     * The following system properties can be used:
     *   org.openquark.gems.client.gemcutter.workspace - the path to a workspace file to use
     * @param args an array of command-line arguments.
     * @param defaultWorkspaceFile the name of the workspace file to fallback onto if no stream provider is supplied and no system properties are defined.
     */
    public static void appMain(String[] args, String defaultWorkspaceFile) {
        try {
            // Setup a logger to intercept messages from our code.
            // and prevent them from being sent to the root logger.
            Logger bobjLogger = Logger.getLogger("org.openquark");
            bobjLogger.setLevel(Level.FINE);
            bobjLogger.setUseParentHandlers(false);
            bobjLogger.addHandler(new SimpleConsoleHandler());

            // WORKAROUND: The following is a hack around the behaviour of the ToolTipManager for Java 1.4 (and potentially earlier)
            // where a tooltip is forcefully repositioned to not extend pass the lower-right corner of its parent.
            // This may cause an intellicut panel entry tooltip to partially/completely cover the entry itself.
            //
            // todo-jowong remove this when the codebase moves to Java 5
            {
                final ToolTipManager tooltipManager = ToolTipManager.sharedInstance();
               
                try {
                    // Note that this field is declared as protected in ToolTipManager, but has been left unused
                    // since Java 5. In Java 1.4, the erroneous bounds calculation code is executed in showTipWindow()
                    // if and only if this field is left in its default value of 'false'. Besides this, the field
                    // controls no other piece of logic in the class.
                   
                    // We change its value to 'true' via reflection.
                    final Field field = ToolTipManager.class.getDeclaredField("heavyWeightPopupEnabled");
                    final boolean isAccessible = field.isAccessible(); // remember the accessibility of the field before the changes
                    field.setAccessible(true);
                    field.set(tooltipManager, Boolean.TRUE);
                    field.setAccessible(isAccessible); // restore the accessibility of the field
                   
                } catch (SecurityException e) {
                } catch (NoSuchFieldException e) {
                } catch (IllegalArgumentException e) {
                } catch (IllegalAccessException e) {
                }
            }

            // Set native look and feel ...
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
   
            // Make stuff even more like Windows
            if (UIManager.getSystemLookAndFeelClassName().endsWith("WindowsLookAndFeel")) {
                Font systemPlain11font = new Font("Dialog", Font.PLAIN, 11);
                //set menu fonts
                UIManager.put("Menu.font", systemPlain11font);
                UIManager.put("MenuItem.font", systemPlain11font);
                UIManager.put("CheckBoxMenuItem.font", systemPlain11font);
                UIManager.put("RadioButtonMenuItem.font", systemPlain11font);
   
                //set fonts for buttons & check boxes
                UIManager.put("Button.font", systemPlain11font);
                UIManager.put("RadioButton.font", systemPlain11font);
                UIManager.put("ToggleButton.font", systemPlain11font);
                UIManager.put("CheckBox.font", systemPlain11font);
   
                //set fonts for text
                UIManager.put("Label.font", systemPlain11font);
                UIManager.put("TabbedPane.font", systemPlain11font);
                UIManager.put("List.font", systemPlain11font);
                UIManager.put("Tree.font", systemPlain11font);
                UIManager.put("TextField.font", systemPlain11font);
                UIManager.put("TextArea.font", systemPlain11font);
                UIManager.put("PasswordField.font", systemPlain11font);
                UIManager.put("ComboBox.font", systemPlain11font);
                UIManager.put("Slider.font", systemPlain11font);
                UIManager.put("ToolTip.font", systemPlain11font);
               
                // Avoid the "Grey fog" which appears when painting is delayed.
                //   Instead, the window painting will appear stuck (like regular Windows windows).
                //   Also, the frame is resized dynamically (also like Windows windows).
                // Ref: http://forums.java.sun.com/thread.jspa?forumID=57&messageID=2387354&threadID=503874
                System.setProperty("sun.awt.noerasebackground", "true");
                Toolkit.getDefaultToolkit().setDynamicLayout(true);
            }
           
            /* Create the frame */
            GemCutter aGemCutter = new GemCutter(null, defaultWorkspaceFile);
           
            // set workspace name in Gem Browser to include name of workspace file
            aGemCutter.getGemBrowser().setWorkspaceNodeName(defaultWorkspaceFile);
  
            /* Create and center the splash screen */
            aGemCutter.gemCutterSplashScreen = new GemCutterSplashScreen(aGemCutter);
            aGemCutter.gemCutterSplashScreen.pack();
            centerWindow(aGemCutter.gemCutterSplashScreen);
           
            // Find number of modules to be loaded and pass this information to progress bar
            int nModules = aGemCutter.getWorkspace().getModuleNames().length;
            aGemCutter.gemCutterSplashScreen.setProgressBarMaxValue(nModules);
  
            // the splash screen is displayed on top of the gem cutter
            aGemCutter.gemCutterSplashScreen.toFront();
  
            // Setup the window size from the preferences.
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
           
            Dimension defaultSize = new Dimension((int) (screenSize.width * 0.8),
                                                  (int) (screenSize.height * 0.8));
           
            Point defaultLocation = new Point((screenSize.width - defaultSize.width) / 2,
                                              (screenSize.height - defaultSize.height) / 2);
           
            PreferencesHelper.getFrameProperties(getPreferences(), WINDOW_PROPERTIES_PREF_KEY, aGemCutter, defaultSize, defaultLocation);
  
            // get the display time
            long splashScreenAppearTime = System.currentTimeMillis();
  
            // disable input to the gemcutter while splashing
            aGemCutter.setEnabled(false);
  
            try {
                // set the screens to be visible   
                aGemCutter.gemCutterSplashScreen.setVisible(true);
                aGemCutter.setVisible(true);
    
                // We want the repaint to be done before we continue. or else the repaints may not occur
                // until the compile is already done - and we won't see the (quasi)real-time compile status!
                // Note how we still have to provide an empty run method - the default Thread constructor
                // doesn't create a runnable object
                SwingUtilities.invokeAndWait(new Thread() {public void run() {}});
    
                // Initialize the CAL compiler
                aGemCutter.initCompile();

                // Set up the runners
                try{
                    aGemCutter.initRunners();
                } catch (ValueEntryException exception) {
                    JOptionPane.showMessageDialog(aGemCutter, exception.getMessage() + "\nCause: " + exception.getCause().getMessage() + "\n\nGemCutter will shut-down.",
                            "Error in initializing the value entry handlers:", JOptionPane.ERROR_MESSAGE);
                    return;
                }  

                // splash for a minimum of 3 seconds
                long currentTime = System.currentTimeMillis();
                long timeDif = currentTime - splashScreenAppearTime;
                if (timeDif < 3000) {
                    try {
                        Thread.sleep(3000 - timeDif);
                    } catch (InterruptedException ie) {}
                }
  
            } catch (Throwable exception) {
                JOptionPane.showMessageDialog(aGemCutter, "An error was encountered during initialization.\nSee console for details.",
                        "Initialization Error.", JOptionPane.ERROR_MESSAGE);
                throw exception;

            } finally {
                // make the splashing stop, and re-enable the gemCutter
                aGemCutter.setEnabled(true);
                aGemCutter.gemCutterSplashScreen.dispose();
                aGemCutter.gemCutterSplashScreen = null;
            }
  
            aGemCutter.getTableTop().resetTargetForNewTableTop();
            aGemCutter.updateWindowTitle();
           
            // (Java bug workaround) ensure JFileChooser will load..
            ensureJFileChooserLoadable();
                       
        } catch (Throwable exception) {
            System.err.println("Exception occurred in main() of GemCutter");
            exception.printStackTrace(System.out);
        }
    }

    /**
     * GemCutter constructor.
     * @param workspaceDeclarationStreamProvider the stream provider of the workspace declaration to use, or
     *          null to proceed with standard processing (using system properties).
     * @param defaultWorkspaceFile the name of the workspace file to fallback onto if no stream provider is supplied and no system properties are defined.
     */
    public GemCutter(WorkspaceDeclaration.StreamProvider workspaceDeclarationStreamProvider, String defaultWorkspaceFile) {
        try {
            // Save the window size if the GemCutter is closed.
            addWindowListener(new WindowAdapter() {
                public void windowClosed(WindowEvent e) {

                    // Do our best to ensure that any connection to CE is logged off.
                    if (enterpriseSupport != null) {
                        enterpriseSupport.ensureLoggedOff();
                    }

                    // Save the frame properties.
                    PreferencesHelper.putFrameProperties(getPreferences(), WINDOW_PROPERTIES_PREF_KEY, GemCutter.this);
                }
               
                public void windowClosing (WindowEvent e) {
                    // Prompt the user to save the tabletop when the window is being closed
                    if (promptSaveCurrentTableTopIfNonEmpty(null, null)) {
                        dispose();
                    }
                }
            });

            // Set the icon
            setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/Resources/gemcutter_16.gif")));
   
            // Create the type colour manager
            typeColours = new TypeColourManager();
           
            // Create an intellicut manager
            intellicutManager = new IntellicutManager(this);
           
            // Create the gem design manager
            persistenceManager = new GemCutterPersistenceManager(this);
           
            // Create a workspace manager
            String clientID = WorkspaceConfiguration.getDiscreteWorkspaceID(DEFAULT_WORKSPACE_CLIENT_ID);
            this.workspaceManager = WorkspaceManager.getWorkspaceManager(clientID);
            initWorkspace(workspaceDeclarationStreamProvider, defaultWorkspaceFile);

            // Reset the background from the preferences.
            resetBackground();

            // Set some window properties.
            setName("GemCutter");
            setJMenuBar(getGemCutterJMenuBar());
            setSize(460, 300);
            setTitle(getResourceString("WindowTitle"));
            setContentPane(getJFrameContentPane());
            // The close operation is handled by window listener
            setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

            // Enable running
            setTargetRunningButtons(false);
   
            // set the output display manager
            outputDisplayManager = new OutputDisplayManager();
   
            // set the message display manager
            statusMessageManager = new StatusMessageManager();
           
            navigationToolBar = new NavigationToolBar();   
               
            // set up the undo manager
            extendedUndoManager = new ExtendedUndoManager();

            extendedUndoManager.addUndoableEditListener(new UndoableEditListener() {

                public void undoableEditHappened(UndoableEditEvent uee) {
                    // update the undo buttons whenever a new undoable event is added to the manager.
                    updateUndoWidgets();
                }
            });
           
            // enable/disable undo/redo buttons
            updateUndoWidgets();
           
            // add the undo manager as a listener for undo events on the tabletop
            getTableTop().addUndoableEditListener(extendedUndoManager);

            // Set up the glass pane to handle mouse events when we're in "add gem" mode.  Intercepted mouse
            // presses that are not over the TableTop will cancel "add gem" mode. 
            // NOTE: mouse events aimed at a code editor are not aimed at the TableTop so "add gem" mode
            //       will be canceled.
            // NOTE: the glass pane should only be visible (and catching mouse events) when we're in
            //       "add gem" mode
            getGlassPane().addMouseListener(new MouseAdapter() {
               
                public void mousePressed(MouseEvent evt) {

                    // Make sure we're really in "add gem" mode
                    if (getGUIState() != GUIState.ADD_GEM) {
                        throw new Error("Programming error: glass pane should not be visible when in gui state " + getGUIState());
                    }
                   
                    // If the left mouse button was used then figure out which component the mouse was aimed at.
                    if (SwingUtilities.isLeftMouseButton(evt)) {
                        Point pressedPoint = SwingUtilities.convertPoint(getGlassPane(), evt.getPoint(), getLayeredPane());
                        Component pressedComp = SwingUtilities.getDeepestComponentAt(getLayeredPane(),
                                                                                    pressedPoint.x, pressedPoint.y);
                                                                                   
                        // If the mouse was aimed at the table top then forward the event
                        // to it so that the gem can be added.
                        if (pressedComp == getTableTopPanel() || pressedComp == getTableTopExplorer().getExplorerTree()) {
                            pressedComp.dispatchEvent(SwingUtilities.convertMouseEvent((Component)evt.getSource(), evt, pressedComp));
                            return;
                        }
                    }
                   
                    // If we get here then either the mouse press was with the right mouse button or
                    // the press was not on the TableTop so cancel "add gem" mode.
                    enterGUIState(GUIState.EDIT);
                }
            });
           
            // Set up the glass pane to handle mouse events when we're in "add gem" mode.  Intercepted mouse
            // presses that are not over the TableTop will cancel "add gem" mode. 
            // NOTE: mouse events aimed at a code editor are not aimed at the TableTop so "add gem" mode
            //       will be canceled.
            // NOTE: the glass pane should only be visible (and catching mouse events) when we're in
            //       "add gem" mode
            getGlassPane().addMouseMotionListener(new MouseMotionAdapter() {
                public void mouseMoved(MouseEvent e) {
                    TableTopExplorer tableTopExplorer = getTableTopExplorer();
                    Point pressedPoint = SwingUtilities.convertPoint(getGlassPane(), e.getPoint(), getLayeredPane());
                   
                    if (tableTopExplorer.getBounds().contains(SwingUtilities.convertPoint(getLayeredPane(), pressedPoint, getTableTopExplorer()))) {
                        tableTopExplorer.dispatchEvent(SwingUtilities.convertMouseEvent((Component)e.getSource(), e, tableTopExplorer));             
                    }
                }
            });

            // Create the adapter class for the navigator owner
            navigatorOwner = new NavigatorAdapter(this);
            navigatorOwner.addUndoableEditListener(extendedUndoManager);
            getGemBrowser().getBrowserTree().setNavigatorOwner(navigatorOwner);
           
            // Alter the timing so that tool tips stay open for a longer period of time (100 sec).
            ToolTipManager.sharedInstance().setDismissDelay(100000);
           
        } catch (Throwable ivjExc) {
            handleException(ivjExc);
        }
   
        // Ensure the status flag is set to 'ready'
        setStatusFlag(null);
       
        setFocusTraversalPolicy(new FocusTraversalPolicy() {
            public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
               
                if (aComponent == getTableTopPanel()) {
                    return getGemBrowserPanel();
                }
                return getTableTopPanel();
            }

            public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
               
                if (aComponent == getTableTopPanel()) {
                    return getGemBrowserPanel();
                }
                return getTableTopPanel();
            }

            public Component getDefaultComponent(Container focusCycleRoot) {
                return getTableTopPanel();
            }

            public Component getFirstComponent(Container focusCycleRoot) {
                return getTableTopPanel();
            }

            public Component getLastComponent(Container focusCycleRoot) {
                return getTableTopPanel();
            }
        });
    }

    /**
     * Initialize the workspace manager with the initial workspace contents.
     * No compilation will take place.
     * @param workspaceDeclarationStreamProvider the stream provider of the workspace declaration to use, or
     *          null to proceed with standard processing (using system properties).
     */
    private void initWorkspace(WorkspaceDeclaration.StreamProvider workspaceDeclarationStreamProvider) {
        initWorkspace(workspaceDeclarationStreamProvider, DEFAULT_WORKSPACE_FILE);
    }
   
    /**
     * Initialize the workspace manager with the initial workspace contents.
     * No compilation will take place.
     * @param workspaceDeclarationStreamProvider the stream provider of the workspace declaration to use, or
     *          null to proceed with standard processing (using system properties).
     * @param defaultWorkspaceFile the name of the workspace file (from the StandardVault) to fallback onto if
     *   no stream provider is supplied and no system properties are defined.
     */
    private void initWorkspace(WorkspaceDeclaration.StreamProvider workspaceDeclarationStreamProvider, String defaultWorkspaceFile) {
       
        // Register the EnterpriseVault.
        enterpriseSupport.registerEnterpriseVaultProvider(workspaceManager.getWorkspace());
       
        // Register the CE vault authenticator.
        enterpriseSupport.registerEnterpriseVaultAuthenticator(getWorkspace(), GemCutter.this);
       
        Status initStatus = new Status("Init status.");
       
        String defaultStandardVaultWorkspaceName = System.getProperty (GEMCUTTER_PROP_DEFAULT_STANDARD_VAULT_WORKSPACE, defaultWorkspaceFile);

        // Create a default workspace declaration provider if none is provided.
        if (workspaceDeclarationStreamProvider == null) {
            workspaceDeclarationStreamProvider =
                DefaultWorkspaceDeclarationProvider.getDefaultWorkspaceDeclarationProvider(GEMCUTTER_PROP_WORKSPACE_FILE, defaultStandardVaultWorkspaceName);
        }
       
        // Init the workspace.
        workspaceManager.initWorkspace(workspaceDeclarationStreamProvider, initStatus);
       
        // TODOEL how to handle this?
        // Ask the user if they would like to load a default workspace, or something..
        if (initStatus.getSeverity().compareTo(Status.Severity.WARNING) >= 0) {
            String title = getResourceString("WindowTitle");
            String message = "Problems were encountered loading the workspace.";
            String details = initStatus.getDebugMessage();
           
            DetailsDialog.MessageType messageType = initStatus.getSeverity() == Status.Severity.WARNING ?
                        DetailsDialog.MessageType.WARNING : DetailsDialog.MessageType.ERROR;
           
            DetailsDialog dialog = new DetailsDialog(this, title, message, details, messageType);
            dialog.doModal();
           
            if (initStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
                System.exit(-1);
            }
        }
    }
   
    /**
     * Initializes the CAL compiler by recompiling the specified module or all modules from
     * the workspace source provider. Compilation fails, an option dialog will be displayed
     * indicating the error.
     * @param dirtyModulesOnly if True, when compiling all modules will compile only modules modified since last successful compilation
     * @return True if errors were encountered; False otherwise
     */
    private boolean compileWorkspace(boolean dirtyModulesOnly) {
       
        // Create a listener that just sets status messages.
        // We no longer want detailed compilation status messages in status bar - these go in the splash screen now
        StatusListener listener = new StatusListener.StatusListenerAdapter() {
            public void setEntityStatus(StatusListener.Status.Entity entityStatus, String entityName) {
               
            }
            public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) {
                String resourceName = statusToPropertyKeyMap.get(moduleStatus);
               
                // update splash screen progress bar if module was loaded
                if (gemCutterSplashScreen != null && moduleStatus.equals(StatusListener.SM_LOADED)) {
                    gemCutterSplashScreen.increaseProgressBar(moduleName == null ? getResourceString(resourceName) : getResourceString(resourceName, moduleName));
                }
            }
        };

        // Set the status label to show that the GemCutter is initializing
        setStatusFlag(getResourceString("InitializingFlag"));

        // Compile, capturing the max error severity and the error to be displayed, if any
        boolean foundErrors = false;
        CompilerMessageLogger logger = new MessageLogger ();

        // Set to busy cursor, since compilation may take a while.
        Cursor oldCursor = getCursor();
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
       
        try {
            workspaceManager.compile(logger, dirtyModulesOnly, listener);

        } finally {
            // Reset the old cursor.
            setCursor(oldCursor);
        }
   
        CompilerMessage.Severity errSev = logger.getMaxSeverity();
        if (errSev.compareTo(CompilerMessage.Severity.ERROR) >= 0) {
            foundErrors = true;
           
            // A problem occurred

            // Notify displayed functional agent gems that their size may have changed
            // (displayed text may change, since the naming policy may decide to show(/hide?) module name)
            for (final DisplayedGem displayedGem : getTableTop().getDisplayedGems()) {
                if (displayedGem.getGem() instanceof FunctionalAgentGem) {
                    Rectangle oldBounds = displayedGem.getBounds();
                    displayedGem.sizeChanged();
                    getTableTopPanel().repaint(oldBounds);
                    getTableTopPanel().repaint(displayedGem.getBounds());
                }
            }

            // Tell the user something bad occurred.
            showCompilationErrors(logger, getResourceString("WorkspaceHadErrors"));
           
        } else if (errSev.compareTo(CompilerMessage.Severity.WARNING) >= 0) {
           
            // Warnings occurred
            // Tell the user something about the warnings.
            showCompilationErrors(logger, getResourceString("WorkspaceHadWarnings"));
        }

        getStatusMessageDisplayer().clearMessage(this);

        // Set status back to ready
        setStatusFlag(null);
       
        return foundErrors;
    }
   
    /**
     * Recompile one or all modules from the current source provider and adjust the GemCutter to reflect the new state
     * @param dirtyModulesOnly if True, when compiling all modules will compile only modules modified since last successful compilation
     */
    void recompileWorkspace(final boolean dirtyModulesOnly) {
    
        // This may take a while, so set the cursor.
        final Cursor oldCursor = getCursor();
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        getTableTopPanel().enableMouseEvents(false);

        // Keep the GUI locked while compiling.
        final GUIState oldGUIState = getGUIState();
        enterGUIState(GUIState.LOCKED_DOWN);
       
       
//        // Run the compile on its own thread, and then call into the AWT thread to update the UI.
//        Thread compileThread = new Thread("compile thread") {
//            public void run() {
                try {
                    long compileStartTime = System.currentTimeMillis();
                    boolean compileErrors = reinitCompile(dirtyModulesOnly);
                    String statusMessage;
                    if (!compileErrors) {
                        statusMessage = GemCutterMessages.getString("SM_RecompilationFinished", Double.toString((System.currentTimeMillis() - compileStartTime)/1000.0));               
                    } else {               
                        statusMessage = GemCutterMessages.getString("SM_RecompilationErrors");               
                    }
                    statusMessageManager.displayMessage(this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);
                   
                    try {
                        // Now that the supercombinators are regenerated, we should update the gem references.
                        getTableTop().getGemGraph().updateFunctionalAgentReferences(getWorkspace());
                       
                    } catch (GemEntityNotPresentException fanpe) {
                        // Couldn't reload the functional agent for one of the gems.
                        // Oh well, this is a reentrancy hack anyways - just display a dialog.
                        JOptionPane.showMessageDialog(GemCutter.this, "Error reloading functional agent: " + fanpe.getEntityName(),
                                "Reload error", JOptionPane.WARNING_MESSAGE);
                    }
                   
                    // TODOEL: remove all non-reloaded gems from the gem graph / tabletop.
                   
                } finally {
                    // Go back to our old cursor
                    setCursor(oldCursor);
                    getTableTopPanel().enableMouseEvents(true);
                   
                    enterGUIState(oldGUIState);
                }
//            }
//        };
//       
//        compileThread.start();
//       
//        // TEMP: some callers rely on the compilation having finished before returning.
//        try {
//            compileThread.join();
//        } catch (InterruptedException e) {
//        }
    }
   
    /**
     * Perform the reinitialization the CAL compiler.
     * This will update the compiler for the new state of the workspace from the current source provider.
     * @param dirtyModulesOnly if True, when compiling all modules will compile only modules modified since last successful compilation
     * @return True if errors were encountered; false otherwise
     */
    private boolean reinitCompile(boolean dirtyModulesOnly) {

        // Update the preferred working module if the user is working on something.
        if (getTableTop().getGemGraph().getGems().size() > 1) {
            preferredWorkingModuleName = getWorkingModuleName();
        }
       
        // Compile specified module or all modules in workspace
        boolean foundErrors = compileWorkspace(dirtyModulesOnly);

        // The new working module, if any.
        ModuleName newWorkingModuleName = null;
       
        // Change to the preferred working module if it's not the current module, and it exists.
        if (preferredWorkingModuleName != null &&
                !preferredWorkingModuleName.equals(getWorkingModuleName()) &&
                perspective.getMetaModule(preferredWorkingModuleName) != null) {
           
            newWorkingModuleName = preferredWorkingModuleName;
           
        // update the perspective if the working module no longer exists (eg. because of compile failure..)
        } else if (perspective.getWorkingModule() == null) {
            newWorkingModuleName = getInitialWorkingModuleName(workspaceManager.getWorkspace());
        }

        // If there is a new working module, change to it now.
        if (newWorkingModuleName != null) {
            perspective.setWorkingModule(newWorkingModuleName);

            // Also update the window title.
            updateWindowTitle();
        }
       
        // Refresh the gem browser and navigator to show any new gems
        getGemBrowser().refresh();
        getNavigatorOwner().refresh();
       
        return foundErrors;
    }

    /**
     * Perform the initial compilation for the CAL compiler.
     * This will compile the workspace and prepare the compiler for use in the GemCutter
     */
    private void initCompile() {

        // Compile all modules in workspace
        compileWorkspace(false);

        // Create the perspective
        CALWorkspace workspace = workspaceManager.getWorkspace();
        ModuleName workingModuleName = getInitialWorkingModuleName(workspace);
        MetaModule initialWorkingModule = workspace.getMetaModule(workingModuleName);
        perspective = new Perspective(workspace, initialWorkingModule);

        // Set the initial preferred working module name.
        setInitialPreferredWorkingModuleName();
       
        // Populate the gem browser and navigator.
        getGemBrowser().initialize(perspective, true, true);
        getNavigatorOwner().refresh();
    }

    /**
     * Initialize the various CALRunners.
     * @throws ValueEntryException thrown if there is an error creating ValueNodeBuilderHelpers.
     */
    private void initRunners() throws ValueEntryException {

        valueRunner = new ValueRunner(workspaceManager);

        // TODOEL: the various runners should create their own builder helpers.
        //   Unfortunately we still rely on the value editor manager for vnbh initialization.
        ValueNodeBuilderHelper valueBuilderHelper = new ValueNodeBuilderHelper(perspective);
        ValueNodeBuilderHelper displayedGemBuilderHelper = new ValueNodeBuilderHelper(perspective);
       
        valueEditorManager = new ValueEditorManager(displayedGemBuilderHelper, getWorkspace(), typeColours, getTypeCheckInfo());
        displayedGemRunner = new DisplayedGemRunner(workspaceManager, this);
        tableTopEditorHierarchyManager = new ValueEditorHierarchyManager(valueEditorManager, getTableTopPanel());
        outputPanelHierarchyManager = new ValueEditorHierarchyManager(valueEditorManager, getLayeredPane());

        valueRunner.setValueNodeBuilderHelper(valueBuilderHelper);
        displayedGemRunner.setValueNodeBuilderHelper(displayedGemBuilderHelper);
        tableTop.getGemGraph().setValueNodeBuilderHelper(valueEditorManager.getValueNodeBuilderHelper());
       
        // Init builder helpers, include the node helper from the registry.
        valueEditorManager.initValueNodeBuilderHelper(valueBuilderHelper);
        valueEditorManager.initValueNodeBuilderHelper(displayedGemBuilderHelper);
    }
   
    /**
     * Place a given window in the center of the screen.
     * @param w Window the window to center on the screen.
     */
    static void centerWindow(Window w) {
        /* Calculate the screen size */
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
   
        /* Center splash screen */
        Dimension windowSize = w.getSize();
        if (windowSize.height > screenSize.height){
            windowSize.height = screenSize.height;
        }
        if (windowSize.width > screenSize.width){
            windowSize.width = screenSize.width;
        }
        int x = (screenSize.width - windowSize.width) / 2;
        int y = (screenSize.height - windowSize.height) / 2;
        w.setLocation(x, y);
    }

    /**
     * Using the output/result TypeExpr, and the data contained in the ValueNode, dataVN, we display the
     * resulting output in a ValueEntryPanel contained in an internal frame.
     * @param dataVN the value node with the data to display
     * @param target the target whose result to display
     * @param messageTitle the title of the message to display
     * @param errorString boolean flag to indicate whether result is an error condition
     */
    public void displayOutput(ValueNode dataVN, DisplayedGem target, String messageTitle, String errorString) {
   
        final ValueEditor editor;
        if (dataVN != null) {
           
            // Create the resultVEP.  (Remember, the resultVEP should not be editable).
            editor = valueEditorManager.getValueEditorDirector().getRootValueEditor(outputPanelHierarchyManager, dataVN, null, 0, null);
            editor.setEditable(false);
       
            // This ensures that code pertaining to the ValueEditorHierarchy is not confused by this ValueEntryPanel.
            outputPanelHierarchyManager.addTopValueEditor(editor);
        } else {
            editor = null;
        }
       
        // Display an internal frame generated from resultVEP.
        JInternalFrame internalFrame = outputDisplayManager.showResultFrame(editor, target, messageTitle, errorString);
   
        if (editor != null) {
            // This bit of code ensures that some 'clean-up' code is done when the result internal frame is closed.
            // (ie. Need to update the ValueEditorHierarchy)
            internalFrame.addInternalFrameListener(new InternalFrameAdapter() {
       
                private final ValueEditor resultEditor = editor;
           
                public void internalFrameClosing(InternalFrameEvent e) {
                    outputPanelHierarchyManager.removeValueEditor(resultEditor, true);
                }
            });
        }
    }
   
    /**
     * Return the ExplorerArgumentsPane property value.
     * @return JTabbedPane
     */
    private JTabbedPane getExplorerArgumentsPane() {
        if (explorerArgumentsPane == null) {
            try {
                explorerArgumentsPane = new JTabbedPane(SwingConstants.BOTTOM, JTabbedPane.WRAP_TAB_LAYOUT);
                setExplorerTabVisible(true);
                setArgumentsTabVisible(true);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return explorerArgumentsPane;
    }

    /**
     * Set whether the explorer is visible.
     * @param visible whether the explorer is visible.
     */
    private void setExplorerTabVisible(boolean visible) {
        if (visible) {
            String text = getResourceString("Tab_Explorer");
            String toolTip = getResourceString("Tab_ExplorerToolTip");
            Icon tabIcon = null;
            getExplorerArgumentsPane().insertTab(text, tabIcon, getTableTopExplorer(), toolTip, 0);
        } else {
            int tabIndex = getExplorerArgumentsPane().indexOfComponent(getTableTopExplorer());
            if (tabIndex > -1) {
                getExplorerArgumentsPane().removeTabAt(tabIndex);
            }
        }
    }
   
    /**
     * Get the argument explorer component.
     * @return the argument explorer component.
     */
    private ArgumentExplorer getArgumentExplorer() {
        if (argumentExplorer == null) {
            // Create an owner for the argument explorer.
            ArgumentExplorerOwner owner = new ArgumentExplorerOwner() {

                public String getHTMLFormattedMetadata(PartInput input) {
                    ScopedEntityNamingPolicy namingPolicy = new ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous(getTableTop().getCurrentModuleTypeInfo());
                    return ToolTipHelpers.getPartToolTip(input, tableTop.getGemGraph(), namingPolicy, argumentExplorer);
                }

                public void retargetInputArgument(PartInput argument, CollectorGem newTarget, int addIndex) {
                    getTableTop().handleRetargetInputArgumentGesture(argument, newTarget, addIndex);
                }

                public ValueEditorManager getValueEditorManager() {
                    return valueEditorManager;
                }
               

                public String getTypeString(final TypeExpr typeExpr) {
                    final ScopedEntityNamingPolicy namingPolicy;
                    final ModuleTypeInfo currentModuleTypeInfo = tableTop.getCurrentModuleTypeInfo();
                    if (currentModuleTypeInfo == null) {
                        namingPolicy = ScopedEntityNamingPolicy.FULLY_QUALIFIED;
                    } else {
                        namingPolicy = new ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous(currentModuleTypeInfo);
                    }
                   
                    return tableTop.getGemGraph().getTypeString(typeExpr, namingPolicy);
                }
            };
           
            argumentExplorer = new ArgumentExplorer(getTableTop().getGemGraph(), owner);
        }
        return argumentExplorer;
    }
   
    /**
     * Set whether the argument explorer is visible.
     * @param visible whether the argument explorer is visible.
     */
    private void setArgumentsTabVisible(boolean visible) {
        if (visible) {
            String text = getResourceString("Tab_Arguments");
            String toolTip = getResourceString("Tab_ArgumentsToolTip");
            Icon tabIcon = null;
            getExplorerArgumentsPane().addTab(text, tabIcon, getArgumentExplorer(), toolTip);   // add to the end.
        } else {
            int tabIndex = getExplorerArgumentsPane().indexOfComponent(getArgumentExplorer());
            if (tabIndex > -1) {
                getExplorerArgumentsPane().removeTabAt(tabIndex);
            }
        }
    }

    /**
     * Return the ExplorerBrowserSplit property value.
     * @return JSplitPane
     */
    private JSplitPane getExplorerBrowserSplit() {
        if (explorerBrowserSplit == null) {
            try {
                explorerBrowserSplit = new JSplitPane();
               
                explorerBrowserSplit.setOrientation(JSplitPane.VERTICAL_SPLIT)
                explorerBrowserSplit.setBottomComponent(getBrowserOverviewSplit());             
                explorerBrowserSplit.setTopComponent(getExplorerArgumentsPane());
                explorerBrowserSplit.setBorder(null);
                explorerBrowserSplit.setDividerSize(3);
                explorerBrowserSplit.setDividerLocation(300);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return explorerBrowserSplit;
    }

    /**
     * Return the BrowserOverviewSplit property value.
     * @return JSplitPane
     */
    private JSplitPane getBrowserOverviewSplit() {
        if (browserOverviewSplit == null) {
            try {
                browserOverviewSplit = new JSplitPane();
               
                browserOverviewSplit.setOrientation(JSplitPane.VERTICAL_SPLIT);
                browserOverviewSplit.setTopComponent(getGemBrowserPanel());
                browserOverviewSplit.setBottomComponent(null)// Off to start with
                browserOverviewSplit.setDividerSize(3);
                browserOverviewSplit.setDividerLocation(0.5);
                browserOverviewSplit.setBorder(null);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return browserOverviewSplit;
    }
   
    /**
     * Returns the GemBrowser object
     * @return GemBrowser
     */
    private GemBrowser getGemBrowser() {
        if (gemBrowser == null) {
            gemBrowser = new GemBrowser();

            // Customize the browser tree for the gem cutter
            BrowserTree browserTree = gemBrowser.getBrowserTree();
            browserTree.setCellRenderer(new GemCutterBrowserTreeExtensions.CellRenderer(this));
            browserTree.setPopupMenuProvider(new GemCutterBrowserTreeExtensions.PopupMenuProvider(this));
            browserTree.addMouseMotionListener(new GemCutterBrowserTreeExtensions.MouseListener(this));
            browserTree.addLeafNodeTriggeredListener(new GemCutterBrowserTreeExtensions.LeafNodeListener(this));
        }
        return gemBrowser;
    }
   
    /**
     * Return the GemBrowserPanel property value.
     * @return JPanel
     */
    private JPanel getGemBrowserPanel() {
        if (gemBrowserPanel == null) {
            try {
                gemBrowserPanel = getGemBrowser().getGemBrowserPanel();
                gemBrowserPanel.setFocusTraversalKeysEnabled(true);
                gemBrowserPanel.setFocusCycleRoot(true);
                gemBrowserPanel.setName("GemBrowserPanel");
                gemBrowserPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
                gemBrowserPanel.setPreferredSize(gemBrowserPanel.getMaximumSize());
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return gemBrowserPanel;
    }

    /**
     * Return the BrowserTree
     * @return BrowserTree
     */
    BrowserTree getBrowserTree() {
        return getGemBrowser().getBrowserTree();
    }
   
    /**
     * @return the GemCutterPersistenceManager used by this GemCutter
     */
    GemCutterPersistenceManager getPersistenceManager() {
        return persistenceManager;
    }

    /**
     * Return the GemCutterJMenuBar property value.
     * @return JMenuBar
     */
    private JMenuBar getGemCutterJMenuBar() {
        if (gemCutterJMenuBar == null) {
            try {
               
                gemCutterJMenuBar = new JMenuBar();
                gemCutterJMenuBar.setName("GemCutterJMenuBar");
                gemCutterJMenuBar.add(getFileMenu());
                gemCutterJMenuBar.add(getEditMenu());               
                gemCutterJMenuBar.add(getViewMenu());
                gemCutterJMenuBar.add(getInsertMenu());
                gemCutterJMenuBar.add(getGenerateMenu());
                gemCutterJMenuBar.add(getWorkspaceMenu());
                gemCutterJMenuBar.add(getRunMenu());
                gemCutterJMenuBar.add(getDebugMenu());
                gemCutterJMenuBar.add(getHelpMenu());

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return gemCutterJMenuBar;
    }

    /**
     * Return the JFrameContentPane property value.
     * @return JPanel
     */
    private JPanel getJFrameContentPane() {
        if (jFrameContentPane == null) {
            try {
                jFrameContentPane = new JPanel();
                jFrameContentPane.setName("JFrameContentPane");
                jFrameContentPane.setLayout(new BorderLayout());
                jFrameContentPane.add(getToolBarPane(), "North");
                jFrameContentPane.add(getStatusBarPane(), "South");
               
                // Add the GemCutter pane.
                JSplitPane gemCutterPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
                gemCutterPane.setName("GemCutterPane");
                gemCutterPane.setDividerSize(4);

                // Initialize the divider position between browser and table top
                gemCutterPane.setDividerLocation(280);
                gemCutterPane.add(getTableTopScrollPane(), "right");
                gemCutterPane.add(getExplorerBrowserSplit(), "left");                     
                jFrameContentPane.add(gemCutterPane, "Center");
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return jFrameContentPane;
    }

    /**
     * Return the StatusBarPane property value.
     * @return JPanel
     */
    private JPanel getStatusBarPane() {
        if (statusBarPane == null) {
            try {
                statusBarPane = new JPanel();
                statusBarPane.setName("StatusBarPane");
                statusBarPane.setLayout(new BorderLayout());
                getStatusBarPane().add(getStatusMsg1(), "West");
                getStatusBarPane().add(getStatusMsg2(), "Center");
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return statusBarPane;
    }
   
    /**
     * Return the StatusMsg1 property value.
     * @return JLabel
     */
    private JLabel getStatusMsg1() {
        if (statusMsgLabel1 == null) {
            try {
                statusMsgLabel1 = new JLabel();
                statusMsgLabel1.setName("StatusMsg1");
                statusMsgLabel1.setBorder(new EtchedBorder());
                statusMsgLabel1.setText(getResourceString("ReadyFlag"));
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return statusMsgLabel1;
    }

    /**
     * Return the StatusMsg2 property value.
     * @return JLabel
     */
    private JLabel getStatusMsg2() {
        if (statusMsgLabel2 == null) {
            try {
                statusMsgLabel2 = new JLabel();
                statusMsgLabel2.setName("StatusMsg2");
                statusMsgLabel2.setBorder(new EtchedBorder());
                statusMsgLabel2.setText("");
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return statusMsgLabel2;
    }

    /**
     * Returns the TableTop
     * @return TableTop
     */
    TableTop getTableTop() {
        if (tableTop == null) {
            try {
                tableTop = new TableTop(this);
               
                // adds the explorer as a displayed gem state listener
                tableTop.addStateChangeListener(getTableTopExplorerAdapter());
               
                // add listeners for updating the current gem to run and the current collector
                tableTop.addStateChangeListener(currentGemToRunListener);
                tableTop.addStateChangeListener(currentCollectorListener);
                tableTop.addGemGraphChangeListener(currentGemToRunListener);
                tableTop.addGemGraphChangeListener(currentCollectorListener);
                tableTop.getGemGraph().addGemConnectionListener(targetRunnableListener);               
               
                // Make the target gem selectable.
                currentCollectorListener.selectNewReflectorCollector();

                // Change the window title when the target collector's name changes.
                tableTop.getGemGraph().getTargetCollector().addNameChangeListener(new NameChangeListener() {
                    public void nameChanged(NameChangeEvent e) {
                        updateWindowTitle();
                    }
                });
               
                TableTopPanel tableTopPanel = tableTop.getTableTopPanel();
                tableTopPanel.setName("TableTop");
                tableTopPanel.setLayout(null);
                tableTopPanel.setBackground(SystemColor.window);
                tableTopPanel.setBounds(0, 0, 160, 120);
                ToolTipManager.sharedInstance().registerComponent(tableTopPanel);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return tableTop;
    }
   
    /**
     * Returns the reference to the tableTopPanel
     * @return TableTopPanel
     */
    TableTopPanel getTableTopPanel() {
        return getTableTop().getTableTopPanel();
    }
   
    /**
     * Return the TableTopScrollPane property value.
     * @return JScrollPane
     */
    JScrollPane getTableTopScrollPane() {
        if (tableTopScrollPane == null) {
            try {
                tableTopScrollPane = new JScrollPane();
                tableTopScrollPane.setName("TableTopScrollPane");
                getTableTopScrollPane().setViewportView(getTableTopPanel());

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return tableTopScrollPane;
    }

    /**
     * Return the ToolBarPane property value.
     * @return JToolBar
     */
    JToolBar getToolBarPane() {
        if (toolBarPane == null) {
            try {
                toolBarPane = new JToolBar();
                toolBarPane.setName(getResourceString("ViewToolbar"));
                toolBarPane.setRollover(true);
               
                toolBarPane.add(makeNewButton(getNewAction()));
                toolBarPane.add(makeNewButton(getOpenAction()));
                toolBarPane.add(makeNewButton(getSaveGemAction()));
               
                toolBarPane.addSeparator();
                toolBarPane.add(makeNewButton(getCutAction()));
                toolBarPane.add(makeNewButton(getCopyAction()));
                toolBarPane.add(makeNewButton(getPasteAction()));
               
                toolBarPane.addSeparator();
                toolBarPane.add(getUndoButton());
                toolBarPane.add(getUndoDropDownButton());
                toolBarPane.add(getRedoButton());
                toolBarPane.add(getRedoDropDownButton());
               
                toolBarPane.addSeparator();
                toolBarPane.add(makeNewButton(getSearchAction()));
               
                toolBarPane.addSeparator();
                toolBarPane.add(makeNewButton(getAddGemAction()));
                toolBarPane.add(makeNewButton(getAddValueGemAction()));
                toolBarPane.add(makeNewButton(getAddCodeGemAction()));
                toolBarPane.add(makeNewButton(getAddCollectorGemAction()));
                toolBarPane.add(getAddReflectorGemButton());
                toolBarPane.add(getAddReflectorGemDropDownButton());
                toolBarPane.add(makeNewButton(getAddRecordCreationGemAction()));
                toolBarPane.add(makeNewButton(getAddRecordFieldSelectionGemAction()));
               
                toolBarPane.addSeparator();
                toolBarPane.add(getRunButton());
                toolBarPane.add(getRunDropDownButton());
                toolBarPane.add(makeNewButton(getStopAction()));
               
                // Space for the Parameter navigation buttons.
                toolBarPane.addSeparator();

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return toolBarPane;
    }

    /**
     * Creates an instance of ViewPreferencesDialog if null
     * @return PreferencesDialog
     */
    private PreferencesDialog getPreferencesDialog() {
        if (preferencesDialog == null) {
            preferencesDialog = new PreferencesDialog(this);
            centerWindow(preferencesDialog);
        }
        return preferencesDialog;
    }

    /**
     * Return the FileMenu property value.
     * @return JMenu
     */
    private JMenu getFileMenu() {
        if (fileMenu == null) {
            try {
                fileMenu = new JMenu();
                fileMenu.setName("FileMenu");
                fileMenu.setText(getResourceString("FileMenu"));
                fileMenu.setMargin(new Insets(2, 0, 2, 0));
                fileMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_FILE_MENU);
                fileMenu.add(makeNewMenuItem(getNewAction()));
                fileMenu.add(makeNewMenuItem(getOpenAction()));
                fileMenu.add(makeNewMenuItem(getSaveGemAction()));               
                fileMenu.addSeparator();
                fileMenu.add(makeNewMenuItem(getSwitchWorkspaceAction()));
                fileMenu.addSeparator();
                fileMenu.add(makeNewMenuItem(getExitAction()));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return fileMenu;
   


    /**
     * Called whenever the part throws an exception.
     * @param exception Throwable
     */
    private static void handleException(Throwable exception) {
   
        /* Uncomment the following lines to print uncaught exceptions to stdout */
        System.out.println("--------- UNCAUGHT EXCEPTION ---------");
        exception.printStackTrace(System.out);
    }
   
    /**
     * Return the OverviewPanel property value.
     * @return OverviewPanel
     */
    OverviewPanel getOverviewPanel() {
        if (overviewPanel == null) {
            try {
                overviewPanel = new OverviewPanel(getTableTopPanel());
                getOverviewPanel().setBackground(Color.white);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return overviewPanel;
    }
   
    /**
     * Return the action that creates a "new" TableTop.
     * @return Action
     */
    private Action getNewAction() {
       
        if (newAction == null) {
            try {
                newAction = new AbstractAction (getResourceString("New"),
                        new ImageIcon(GemCutter.class.getResource("/Resources/new.gif"))) {

                    private static final long serialVersionUID = -8609323621425336729L;

                    public void actionPerformed(ActionEvent evt) {
                        if (promptSaveCurrentTableTopIfNonEmpty(null, null)) {
                            newTableTop();
                        }
                    }
                };
               
                newAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_NEW));
                newAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_NEW);
                newAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("NewToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return newAction;
    }

    /**
     * Reset the table top to a new state.
     */
    private void newTableTop() {

        // Close all open result frames
        outputDisplayManager.closeAllResultFrames();

        // Clear the tabletop
        getTableTop().doNewTableTopUserAction();
   
        // Clear the undo stack and dirty edit whenever a new table top is created
        extendedUndoManager.discardAllEdits();
        editToUndoWhenNonDirty = null;
        updateUndoWidgets();
               
        // Reset the runnable state
        setTargetRunningButtons(false);

        // Reset the TableTop size to match the Viewable size.
        Dimension size = new Dimension(1, 1);
        TableTop tableTop = getTableTop();
        tableTop.getTableTopPanel().setSize(size);
        tableTop.getTableTopPanel().setPreferredSize(size);
        tableTop.getTableTopPanel().revalidate();
       
        // Update the explorer.
        tableTop.addGemGraphChangeListener(getTableTopExplorer());
        getTableTopExplorerAdapter().getTableTopExplorer().rebuildTree();

        // Add listeners for updating the current gem to run and the current collector
        tableTop.addGemGraphChangeListener(currentGemToRunListener);
        tableTop.addGemGraphChangeListener(currentCollectorListener);
        //tableTop.add
        tableTop.getGemGraph().addGemConnectionListener(targetRunnableListener);       
       
        // Update the current gem to run and the current collector
        currentGemToRunListener.selectNewGem();
        currentCollectorListener.selectNewReflectorCollector();
    }

    /**
     * @return the Action for opening a gem design
     */
    private Action getOpenAction() {
       
        if (openAction == null) {
            try {
                openAction = new AbstractAction (getResourceString("Open"),
                        new ImageIcon(getClass().getResource("/Resources/open.gif"))) {

                    private static final long serialVersionUID = -4225999206287072243L;

                    public void actionPerformed(ActionEvent evt) {
                        openGemDesign(null);
                    }
                };
               
                openAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_OPEN));
                openAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_OPEN);
                openAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("OpenToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return openAction;
    }
   
    /**
     * Return the action that handles saving the current target gem.
     * @return Action
     */
    private Action getSaveGemAction() {
       
        if (saveGemAction == null) {
            try {
                saveGemAction = new AbstractAction (getResourceString("SaveGemFromList"),
                        new ImageIcon(getClass().getResource("/Resources/save.gif"))) {

                    private static final long serialVersionUID = 5568345421791750368L;

                    public void actionPerformed(ActionEvent evt) {
                        saveGem();
                    }
                };
               
                saveGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_SAVE_GEM));
                saveGemAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_SAVE_GEM);
                saveGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SaveGemFromListToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return saveGemAction;
    }

    /**
     * Return the action that handles the closing of the GemCutter.
     * @return Action
     */
    private Action getExitAction() {
       
        if (exitAction == null) {
            try {
                exitAction = new AbstractAction (getResourceString("Exit")) {
                                                           
                    private static final long serialVersionUID = -6645421153668615170L;

                    public void actionPerformed(ActionEvent evt) {
                        if (promptSaveCurrentTableTopIfNonEmpty(null, null)){
                            dispose();
                        }
                    }
                };
               
                exitAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_EXIT));
                exitAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ExitToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return exitAction;
    }
   
    /**
     * Return the DebugMenu property value.
     * @return JMenu
     */
    private JMenu getDebugMenu() {
        if (debugMenu == null) {
            try {
                debugMenu = new JMenu();
                debugMenu.setName("DebugMenu");
                debugMenu.setText(getResourceString("DebugMenu"));
                debugMenu.setMargin(new Insets(2, 0, 2, 0));
                debugMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_DEBUG_MENU);
               
                debugMenu.add(makeNewMenuItem(getCheckGraphAction()));
                debugMenu.add(makeNewMenuItem(getDumpDefinitionAction()));               
                debugMenu.add(makeNewMenuItem(getDumpGraphAction()));
                debugMenu.addSeparator();

                debugMenu.add(makeNewMenuItem(dumpOrphanedMetadataAction()));
                debugMenu.add(makeNewMenuItem(getLoadSaveAllMetadataAction()));
                debugMenu.add(makeNewMenuItem(getTestExamplesAction()));
                debugMenu.add(makeNewMenuItem(getDumpInconsistententArgumentMetadataAction()));
                debugMenu.addSeparator();

                debugMenu.add(makeNewMenuItem(getDumpReferenceFrequenciesAction()));
                debugMenu.add(makeNewMenuItem(getDumpCompositionalFrequenciesAction()));
                debugMenu.add(makeNewMenuItem(getDumpLintWarningsAction()));
                debugMenu.addSeparator();

                JCheckBoxMenuItem allowPreludeRenamingMenuItem = makeNewCheckBoxMenuItem(getAllowPreludeRenamingAction());
                allowPreludeRenamingMenuItem.setSelected(isAllowPreludeRenamingMode());
                debugMenu.add(allowPreludeRenamingMenuItem);

                JCheckBoxMenuItem allowDuplicateRenamingMenuItem = makeNewCheckBoxMenuItem(getAllowDuplicateRenamingAction());
                allowDuplicateRenamingMenuItem.setSelected(isAllowDuplicateRenamingMode());
                debugMenu.add(allowDuplicateRenamingMenuItem);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return debugMenu;
    }
  
   /** Get an action to dump the gem definition associated with the target collector. */
    private Action getDumpDefinitionAction() {        
        if(dumpDefinitionAction == null) {
            dumpDefinitionAction = new AbstractAction(getResourceString("DumpGemDefinition")) {
                private static final long serialVersionUID = -9075454446137881013L;

                /** Get the target collector and dump it's definition to the console */
                public void actionPerformed(ActionEvent evt) {
                    CollectorGem targetCollector = tableTop.getTargetCollector();
                    Target targetGem = tableTop.getDisplayedGem(targetCollector).getTarget();                   
                    MetaModule currentMetaModule = getWorkspace().getMetaModule(getWorkingModuleName());
                    ModuleTypeInfo currentModuleTypeInfo = currentMetaModule.getTypeInfo();
                   
                    System.out.println("Gem Definition:");
                    System.out.println(targetGem.getTargetDef(null, currentModuleTypeInfo)+"\n");
                   
                }
            };               
            dumpDefinitionAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPDEFINITION));
           
            // Set this action to disabled for now
            dumpDefinitionAction.setEnabled(false);           
        }
        return dumpDefinitionAction;
    }
   
    /**
     * @return the action which toggles whether renaming gems in the Prelude module is allowed
     */
    private Action getAllowPreludeRenamingAction() {
       
        if (allowPreludeRenamingAction == null) {
            try {
                allowPreludeRenamingAction = new AbstractAction (getResourceString("AllowPreludeRenaming")) {
                                                           
                    private static final long serialVersionUID = 3990990055098390968L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // This just toggles the debug output mode
                        boolean oldValue = ((Boolean)getValue("InAllowRenamingMode")).booleanValue();
                        putValue("InAllowRenamingMode", Boolean.valueOf(!oldValue));
                    }
                };
               
                //allowPreludeRenamingAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DEBUG_OUTPUT));
                allowPreludeRenamingAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AllowPreludeRenamingToolTip"));
               
                // Get the action to remember if we are allowing renaming
                allowPreludeRenamingAction.putValue("InAllowRenamingMode", Boolean.FALSE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return allowPreludeRenamingAction;
    }
   
    /**
     * @return the action which toggles whether renaming gems to an already-existing name is allowed
     */
    private Action getAllowDuplicateRenamingAction() {
       
        if (allowDuplicateRenamingAction == null) {
            try {
                allowDuplicateRenamingAction = new AbstractAction (getResourceString("AllowDuplicateRenaming")) {

                    private static final long serialVersionUID = -7687196097621043422L;

                    public void actionPerformed(ActionEvent evt) {

                        // This just toggles the debug output mode
                        boolean oldValue = ((Boolean)getValue("InAllowDuplicateRenamingMode")).booleanValue();
                        putValue("InAllowDuplicateRenamingMode", Boolean.valueOf(!oldValue));
                    }
                };
               
                allowDuplicateRenamingAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AllowDuplicateRenamingToolTip"));
               
                // Get the action to remember if we are allowing renaming
                allowDuplicateRenamingAction.putValue("InAllowDuplicateRenamingMode", Boolean.FALSE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return allowDuplicateRenamingAction;
    }
   
    /**
     * @return the action which tests metadata examples.
     */
    private Action getTestExamplesAction() {
       
        Action testAction = new AbstractAction(getResourceString("TestMetadataExamples")) {
            private static final long serialVersionUID = -2124039687691058698L;

            public void actionPerformed(ActionEvent evt) {
                getNavigatorOwner().testMetadataExamples();
            }
        };
       
        testAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_TESTMETADATA));               
        testAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("TestMetadataExamplesToolTip"));

        return testAction;
    }
   
    private Action getCheckGraphAction() {
       
        Action checkAction = new AbstractAction(getResourceString("CheckGraphText")) {
            private static final long serialVersionUID = 4810094313633625167L;

            public void actionPerformed(ActionEvent evt) {
                TableTop tTop = getTableTop();
                Set<Gem> nodeSet = tTop.getGemGraph().getRoots();
                System.out.println("Check Graph Source Text:");
                System.out.println(CALSourceGenerator.getDebugCheckGraphSource(nodeSet));               
            }
        };
       
        checkAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_CHECKGRAPH));
        return checkAction;
    }
   
    /**
     * @return the action which dumps the gem graph for the current tabletop.
     */
    private Action getDumpGraphAction() {
       
        Action dumpAction = new AbstractAction(getResourceString("DumpGemGraph")) {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent evt) {
                TableTop tTop = getTableTop();
                String graphStr = tTop.getGemGraph().toString();
                System.out.println(graphStr);
            }
        };
       
        dumpAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPGRAPH));
        return dumpAction;
    }
   
    /**
     * A customized FileChooser for dumping CSV files that includes some checkboxes for various
     * options (such as grouping by module, etc).
     *
     * Creation date: (Jun 29, 2005)
     * @author Jawright
     */
    private static class DumpOptionChooser extends JFileChooser {
        private static final long serialVersionUID = -5334307235048868783L;

        /** UI element for the filter out test modules option */
        private final JCheckBox filterTestModulesCheckbox = new JCheckBox(getResourceString("FilterTestModulesOption"));

        /** UI element (checkbox) for the exclude functions by regexp option */
        private final JCheckBox excludeFunctionsCheckbox = new JCheckBox();
       
        /** UI element (text field) for the exclude functions by regexp option */
        private final JComboBox excludeFunctionsField = new JComboBox();
       
        /**
         * Construct a DumpOptionChooser.
         */
        DumpOptionChooser() {
            super();
           
            // Restore options from preferences
            filterTestModulesCheckbox.setSelected(getPreferences().getBoolean(FILTER_TEST_MODULES_PREF_KEY, FILTER_TEST_MODULES_DEFAULT));
            excludeFunctionsCheckbox.setSelected(getPreferences().getBoolean(EXCLUDE_FUNCTIONS_BY_REGEXP_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_DEFAULT));
            excludeFunctionsCheckbox.setAction(
                    new AbstractAction(getResourceString("ExcludeFunctionsOption")) {
                        private static final long serialVersionUID = -7700111529077716606L;

                        public void actionPerformed(final ActionEvent e) {
                            excludeFunctionsField.setEnabled(shouldExcludeFunctionsByRegexp());
                        }
                    });
           
            final String prefExcludeRegexp = getPreferences().get(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT);
            excludeFunctionsField.addItem(prefExcludeRegexp);

            if(!prefExcludeRegexp.equals(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT)) {
                excludeFunctionsField.addItem(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT);
            }
            excludeFunctionsField.setEditable(true);
            excludeFunctionsField.getEditor().setItem(getPreferences().get(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT));
            excludeFunctionsField.setEnabled(shouldExcludeFunctionsByRegexp());
           
            // Set up other customizations
            final FileFilter filter = new ExtensionFileFilter("csv", getResourceString("CSVFileFilterName"));
         
            setAcceptAllFileFilterUsed(true);
            addChoosableFileFilter(filter);
            setFileFilter(filter);           
        }
       
        /**
         * We override the createDialog method of JFileChooser in order to add the extra preference
         * UI elements.
         */
        protected JDialog createDialog(final Component parent) {
            final JDialog dialog = super.createDialog(parent);
           
            // The default dialog places the file chooser in the center; we want it
            // elsewhere.
            dialog.getContentPane().remove(this);
           
            final Box optionBox = Box.createVerticalBox();
           
            final Box filterTestModulesBox = Box.createHorizontalBox();
            filterTestModulesBox.add(filterTestModulesCheckbox);
            filterTestModulesBox.add(Box.createGlue());
            optionBox.add(filterTestModulesBox);
           
            final Box excludeFunctionsByRegexpBox = Box.createHorizontalBox();
           
            excludeFunctionsByRegexpBox.add(excludeFunctionsCheckbox);
            excludeFunctionsByRegexpBox.add(excludeFunctionsField);
            excludeFunctionsByRegexpBox.add(Box.createGlue());
            optionBox.add(excludeFunctionsByRegexpBox);
           
            optionBox.setBorder(BorderFactory.createTitledBorder(getResourceString("OutputPreferencesBorderTitle")));
           
            dialog.getContentPane().setLayout(new BoxLayout(dialog.getContentPane(), BoxLayout.Y_AXIS));
            dialog.getContentPane().add(this);
            dialog.getContentPane().add(optionBox);
           
            dialog.setModal(true);
            dialog.pack();
           
            return dialog;
        }
       
        /**
         * @return true when the filter-out-test-modules option has been selected.
         */
        boolean shouldFilterTestModules() {
            return filterTestModulesCheckbox.isSelected();
        }
   
        /**
         * @return true when the "exclude functions matching regexp" option has been selected
         */
        boolean shouldExcludeFunctionsByRegexp() {
            return excludeFunctionsCheckbox.isSelected();
        }
       
        /**
         * @return The regexp to use to filter functions
         */
        String getExcludeFunctionsRegexp() {
            return (String)excludeFunctionsField.getEditor().getItem();
        }
       
        /**
         * Saves the current options state to preferences.
         */
        void savePreferences() {
            getPreferences().putBoolean(EXCLUDE_FUNCTIONS_BY_REGEXP_PREF_KEY, shouldExcludeFunctionsByRegexp());
            getPreferences().put(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, getExcludeFunctionsRegexp());
            getPreferences().putBoolean(FILTER_TEST_MODULES_PREF_KEY, shouldFilterTestModules());
        }
    }
   
    /**
     * @return the Action that dumps the reference frequency of each gem in the workspace
     *          to a user-specified file.
     */
    private Action getDumpReferenceFrequenciesAction() {
       
        Action dumpAction = new AbstractAction(getResourceString("DumpReferenceFrequencies")) {
            private static final long serialVersionUID = -3222695625267056652L;

            public void actionPerformed(ActionEvent evt) {
                SourceMetrics workspaceSourceMetrics = perspective.getWorkspace().getSourceMetrics();

              DumpOptionChooser chooser = new DumpOptionChooser();
              int fileResponse = chooser.showSaveDialog(GemCutter.this);
             
              // If the user closed the dialog or hit cancel, do nothing
              // If they didn't, then remember their option selections for next time.
              if (fileResponse == JFileChooser.CANCEL_OPTION || fileResponse == JFileChooser.ERROR_OPTION) {
                  return;
              }
              chooser.savePreferences();
             
              ModuleFilter moduleFilter;
              if (chooser.shouldFilterTestModules()) {
                  moduleFilter = new ExcludeTestModulesFilter(getWorkspace());
              } else {
                  moduleFilter = new AcceptAllModulesFilter();
              }
             
              QualifiedNameFilter functionFilter;
              if (chooser.shouldExcludeFunctionsByRegexp()) {
                  functionFilter = new RegExpBasedUnqualifiedNameFilter(chooser.getExcludeFunctionsRegexp(), true);
              } else {
                  functionFilter = new AcceptAllQualifiedNamesFilter();
              }
             
              try {
                  FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile());
                  PrintStream ps = new PrintStream(fos);
                  ps.print(workspaceSourceMetrics.dumpReferenceFrequencies(moduleFilter, functionFilter, true));
                  ps.close();
                  fos.close();
              } catch(FileNotFoundException e) {
                  String errTitle = getResourceString("DumpFrequenciesErrorDialogTitle");
                  String errMessage = GemCutterMessages.getString("DumpFrequenciesErrorDialogMessage", chooser.getSelectedFile().toString());
                  JOptionPane.showMessageDialog(GemCutter.this, errMessage, errTitle, JOptionPane.ERROR_MESSAGE);
              } catch(IOException e) {
                  String errTitle = getResourceString("DumpFrequenciesErrorDialogTitle");
                  String errMessage = GemCutterMessages.getString("DumpFrequenciesErrorDialogTitle", chooser.getSelectedFile().toString());
                  JOptionPane.showMessageDialog(GemCutter.this, errMessage, errTitle, JOptionPane.ERROR_MESSAGE);
              }
          }
        };
       
        dumpAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPFREQUENCIES));
        return dumpAction;
    }
   
    /**
     * @return the Action that dumps the reference frequency of each gem in the workspace
     *          to a user-specified file.
     */
    private Action getDumpCompositionalFrequenciesAction() {
       
        Action dumpAction = new AbstractAction(getResourceString("DumpCompositionalFrequencies")) {
          private static final long serialVersionUID = -8849198411020849911L;

        public void actionPerformed(ActionEvent evt) {
              SourceMetrics workspaceSourceMetrics = perspective.getWorkspace().getSourceMetrics();

              DumpOptionChooser chooser = new DumpOptionChooser();
              int fileResponse = chooser.showSaveDialog(GemCutter.this);
           
              // If the user closed the dialog or hit cancel, do nothing
              if (fileResponse == JFileChooser.CANCEL_OPTION || fileResponse == JFileChooser.ERROR_OPTION) {
                  return;
              }
              chooser.savePreferences();
             
              ModuleFilter moduleFilter;
              if (chooser.shouldFilterTestModules()) {
                  moduleFilter = new ExcludeTestModulesFilter(getWorkspace());
              } else {
                  moduleFilter = new AcceptAllModulesFilter();
              }
             
              QualifiedNameFilter functionFilter;
              if (chooser.shouldExcludeFunctionsByRegexp()) {
                  functionFilter = new RegExpBasedUnqualifiedNameFilter(chooser.getExcludeFunctionsRegexp(), true);
              } else {
                  functionFilter = new AcceptAllQualifiedNamesFilter();
              }
             
              try {
                  FileOutputStream fos = new FileOutputStream(chooser.getSelectedFile());
                  PrintStream ps = new PrintStream(fos);
                  ps.print(workspaceSourceMetrics.dumpCompositionalFrequencies(moduleFilter, functionFilter, true));
                  ps.close();
                  fos.close();
              } catch(FileNotFoundException e) {
                  String errTitle = getResourceString("DumpFrequenciesErrorDialogTitle");
                  String errMessage = GemCutterMessages.getString("DumpFrequenciesErrorDialogMessage", chooser.getSelectedFile().toString());
                  JOptionPane.showMessageDialog(GemCutter.this, errMessage, errTitle, JOptionPane.ERROR_MESSAGE);
              } catch(IOException e) {
                  String errTitle = getResourceString("DumpFrequenciesErrorDialogTitle");
                  String errMessage = GemCutterMessages.getString("DumpFrequenciesErrorDialogTitle", chooser.getSelectedFile().toString());
                  JOptionPane.showMessageDialog(GemCutter.this, errMessage, errTitle, JOptionPane.ERROR_MESSAGE);
              }
          }
        };
       
        dumpAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPCOMPOSITIONALFREQUENCIES));
        return dumpAction;
    }
   
    /**
     * A custom dialogue for choosing the various options associated with running a
     * lint check.
     *
     * @author Jawright
     */
    private static class LintOptionChooser extends JDialog {
       
        private static final long serialVersionUID = -3249297142628860383L;

        /** UI element (checkbox) for the filter out test modules option */
        private final JCheckBox filterTestModulesCheckbox = new JCheckBox(getResourceString("FilterTestModulesOption"));

        /** UI element (checkbox) for the exclude functions by regexp option */
        private final JCheckBox excludeFunctionsCheckbox = new JCheckBox();
       
        /** UI element (text field) for the exclude functions by regexp option */
        private final JComboBox excludeFunctionsField = new JComboBox();
       
        /** UI element (checkbox) for the trace skipped modules & functions option */
        private final JCheckBox traceSkippedCheckbox = new JCheckBox(getResourceString("TraceSkippedOption"));
       
        /** UI element (checkbox) for the redundant lambdas check */
        private final JCheckBox includeRedundantLambdasCheckbox = new JCheckBox(getResourceString("IncludeRedundantLambdasOption"));
       
        /** UI element (checkbox) for the unplinged primitive args check */
        private final JCheckBox includeUnplingedArgsCheckbox = new JCheckBox(getResourceString("IncludeUnplingedArgsOption"));
       
        /** UI element (checkbox) for the unused private functions check */
        private final JCheckBox includeUnusedPrivates = new JCheckBox(getResourceString("IncludeUnusedPrivates"));
       
        /** UI element (checkbox) for the mismatched plinging of alias function parameters check */
        private final JCheckBox includeMismatchedAliasPlings = new JCheckBox(getResourceString("MismatchedAliasPlings"));
       
        /** UI element (checkbox) for the unreferenced let variables check */
        private final JCheckBox includeUnreferencedLetVariables = new JCheckBox(getResourceString("IncludeUnreferencedLetVariables"));
       
        private final JButton okButton = new JButton(getResourceString("LOC_OK"));
        private final JButton cancelButton = new JButton(getResourceString("LOC_Cancel"));
       
        /** true if the user selected the OK button */
        private boolean okSelected = false;
       
        /** Construct a new option dialog for the lint checks*/
        LintOptionChooser(JFrame parentFrame) {
            super(parentFrame);
            initialize();
        }
       
        /**
         * Initialize the class.
         */
        private void initialize() {
           
            // Basic window properties
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            setTitle(getResourceString("LintOptionDialog"));
            JPanel mainPanel = new JPanel(new BorderLayout());
            mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            setContentPane(mainPanel);
           
            // Restore options from preferences
            includeRedundantLambdasCheckbox.setSelected(getPreferences().getBoolean(INCLUDE_REDUNDANT_LAMBDAS_PREF_KEY, INCLUDE_UNPLINGED_PRIMITIVE_ARGS_DEFAULT));
            includeUnplingedArgsCheckbox.setSelected(getPreferences().getBoolean(INCLUDE_UNPLINGED_PRIMITIVE_ARGS_PREF_KEY, INCLUDE_UNPLINGED_PRIMITIVE_ARGS_DEFAULT));
            includeMismatchedAliasPlings.setSelected(getPreferences().getBoolean(INCLUDE_MISMATCHED_WRAPPER_PLINGS_PREF_KEY, INCLUDE_MISMATCHED_WRAPPER_PLINGS_DEFAULT));
            includeUnusedPrivates.setSelected(getPreferences().getBoolean(INCLUDE_UNUSED_PRIVATE_FUNCTIONS_PREF_KEY, INCLUDE_UNUSED_PRIVATE_FUNCTIONS_DEFAULT));
            includeUnreferencedLetVariables.setSelected(getPreferences().getBoolean(INCLUDE_REFERENCED_LET_VARIABLES_PREF_KEY, INCLUDE_REFERENCED_LET_VARIABLES_DEFAULT));
           
            traceSkippedCheckbox.setSelected(getPreferences().getBoolean(TRACE_SKIPPED_PREF_KEY, TRACE_SKIPPED_DEFAULT));
            filterTestModulesCheckbox.setSelected(getPreferences().getBoolean(FILTER_TEST_MODULES_PREF_KEY, FILTER_TEST_MODULES_DEFAULT));
            excludeFunctionsCheckbox.setSelected(getPreferences().getBoolean(EXCLUDE_FUNCTIONS_BY_REGEXP_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_DEFAULT));
            excludeFunctionsCheckbox.setAction(
                    new AbstractAction(getResourceString("ExcludeFunctionsOption")) {
                        private static final long serialVersionUID = 2004612193586063898L;

                        public void actionPerformed(ActionEvent e) {
                            excludeFunctionsField.setEnabled(shouldExcludeFunctionsByRegexp());
                        }
                    });
           
            String prefExcludeRegexp = getPreferences().get(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT);
            excludeFunctionsField.addItem(prefExcludeRegexp);
            if(!prefExcludeRegexp.equals(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT)) {
                excludeFunctionsField.addItem(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT);
            }
            excludeFunctionsField.setEditable(true);
            excludeFunctionsField.getEditor().setItem(getPreferences().get(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_DEFAULT));
            excludeFunctionsField.setEnabled(shouldExcludeFunctionsByRegexp());

            // Layout
            Box excludeFunctionsBox = Box.createHorizontalBox();
            excludeFunctionsBox.add(excludeFunctionsCheckbox);
            excludeFunctionsBox.add(excludeFunctionsField);
            excludeFunctionsBox.add(Box.createHorizontalGlue());
            excludeFunctionsField.setMinimumSize(new Dimension(235, 23));
            excludeFunctionsField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 23));
           
            Box filterTestModulesBox = Box.createHorizontalBox();
            filterTestModulesBox.add(filterTestModulesCheckbox);
            filterTestModulesBox.add(Box.createHorizontalGlue());
           
            Box traceSkippedBox = Box.createHorizontalBox();
            traceSkippedBox.add(traceSkippedCheckbox);
            traceSkippedBox.add(Box.createHorizontalGlue());
           
            Box optionsBox = Box.createVerticalBox();
            optionsBox.add(filterTestModulesBox);
            optionsBox.add(excludeFunctionsBox);
            optionsBox.add(traceSkippedBox);
            optionsBox.setBorder(BorderFactory.createTitledBorder(getResourceString("OutputPreferencesBorderTitle")));

            Box checkTypesInnerBox = Box.createVerticalBox();
            checkTypesInnerBox.add(includeRedundantLambdasCheckbox);
            checkTypesInnerBox.add(includeUnplingedArgsCheckbox);
            checkTypesInnerBox.add(includeUnusedPrivates);
            checkTypesInnerBox.add(includeMismatchedAliasPlings);
            checkTypesInnerBox.add(includeUnreferencedLetVariables);
            checkTypesInnerBox.setBorder(BorderFactory.createTitledBorder(getResourceString("LintCheckTypesBorderTitle")));

            Box checkTypesBox = Box.createHorizontalBox();
            checkTypesBox.add(checkTypesInnerBox);
            checkTypesBox.add(Box.createHorizontalGlue());
           
            Box buttonBox = Box.createHorizontalBox();
            buttonBox.add(Box.createHorizontalGlue());
            buttonBox.add(okButton);
            buttonBox.add(Box.createHorizontalStrut(10));
            buttonBox.add(cancelButton);
            buttonBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 2, 2));
           
            Box uberBox = Box.createVerticalBox();
            uberBox.add(checkTypesBox);
            uberBox.add(optionsBox);
           
            getContentPane().add(uberBox, "Center");
            getContentPane().add(buttonBox, "South");
           
            // Actions
            okButton.setAction(
                    new AbstractAction(getResourceString("LOC_OK")) {
                        private static final long serialVersionUID = 5544185100443091371L;

                        public void actionPerformed(ActionEvent e) {
                            okSelected = true;
                            LintOptionChooser.this.dispose();
                        }
                    });

            cancelButton.setAction(
                    new AbstractAction(getResourceString("LOC_Cancel")) {
                        private static final long serialVersionUID = -3915109617604551485L;

                        public void actionPerformed(ActionEvent e) {
                            okSelected = false;
                            LintOptionChooser.this.dispose();
                        }
                    });

            // Commit
            setModal(true);
            pack();
        }       

        /**
         * @return true when the filter-out-test-modules option has been selected.
         */
        boolean shouldFilterTestModules() {
            return filterTestModulesCheckbox.isSelected();
        }
   
        /**
         * @return true when the "exclude functions matching regexp" option has been selected
         */
        boolean shouldExcludeFunctionsByRegexp() {
            return excludeFunctionsCheckbox.isSelected();
        }
       
        /**
         * @return The regexp to use to filter functions
         */
        String getExcludeFunctionsRegexp() {
            return (String)excludeFunctionsField.getSelectedItem();
        }
       
        /**
         * @return true if the "warn about redundant lambdas" option has been selected
         */
        boolean shouldIncludeRedundantLambdas() {
            return includeRedundantLambdasCheckbox.isSelected();
        }
       
        /**
         * @return true if the "warn about unplinged primitive args" option has been selected
         */
        boolean shouldIncludeUnplingedPrimitiveArgs() {
            return includeUnplingedArgsCheckbox.isSelected();
        }
       
        /**
         * @return true if the "dump skipped functions/modules" option has been selected
         */
        boolean shouldTraceSkipped() {
            return traceSkippedCheckbox.isSelected();
        }
       
        /**
         * @return true if the "warn about unused private functions" option has been selected
         */
        boolean shouldIncludeUnusedPrivates() {
            return includeUnusedPrivates.isSelected();
        }
       
        /**
         * @return true if the "warn about alias functions with mismatched parameter plings" option has been selected
         */
        boolean shouldIncludeMismatchedAliasPlings() {
            return includeMismatchedAliasPlings.isSelected();
        }
       
        /**
         * @return true if the "warn about unreferenced let variables" option has been selected
         */
        boolean shouldIncludeUnreferencedLetVariables() {
            return includeUnreferencedLetVariables.isSelected();
        }
       
        /**
         * @return true if the user pushed the OK button, or false otherwise
         */
        boolean getOkSelected() {
            return okSelected;
        }
       
        /**
         * Saves the current options state to preferences.
         */
        void savePreferences() {
            getPreferences().putBoolean(EXCLUDE_FUNCTIONS_BY_REGEXP_PREF_KEY, shouldExcludeFunctionsByRegexp());
            getPreferences().put(EXCLUDE_FUNCTIONS_BY_REGEXP_ARGUMENT_PREF_KEY, getExcludeFunctionsRegexp());
            getPreferences().putBoolean(FILTER_TEST_MODULES_PREF_KEY, shouldFilterTestModules());
            getPreferences().putBoolean(INCLUDE_REDUNDANT_LAMBDAS_PREF_KEY, shouldIncludeRedundantLambdas());
            getPreferences().putBoolean(INCLUDE_UNPLINGED_PRIMITIVE_ARGS_PREF_KEY, shouldIncludeUnplingedPrimitiveArgs());
            getPreferences().putBoolean(INCLUDE_UNUSED_PRIVATE_FUNCTIONS_PREF_KEY, shouldIncludeUnusedPrivates());
            getPreferences().putBoolean(INCLUDE_MISMATCHED_WRAPPER_PLINGS_PREF_KEY, shouldIncludeMismatchedAliasPlings());
            getPreferences().putBoolean(TRACE_SKIPPED_PREF_KEY, shouldTraceSkipped());
        }
    }
   
    /**
     * @return the Action that dumps lint warnings for every module in the current workspace to
     *          the system console.
     */
    private Action getDumpLintWarningsAction() {
       
        Action lintAction = new AbstractAction(getResourceString("DumpLintWarnings")) {
            private static final long serialVersionUID = -8628525045109137685L;

            public void actionPerformed(ActionEvent evt) {
               
                LintOptionChooser chooser = new LintOptionChooser(GemCutter.this);
                centerWindow(chooser);
                chooser.setVisible(true);
               
                ModuleFilter moduleFilter;
                if (chooser.shouldFilterTestModules()) {
                    moduleFilter = new ExcludeTestModulesFilter(getWorkspace());
                } else {
                    moduleFilter = new AcceptAllModulesFilter();
                }
               
                QualifiedNameFilter functionFilter;
                if (chooser.shouldExcludeFunctionsByRegexp()) {
                    functionFilter = new RegExpBasedUnqualifiedNameFilter(chooser.getExcludeFunctionsRegexp(), true);
                } else {
                    functionFilter = new AcceptAllQualifiedNamesFilter();
                }
               
                if(chooser.getOkSelected()) {
                    chooser.savePreferences();
                    System.out.println("Dumping lint warnings:");
                    SourceMetrics workspaceSourceMetrics = perspective.getWorkspace().getSourceMetrics();
                    workspaceSourceMetrics.dumpLintWarnings(moduleFilter, functionFilter,
                                                            chooser.shouldTraceSkipped(),
                                                            chooser.shouldIncludeUnplingedPrimitiveArgs(), chooser.shouldIncludeRedundantLambdas(), chooser.shouldIncludeUnusedPrivates(), chooser.shouldIncludeMismatchedAliasPlings(), chooser.shouldIncludeUnreferencedLetVariables());
                    System.out.println("done.");
                }
            }
        };
   
        lintAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPLINTWARNINGS));
        return lintAction;
    }
   
    /**
     * @return the action which attempts to load and save all metadata entries in the metadata manager
     */
    private Action getLoadSaveAllMetadataAction() {
       
        Action loadSaveAction = new AbstractAction(getResourceString("LoadSaveAllMetadata")) {
            private static final long serialVersionUID = 7037711280147379093L;

            public void actionPerformed(ActionEvent evt) {
                System.out.println("Loading and saving all metadata resources in the workspace...");
               
                ModuleName[] workspaceModuleNames = getWorkspace().getModuleNames();
               
                // loop through each module in the workspace
                for (final ModuleName moduleName : workspaceModuleNames) {
                   
                    ResourceManager metadataResourceManager = getWorkspace().getResourceManager(moduleName, WorkspaceResource.METADATA_RESOURCE_TYPE);
                   
                    MetadataStore metadataStore = (MetadataStore)metadataResourceManager.getResourceStore();
                    if (metadataStore.isWriteable()) {
                        for (Iterator<WorkspaceResource> it = metadataStore.getResourceIterator(moduleName); it.hasNext(); ) {
                            WorkspaceResource metadataResource = it.next();
                            ResourceIdentifier identifier = metadataResource.getIdentifier();
                            CALFeatureName featureName = (CALFeatureName)identifier.getFeatureName();
                            Locale locale = LocalizedResourceName.localeOf(identifier.getResourceName());
                            if (getWorkspace().getMetaModule(featureName.toModuleName()) != null) {
                                CALFeatureMetadata metadata = getWorkspace().getMetadata(featureName, locale);
                                if (!getWorkspace().saveMetadata(metadata)) {
                                    System.out.println("Error saving metadata for " + featureName + " for " + (locale == null ? "default" : locale.toString()) + " locale.");
                                }
                            }
                        }
                    } else {
                        System.out.println("Module " + moduleName + " comes from a read-only store.");
                    }
                }
                System.out.println("Finished.");
            }
        };
       
        loadSaveAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_LOADSAVEMETADATA));
        return loadSaveAction;
    }

    /**
     * @return the Action which dumps a report of each gem whose argument names (in metadata)
     *          are not consistent with its
     */
    private Action getDumpInconsistententArgumentMetadataAction() {
        Action dumpAction = new AbstractAction(getResourceString("DumpInconsistentArgumentMetadata")) {
            private static final long serialVersionUID = 2301791099324811915L;

            public void actionPerformed(ActionEvent evt) {
                System.out.println("Dumping gems in the "+perspective.getWorkingModule().getName() +" module w/inconsistent metadata argument names...");
               
                Set<GemEntity> gemSet = perspective.getVisibleGemEntities(perspective.getWorkingModule());
                for (final GemEntity gemEntity : gemSet) {
                    FunctionalAgentMetadata[] metadataArray = gemEntity.getMetadataForAllLocales();
                   
                    for (final FunctionalAgentMetadata metadata : metadataArray) {
                        ArgumentMetadata[] argMetadata = metadata.getArguments();
                        int numNamedArgs = gemEntity.getNNamedArguments();
                        boolean inconsistent = false;
                       
                        // Step through the arguments until we find an inconsistency
                        for (int i = 0; i < numNamedArgs && i < argMetadata.length && inconsistent==false; i++) {
                            String nameFromCode = gemEntity.getNamedArgument(i);
                            String nameFromMetadata = argMetadata[i].getDisplayName();
                           
                            if (nameFromCode != null && nameFromMetadata != null && !nameFromCode.equals(nameFromMetadata)) {
                                inconsistent = true;
                            }
                        }
                       
                        // If we found an inconsistency, step through again to display the names of each arg
                        if (inconsistent) {
                            System.out.println(gemEntity.getName().getQualifiedName() + ":");
                           
                            for(int i = 0; i < Math.max(numNamedArgs, argMetadata.length); i++) {
                                String nameFromCode = i < numNamedArgs? gemEntity.getNamedArgument(i) : null;
                                String nameFromMetadata = i < argMetadata.length? argMetadata[i].getDisplayName() : null;
                               
                                System.out.println("    "+nameFromCode + " vs. " + nameFromMetadata);   
                            }
                        }
                    }
                }
           
                System.out.println("Finished");
            }
        };
       
        dumpAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPINCONSISTENTARGUMENTMETADATA));
        return dumpAction;
    }
   
    /**
     * @return the Action which generates HTML documentation from CALDoc (and metadata).
     */
    private Action getGenerateCALDocDocumentationAction() {
       
        Action lintAction = new AbstractAction(getResourceString("GenerateCALDocDocumentation")) {
            private static final long serialVersionUID = 6391638158996429084L;

            public void actionPerformed(ActionEvent evt) {
               
                CALDocGenerationDialog dialog = new CALDocGenerationDialog(GemCutter.this, workspaceManager.getWorkspace());
                centerWindow(dialog);
                dialog.setVisible(true);
               
                if(dialog.isOKSelected()) {
                    Logger logger = Logger.getAnonymousLogger();
                    logger.setLevel(Level.FINEST);
                    logger.setUseParentHandlers(false);
                   
                    SimpleConsoleHandler handler = new SimpleConsoleHandler();
                    handler.setLevel(Level.FINE);
                    logger.addHandler(handler);
                   
                    doGenerateCALDocDocumentationAction(dialog.getConfiguration(logger, workspaceManager));
                }
            }
        };
       
        lintAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DUMPLINTWARNINGS));
        return lintAction;
    }
   
    /**
     * Generates CALDoc documentation based on the specified configuration.
     * @param configuration options for the documentation generator.
     */
    private void doGenerateCALDocDocumentationAction(final HTMLDocumentationGeneratorConfiguration configuration) {
       
        // Since this is a long-running operation, we handle it this way:
        // - we launch a separate thread to perform the actual documentation generation
        // - we use a modal dialog that cannot be closed to indicate to the user that the GemCutter is busy
        //
        // The documentation generation thread would then close the modal dialog when it is done, and unblocking
        // the UI in the process.
       
        // Setup the modal dialog
        String message = getResourceString("CALDoc_PleaseWaitDialogMessage");
        JOptionPane optionPane = new JOptionPane(message, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[0]);
       
        String dialogTitle = getResourceString("CALDoc_PleaseWaitDialogTitle");
        final JDialog dialog = optionPane.createDialog(this, dialogTitle);
       
        dialog.setModal(true);
        dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        centerWindow(dialog);
       
        // Create the documentation generation thread
        Thread thread = new Thread() {
            public void run() {
                try {
                    System.out.println("Generating CALDoc documentation.");
                    long before = System.currentTimeMillis();
                    CALDocTool.run(workspaceManager, configuration);
                    long after = System.currentTimeMillis();
                    System.out.println("finished in " + ((after - before) / 1000.0) + " seconds");
                    System.out.println("done.");
                } finally {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            dialog.dispose();
                        }
                    });
                }
            }
        };
       
        // start the thread then launch the modal dialog (which will be closed by the thread when it is done)
        thread.start();
        dialog.setVisible(true);
    }
   
    /**
     * @return the action which dumps orphaned metadata to the console.
     */
    private Action dumpOrphanedMetadataAction() {
        Action dumpOrphanedMetadataAction = new AbstractAction(getResourceString("FindOrphans")) {
            private static final long serialVersionUID = 1670196195751082059L;

            public void actionPerformed(ActionEvent evt) {
                getWorkspaceManager().dumpOrphanedMetadata();
            }
        };
       
        dumpOrphanedMetadataAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_FINDORPHANS));
        return dumpOrphanedMetadataAction;
    }
   
    /**
     * Return the EditMenu property value.
     * @return JMenu
     */
    private JMenu getEditMenu() {
        if (editMenu == null) {
            try {
                editMenu = new JMenu();
                editMenu.setName("EditMenu");
                editMenu.setText(getResourceString("EditMenu"));
                editMenu.setMargin(new Insets(2, 0, 2, 0));
                editMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_EDIT_MENU);
               
                editMenu.add(getUndoMenuItem());
                editMenu.add(getRedoMenuItem());
                editMenu.addSeparator();
                editMenu.add(makeNewMenuItem(getCutAction()));
                editMenu.add(makeNewMenuItem(getCopyAction()));
                JMenu copySpecialMenu = getCopySpecialMenu();
                copySpecialMenu.add(makeNewMenuItem(getCopySpecialImageAction()));
                copySpecialMenu.add(makeNewMenuItem(getCopySpecialTextAction()));
                copySpecialMenu.add(makeNewMenuItem(getCopySpecialTargetSourceAction()));
                editMenu.add(copySpecialMenu);
                editMenu.add(makeNewMenuItem(getPasteAction()));
               
                editMenu.addSeparator();
                editMenu.add(makeNewMenuItem(getDeleteAction()));
                editMenu.add(makeNewMenuItem(getSelectAllAction()));
                editMenu.add(makeNewMenuItem(getSearchAction()));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return editMenu;
    }
   
    /**
     * Return the action that handles undo
     * @return Action
     */
    private Action getUndoAction() {
       
        if (undoAction == null) {
            try {
                undoAction = new AbstractAction(getResourceString("Undo"),
                                                new ImageIcon(getClass().getResource("/Resources/undo.gif"))) {
                                                           
                    private static final long serialVersionUID = 3825977644698848717L;

                    public void actionPerformed(ActionEvent evt) {
                        undo();                       
                    }
                };
               
                undoAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_UNDO));
                undoAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_UNDO);
                undoAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("UndoToolTip"));
                undoAction.putValue(ACTION_BUTTON_IS_DROP_PARENT_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return undoAction;
    }

    /**
     * Return the undo button.
     * We need access to this button in order to know where to place the undo drop down menu
     * @return JButton the undo button
     */
    private JButton getUndoButton() {
        if (undoButton == null) {
            try {
                undoButton = makeNewButton(getUndoAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return undoButton;
    }
   
    /**
     * Return the action that handles the undo drop down menu.
     * @return Action
     */
    private Action getUndoDropDownAction() {
       
        if (undoDropDownAction == null) {
            try {
                undoDropDownAction = new AbstractAction (getResourceString("Undo"),
                                                new ImageIcon(getClass().getResource("/Resources/dropdownarrow.gif"))) {
                                                           
                    private static final long serialVersionUID = -9048917705735426064L;

                    public void actionPerformed(ActionEvent evt) {

                        displayUndoDropDownMenu();                       
                    }
                };
               
                undoDropDownAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("UndoDropDownToolTip"));
                undoDropDownAction.putValue(ACTION_BUTTON_IS_DROP_CHILD_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return undoDropDownAction;
    }

    /**
     * Return the undo drop down button.
     * We need access to this button in order to enable/disable it when the associated undo action is enabled/disabled.
     * @return JButton the undo drop down button
     */
    private JButton getUndoDropDownButton() {
        if (undoDropDownButton == null) {
            try {
                undoDropDownButton = makeNewButton(getUndoDropDownAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return undoDropDownButton;
    }
   
    /**
     * Return the undo menu item.
     * We need access to this menu item in order to change its text according to the current undoable action.
     * @return JMenuItem the undo menu item
     */
    private JMenuItem getUndoMenuItem() {
        if (undoMenuItem == null) {
            try {
                undoMenuItem = makeNewMenuItem(getUndoAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return undoMenuItem;
    }
   
    /**
     * Return the action that handles redo
     * @return Action
     */
    private Action getRedoAction() {
       
        if (redoAction == null) {
            try {
                redoAction = new AbstractAction(getResourceString("Redo"),
                                                new ImageIcon(getClass().getResource("/Resources/redo.gif"))) {
                                                           
                    private static final long serialVersionUID = 2949676218178316616L;

                    public void actionPerformed(ActionEvent evt) {
                           
                        redo();                           
                        getTableTopPanel().revalidateValueGemPanels();    
                    }
                };
               
                redoAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_REDO));
                redoAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_REDO);
                redoAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RedoToolTip"));
                redoAction.putValue(ACTION_BUTTON_IS_DROP_PARENT_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return redoAction;
    }

    /**
     * Return the redo button.
     * We need access to this button in order to know where to place the redo drop down menu
     * @return JButton the redo button
     */
    private JButton getRedoButton() {
        if (redoButton == null) {
            try {
                redoButton = makeNewButton(getRedoAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return redoButton;
    }
   
    /**
     * Return the action that handles the redo drop down menu.
     * @return Action
     */
    private Action getRedoDropDownAction() {
       
        if (redoDropDownAction == null) {
            try {
                redoDropDownAction = new AbstractAction (getResourceString("Redo"),
                                                new ImageIcon(getClass().getResource("/Resources/dropdownarrow.gif"))) {
                                                           
                    private static final long serialVersionUID = -1753196873058783562L;

                    public void actionPerformed(ActionEvent evt) {

                        displayRedoDropDownMenu();                       
                    }
                };
               
                redoDropDownAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RedoDropDownToolTip"));
                redoDropDownAction.putValue(ACTION_BUTTON_IS_DROP_CHILD_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return redoDropDownAction;
    }

    /**
     * Return the redo drop down button.
     * We need access to this button in order to enable/disable it when the associated redo action is enabled/disabled.
     * @return JButton the redo drop down button
     */
    private JButton getRedoDropDownButton() {
        if (redoDropDownButton == null) {
            try {
                redoDropDownButton = makeNewButton(getRedoDropDownAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return redoDropDownButton;
    }
   
    /**
     * Return the redo menu item.
     * We need access to this menu item in order to change its text according to the current redoable action.
     * @return JMenuItem the redo menu item
     */
    private JMenuItem getRedoMenuItem() {
        if (redoMenuItem == null) {
            try {
                redoMenuItem = makeNewMenuItem(getRedoAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return redoMenuItem;
    }
   
    /**
     * Return the Copy As menu item.
     * We need access to this menu item in order to change its text according to the current selection.
     * @return JMenu
     */
    JMenu getCopySpecialMenu() {
        if (copySpecialMenu == null) {
            try {
                copySpecialMenu = (JMenu)UIUtilities.fixMenuItem(new JMenu(getResourceString("CopySpecial")));
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return copySpecialMenu;
    }

    /**
     * Returns the action that handles the "Cut" edit functionality.
     * @return Action
     */
    Action getCutAction() {
       
        if (cutAction == null) {
            try {
                cutAction = new AbstractAction(getResourceString("Cut"),
                                            new ImageIcon(getClass().getResource("/Resources/cut.gif"))) {
                                                           
                    private static final long serialVersionUID = -3423091075403099772L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // defer to the table top to do the grunt work
                        getTableTop().doCutUserAction();
                    }
                };
               
                cutAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_CUT));
                cutAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_CUT);
                cutAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("CutToolTip"));
               
                // For now this is disabled, but it will be enabled as necessary
                cutAction.setEnabled(false);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return cutAction;
    }

    /**
     * Returns the action that handles the "Copy" edit functionality.
     * @return Action
     */
    Action getCopyAction() {
       
        if (copyAction == null) {
            try {
                copyAction = new AbstractAction(getResourceString("Copy"),
                                            new ImageIcon(getClass().getResource("/Resources/copy.gif"))) {
                                                           
                    private static final long serialVersionUID = -7627599000753845358L;

                    public void actionPerformed(ActionEvent evt) {
                        // The table top will do the hard part
                        getTableTop().doCopyUserAction();
                    }
                };
               
                copyAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_COPY));
                copyAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_COPY);
                copyAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("CopyToolTip"));
               
                // For now this is disabled
                copyAction.setEnabled(false);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return copyAction;
    }
   
    /**
     * Returns the action that handles the "Copy Special - Image" edit functionality.
     * @return Action
     */
    public Action getCopySpecialImageAction() {
        if (copySpecialImageAction == null) {
            try {
                copySpecialImageAction = new AbstractAction(getResourceString("CopySpecialTableTopImage")) {
                                                           
                    private static final long serialVersionUID = -1565594567048032517L;

                    public void actionPerformed(ActionEvent evt) {
                        // The table top will do the hard part
                        getTableTop().doCopySpecialImageAction();
                    }
                };
               
                copySpecialImageAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_COPY_AS_IMAGE));
                copySpecialImageAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_COPY_AS_IMAGE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return copySpecialImageAction;
    }
   
    /**
     * Returns the action that handles the "Copy Special - CodeGem" edit functionality.
     * @return Action
     */
    public Action getCopySpecialTextAction() {
        if (copySpecialTextAction == null) {
            try {
                copySpecialTextAction = new AbstractAction(getResourceString("CopySpecialCodeGems")) {
                                                           
                    private static final long serialVersionUID = -3867200478494748905L;

                    public void actionPerformed(ActionEvent evt) {
                        // The table top will do the hard part
                        getTableTop().doCopySpecialTextAction();
                    }
                };
               
                copySpecialTextAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_COPY_AS_TEXT));
                copySpecialTextAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_COPY_AS_TEXT);
               
                // For now this is disabled, but it will be enabled as necessary
                copySpecialTextAction.setEnabled(false);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return copySpecialTextAction;
    }
   
    /**
     * Returns the action that handles the "Copy Special - Target Source" edit functionality.
     * @return Action
     */
    public Action getCopySpecialTargetSourceAction() {
        if (copySpecialTargetSourceAction == null) {
            try {
                copySpecialTargetSourceAction = new AbstractAction(getResourceString("CopySpecialTargetSource")) {
                                                           
                    private static final long serialVersionUID = 2828553746957528006L;

                    public void actionPerformed(ActionEvent evt) {
                        // The table top will do the hard part
                        getTableTop().doCopySpecialTargetSourceAction();
                    }
                };
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return copySpecialTargetSourceAction;
    }

    /**
     * Simply calls the tabletop paste function with the items currently on the clipboard
     */
    void pasteFromClipboard() {
        Transferable displayedGemSelection = getClipboard().getContents(this);
        getTableTop().doPasteUserAction(displayedGemSelection);
    }

    /**
     * Returns that action that handles the "Paste" edit functionality
     * @return Action
     */
    Action getPasteAction() {
       
        if (pasteAction == null) {
            try {
                pasteAction = new AbstractAction(getResourceString("Paste"),
                                            new ImageIcon(getClass().getResource("/Resources/paste.gif"))) {
                                                           
                    private static final long serialVersionUID = -7322078839392049685L;

                    public void actionPerformed(ActionEvent evt) {
                        pasteFromClipboard();
                    }
                };
               
                pasteAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_PASTE));
                pasteAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_PASTE);
                pasteAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("PasteToolTip"));
               
                // For now this is disabled
                pasteAction.setEnabled(false);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return pasteAction;
    }

    /**
     * Returns the action that is responsible for deleting gems.
     * Intellicut will be stopped and any selected gems will be deleted.
     * @return Action
     */
    Action getDeleteAction() {
       
        if (deleteAction == null) {
            try {
                deleteAction = new AbstractAction(getResourceString("Delete")) {
                                                           
                    private static final long serialVersionUID = 3597419894568891083L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        getTableTop().handleDeleteSelectedGemsGesture();
                    }
                };
               
                deleteAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DELETE));
                deleteAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_DELETE);
                deleteAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("DeleteToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return deleteAction;
    }

    /**
     * Returns the action that is responsible for selecting all the gems on the TableTop.
     * @return Action
     */
    private Action getSelectAllAction() {
       
        if (selectAllAction == null) {
            try {
                selectAllAction = new AbstractAction(getResourceString("SelectAll")) {
                                                           
                    private static final long serialVersionUID = 2696834229302548654L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        getTableTop().selectAllGems();
                    }
                };
               
                selectAllAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_SELECT_ALL));
                selectAllAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_SELECT_ALL);
                selectAllAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SelectAllToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return selectAllAction;
    }

    /**
     * Returns the action that handles searching for qualified names
     * @return Action
     */
    private Action getSearchAction() {
       
        if (searchAction == null) {
            try {
                searchAction = new AbstractAction(getResourceString("Search"),
                                            new ImageIcon(getClass().getResource("/Resources/find.gif"))) {
                                                           
                    private static final long serialVersionUID = 2462653760837686957L;

                    public void actionPerformed(ActionEvent evt) {
                        showSearchDialog();
                    }
                };
               
                searchAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_SEARCH));
                searchAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_SEARCH);
                searchAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SearchToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return searchAction;
    }

    /**
     * Return the ViewMenu property value.
     * @return JMenu
     */
    private JMenu getViewMenu() {
        if (viewMenu == null) {
            try {
                viewMenu = new JMenu();
                viewMenu.setName("ViewMenu");
                viewMenu.setText(getResourceString("ViewMenu"));
                viewMenu.setMargin(new Insets(2, 0, 2, 0));
                viewMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_VIEW_MENU);
               
                JCheckBoxMenuItem toolbarItem = makeNewCheckBoxMenuItem(getViewToolbarAction());
                toolbarItem.setSelected(getToolBarPane().isVisible());
                viewMenu.add(toolbarItem);
               
                JCheckBoxMenuItem statusbarItem = makeNewCheckBoxMenuItem(getViewStatusbarAction());
                statusbarItem.setSelected(getStatusBarPane().isVisible());
                viewMenu.add(statusbarItem);

                JCheckBoxMenuItem overviewItem = makeNewCheckBoxMenuItem(getViewOverviewAction());
                overviewItem.setSelected(getBrowserOverviewSplit().getBottomComponent() != null);
                viewMenu.add(overviewItem);
               
                JCheckBoxMenuItem explorerItem = makeNewCheckBoxMenuItem(getViewExplorerAction());
                explorerItem.setSelected(getTableTopExplorer().isEnabled());
                viewMenu.add(explorerItem);

                JCheckBoxMenuItem argumentExplorerItem = makeNewCheckBoxMenuItem(getViewArgumentExplorerAction());
                argumentExplorerItem.setSelected(getArgumentExplorer().isEnabled());
                viewMenu.add(argumentExplorerItem);

                JCheckBoxMenuItem dockingItem = makeNewCheckBoxMenuItem(getTargetDockingAction());
                dockingItem.setSelected(false);
                viewMenu.add(dockingItem);

                viewMenu.addSeparator();
                JCheckBoxMenuItem debugOutputMenuItem = makeNewCheckBoxMenuItem(getDebugOutputAction());
                debugOutputMenuItem.setSelected(((Boolean)getDebugOutputAction().getValue("InDebugOutputMode")).booleanValue());
                viewMenu.add(debugOutputMenuItem);
                viewMenu.add(getViewPropertiesBrowserMenuItem());               
                  
                viewMenu.addSeparator();
                viewMenu.add(makeNewMenuItem(getArrangeGraphAction()));
                viewMenu.add(makeNewMenuItem(getFitTableTopAction()));

                viewMenu.addSeparator();
                viewMenu.add(makeNewMenuItem(getPreferencesAction()));

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewMenu;
    }
   
    /**
     * @return the menuitem for viewing the properties browser (aka CAL Navigator).
     */
    private JMenuItem getViewPropertiesBrowserMenuItem() {

        Action viewAction = new AbstractAction(getResourceString("ViewPropertiesBrowser")) {
           
            private static final long serialVersionUID = 5999355987664811572L;

            public void actionPerformed(ActionEvent e) {
                navigatorOwner.displayNavigator(true);
            }
        };
       
        viewAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewPropertiesBrowserToolTip"));
       
        return makeNewMenuItem(viewAction);
    }

    /**
     * Return the action that handles showing/hiding the toolbar.
     * @return Action
     */
    private Action getViewToolbarAction() {
       
        if (viewToolbarAction == null) {
            try {
                viewToolbarAction = new AbstractAction(getResourceString("ViewToolbar")) {
                                                           
                    private static final long serialVersionUID = 2265840065539915678L;

                    public void actionPerformed(ActionEvent evt) {
                        viewToolBar();
                    }
                };
               
                viewToolbarAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_VIEW_TOOLBAR));
                viewToolbarAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewToolbarToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewToolbarAction;
    }

    /**
     * Return the action that handles showing/hiding the Statusbar
     * @return Action
     */
    private Action getViewStatusbarAction() {
       
        if (viewStatusbarAction == null) {
            try {
                viewStatusbarAction = new AbstractAction(getResourceString("ViewStatusbar")) {
                                                           
                    private static final long serialVersionUID = 2212859268617591676L;

                    public void actionPerformed(ActionEvent evt) {
                        viewStatusBar();
                    }
                };
               
                viewStatusbarAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_VIEW_STATUSBAR));
                viewStatusbarAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewStatusbarToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewStatusbarAction;    
    }

    /**
     * Return the action that handles showing/hiding the Overview view.
     * @return Action
     */
    private Action getViewOverviewAction() {
       
        if (viewOverviewAction == null) {
            try {
                viewOverviewAction = new AbstractAction(getResourceString("ViewOverview")) {
                    private static final long serialVersionUID = -4767355716120627259L;

                    public void actionPerformed(ActionEvent evt) {
                        toggleOverview();
                    }
                };
               
                viewOverviewAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_VIEW_OVERVIEW));
                viewOverviewAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewOverviewToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewOverviewAction;
    }
   
    /**
     * Toggle whether the overview is visible.
     */
    private void toggleOverview() {
       
        JSplitPane browserOverviewSplit = getBrowserOverviewSplit();
        boolean show = browserOverviewSplit.getBottomComponent() == null;
        if (show) {
            browserOverviewSplit.setBottomComponent(getOverviewPanel());
            browserOverviewSplit.setDividerLocation(.5);
        } else {
            browserOverviewSplit.setBottomComponent(null);
            browserOverviewSplit.setDividerLocation(.8);
        }
       
        browserOverviewSplit.validate();
    }
   
    /**
     * Return the action that handles showing/hiding the Explorer view.
     * @return Action
     */
    private Action getViewExplorerAction() {
       
        if (viewExplorerAction == null) {
            try {
                viewExplorerAction = new AbstractAction(getResourceString("ViewExplorer")) {
                                                           
                    private static final long serialVersionUID = -5558386400807002054L;

                    public void actionPerformed(ActionEvent evt) {
                        toggleExplorer();
                    }
                };
               
                viewExplorerAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewExplorerToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewExplorerAction;
    }

    /**
     * Return the action that handles showing/hiding the Argument Explorer view.
     * @return Action
     */
    private Action getViewArgumentExplorerAction() {
       
        if (viewArgumentExplorerAction == null) {
            try {
                viewArgumentExplorerAction = new AbstractAction(getResourceString("ViewArgumentExplorer")) {
                                                           
                    private static final long serialVersionUID = 5447972693154054572L;

                    public void actionPerformed(ActionEvent evt) {
                        toggleArgumentExplorer();
                    }
                };
               
                viewArgumentExplorerAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ViewArgumentExplorerToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return viewArgumentExplorerAction;
    }
   
    /**
     * Toggle whether the arguments explorer is visible.
     */
    private void toggleArgumentExplorer() {
        if (getArgumentExplorer().isEnabled()) {
            getArgumentExplorer().setEnabled(false);
            setArgumentsTabVisible(false);
           
        } else {
            getArgumentExplorer().setEnabled(true);
            setArgumentsTabVisible(true);
        }
       
        checkBrowserOverviewDivider();
    }
   
    /**
     * Toggle whether the explorer is visible.
     */
    private void toggleExplorer() {
        if (getTableTopExplorer().isEnabled()) {
            getTableTopExplorer().setEnabled(false);
            setExplorerTabVisible(false);
           
        } else {
            getTableTopExplorer().setEnabled(true);
            setExplorerTabVisible(true);
        }
        checkBrowserOverviewDivider();
    }

    /**
     * Check that the divider between the browser and the overview panel is properly positioned.
     */
    private void checkBrowserOverviewDivider() {
        JSplitPane browserExplorerSplit = getExplorerBrowserSplit();
        int dividerSize = browserExplorerSplit.getDividerSize();
        if (dividerSize == 0) {
            // The divider is not showing. Show the divider if there are tabs.
            if (getExplorerArgumentsPane().getTabCount() > 0) {
                browserExplorerSplit.setDividerSize(3);
                browserExplorerSplit.setDividerLocation(0.5);
            }
        } else {
            // The divider is showing.  Hide the divider if there are no tabs.
            if (getExplorerArgumentsPane().getTabCount() == 0) {
                browserExplorerSplit.setDividerSize(0);
                browserExplorerSplit.setDividerLocation(0.0);
            }
        }
        getBrowserOverviewSplit().validate();
    }

    /**
     * Returns the action responsible for docking/undocking the target.
     * @return Action
     */
    private Action getTargetDockingAction() {
       
        if (targetDockingAction == null) {
            try {
                targetDockingAction = new AbstractAction (getResourceString("TargetDocking")) {
                                                           
                    private static final long serialVersionUID = 6373002342851298749L;

                    public void actionPerformed(ActionEvent evt) {
                        throw new UnsupportedOperationException("this feature is not yet implemented");
                    }
                };
               
                targetDockingAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_TARGET_DOCKING));
                targetDockingAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("TargetDockingToolTip"));
               
                // Disable this for now
                targetDockingAction.setEnabled(false);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return targetDockingAction;
    }

    /**
     * Return the action that handles the Arrange Graph functionality.
     * @return Action
     */
    private Action getArrangeGraphAction() {
       
        if (arrangeGraphAction == null) {
            try {
                arrangeGraphAction = new AbstractAction (getResourceString("ArrangeGraph")) {
                                                           
                    private static final long serialVersionUID = -8694933314486819543L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        TableTop tTop = getTableTop();
                        tTop.doTidyTableTopAction();
                        getTableTopPanel().repaint ();
                    }
                };
               
                arrangeGraphAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ARRANGE_GRAPH));
                arrangeGraphAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_ARRANGE_GRAPH);
                arrangeGraphAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ArrangeGraphToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return arrangeGraphAction; 
    }
   
    /**
     * Return the action that handles the Fit TableTop functionality.
     * @return Action
     */
    private Action getFitTableTopAction() {
        // Define it only if it was previously undefined
        if (fitTableTopAction == null) {
           
            fitTableTopAction = new AbstractAction (getResourceString("FitTableTop")) {
                private static final long serialVersionUID = 7646412345327942710L;

                public void actionPerformed(ActionEvent evt) {
                    TableTop tableTop = getTableTop();
                    tableTop.doShrinkTableTopUserAction();
                    // have to refresh afterwards!
                    getTableTopPanel().repaint();
                }
               
            };
            fitTableTopAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_FIT_TABLETOP);
            fitTableTopAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_FIT_TABLETOP));
            fitTableTopAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("FitTableTopToolTip"));
           
        }
        return fitTableTopAction;
    }
           
    /**
     * Return the action that handles that pops up the view preferences dialog
     * @return Action
     */
    private Action getPreferencesAction() {
        // Define it only if it was previously undefined
        if (preferencesAction == null) {
           
            preferencesAction = new AbstractAction (getResourceString("Preferences")) {
                private static final long serialVersionUID = 1931027116634034114L;

                public void actionPerformed(ActionEvent evt) {
                    getPreferencesDialog().setVisible(true);
                }
               
            };
           
            preferencesAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_PREFERENCES);
            preferencesAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_PREFERENCES));
            preferencesAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("Preferences"));
           
        }
        return preferencesAction;
    }   

    /**
     * Return the action that handles the Debug Output functionality.
     * @return Action
     */
    private Action getDebugOutputAction() {
       
        if (debugOutputAction == null) {
            try {
                debugOutputAction = new AbstractAction (getResourceString("DebugOutput")) {
                                                           
                    private static final long serialVersionUID = 587492833164610681L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // This just toggles the debug output mode
                        boolean oldValue = ((Boolean)getValue("InDebugOutputMode")).booleanValue();
                        putValue("InDebugOutputMode", Boolean.valueOf(!oldValue));
                    }
                };
               
                debugOutputAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DEBUG_OUTPUT));
                debugOutputAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("DebugOutputToolTip"));
               
                // Get the action to remember if we are in debug output mode
                debugOutputAction.putValue("InDebugOutputMode", Boolean.FALSE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return debugOutputAction;  
    }
   
    /**
     * Returns the menu item for adding new reflectors.
     * @return JMenuItem
     */
    private JMenuItem getAddReflectorGemMenuItem() {
        if (addReflectorGemMenuItem == null) {
            addReflectorGemMenuItem = makeNewMenuItem(getAddReflectorGemAction());
            addReflectorGemMenuItem.setEnabled(false);
        }
       
        return addReflectorGemMenuItem;
    }
   
    /**
     * Return the Insert menu property value.
     * @return JMenu
     */
    private JMenu getInsertMenu() {
       
        if (insertMenu == null) {
            try {
                insertMenu = new JMenu();
                insertMenu.setName("InsertMenu");
                insertMenu.setText(getResourceString("InsertMenu"));
                insertMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_INSERT_MENU);
               
                insertMenu.add(makeNewMenuItem(getAddGemAction()));
                insertMenu.add(makeNewMenuItem(getAddValueGemAction()));
                insertMenu.add(makeNewMenuItem(getAddCodeGemAction()));
               
                insertMenu.addSeparator();
               
                insertMenu.add(makeNewMenuItem(getAddCollectorGemAction()));
                insertMenu.add(getAddReflectorGemMenuItem());
               
                // Create a JMenu using the reflector action and add a menu listener that will
                // update the menu items in the menu just before it is displayed
                JMenu reflectorMenu = makeNewMenu(null);
                reflectorMenu.setAction(getAddReflectorGemDropDownAction());
                reflectorMenu.setToolTipText(null);
                reflectorMenu.setIcon(null);
                reflectorMenu.addMenuListener(new MenuListener() {
                   
                    public void menuSelected(MenuEvent evt) {
                        JMenu menu = (JMenu)evt.getSource();
                        prepareAddReflectorPopup(menu.getPopupMenu());
                    }
                   
                    public void menuDeselected(MenuEvent evt) {}
                   
                    public void menuCanceled(MenuEvent evt) {}
                });
               
                insertMenu.add(reflectorMenu);
               
                insertMenu.addSeparator();
               
                insertMenu.add(makeNewMenuItem(getAddRecordCreationGemAction()));
                insertMenu.add(makeNewMenuItem(getAddRecordFieldSelectionGemAction()));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return insertMenu;
    }
   
    /**
     * @return the generate menu with an entry for each installed gem factory
     */
    private JMenu getGenerateMenu() {
       
        if (generateMenu == null) {
           
            generateMenu = new JMenu();
            generateMenu.setText(getResourceString("GenerateMenu"));
            generateMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_GENERATE_MENU);
           
            List<Class<GemGenerator>> factoryClasses = getFactoryClasses();
           
            for (final Class<GemGenerator> factoryClass : factoryClasses) {

                try {               
                    GemGenerator factoryInstance = factoryClass.getConstructor(new Class[0]).newInstance(new Object[0]);
                    generateMenu.add(makeNewMenuItem(getFactoryAction(factoryInstance)));
                   
                } catch (NoSuchMethodException ex) {
                    System.out.println("Warning: factory class does not define default constructor: " + factoryClass);
                   
                } catch (IllegalAccessException ex) {
                    System.out.println("Warning: factory class does not have visible default constructor: " + factoryClass);
                   
                } catch (Exception ex) {
                    System.out.println("Warning: exception instantiating factory class: " + factoryClass + " - " + ex);
                }
            }
           
            if (generateMenu.getMenuComponentCount() > 0) {
                generateMenu.addSeparator();
            }
           
            generateMenu.add(makeNewMenuItem(getGenerateCALDocDocumentationAction()));
        }
       
        return generateMenu;
    }
   
    /**
     * @param factory the factory this action is for
     * @return the action that launches the given gem factory
     */
    private Action getFactoryAction(final GemGenerator factory) {

        Action factoryAction = new AbstractAction(factory.getGeneratorMenuName(), factory.getGeneratorIcon()) {
           
            private static final long serialVersionUID = -1100808467474770879L;

            public void actionPerformed(ActionEvent e) {

                GemGenerator.GeneratedDefinitions definitions = null;
               
                // Launch the factory. Keep the GUI locked while it's running.
                enterGUIState(GUIState.LOCKED_DOWN);
               
                try {
                    definitions = factory.launchGenerator(GemCutter.this,
                                                        getPerspective(),
                                                        getValueRunner(),
                                                        getValueEditorManager(),
                                                        getTypeChecker());
                } finally {
                    enterGUIState(GUIState.EDIT);
                }
               
                if (definitions == null) {
                    return;
                }
                               
                String lastGemName = null;

                Map<String, String> sourceElementMap = definitions.getSourceElementMap();
                if (sourceElementMap != null && !sourceElementMap.isEmpty()) {

                    // Save all gems to the current module.
                    for (final Map.Entry<String, String> mapEntry : sourceElementMap.entrySet()) {
                        String unqualifiedGemName = mapEntry.getKey();
                        String gemSource = mapEntry.getValue();

                        QualifiedName qualifiedGemName = QualifiedName.make(getWorkingModuleName(), unqualifiedGemName);
                        Status saveStatus = getWorkspace().saveEntity(qualifiedGemName, gemSource, null, null);
                        lastGemName = unqualifiedGemName;

                        if (saveStatus.getSeverity() == Status.Severity.ERROR) {
                            String errTitle = getResourceString("CannotSaveDialogTitle");
                            String errMessage = getResourceString("ErrorSavingGemDefinition");

                            DetailsDialog dialog = new DetailsDialog(GemCutter.this, errTitle, errMessage,
                                    saveStatus.getDebugMessage(), DetailsDialog.MessageType.ERROR);

                            dialog.doModal();
   
                            // Don't bother trying to save the other gems.
                            break;
                        }
                    }
                }

                Status generationStatus = new Status("Generation status.");
               
                SourceModel.ModuleDefn moduleDefn = definitions.getModuleDefn();
                if (moduleDefn != null) {
                    // There was a generated module defn.
                   
                    ModuleName generatedModuleName = SourceModel.Name.Module.toModuleName(moduleDefn.getModuleName());

                    // Can't overwrite the prelude.
                    if (generatedModuleName.equals(CAL_Prelude.MODULE_NAME)) {
                        String errTitle = getResourceString("CannotSaveDialogTitle");
                        String errMessage = getResourceString("CannotClobberPrelude");
                        JOptionPane.showMessageDialog(GemCutter.this, errMessage, errTitle, JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                   
                    // Check whether the module name exists, and if so confirm that the user wished to clobber.
                    // This goes to the workspace source manager -- so it's a bit hacky.
                    // We can't just ask the workspace if it has the module though, since it might be the case where the workspace isn't
                    //   using a module in the nullary case.
                    if (getWorkspace().getSourceManager(generatedModuleName).getResourceStore().hasFeature(
                            new ResourceName(CALFeatureName.getModuleFeatureName(generatedModuleName)))) {
                       
                        // TODOEL: Ideally, if the user does not want to clobber the existing module, we should
                        //   go back to the dialog with the existing inputs.
                       
                        String warningTitle = getResourceString("WarningDialogTitle");
                        String errMessage = getResourceString("ModuleExistsWarning");
                        int continueChoice = JOptionPane.showConfirmDialog(GemCutter.this, errMessage, warningTitle, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
                        if (continueChoice != JOptionPane.OK_OPTION) {
                            return;
                        }
                    }
                   
                   
                    // *** HACK ***
                    // *** Fix me when we can handle modules without vault info.
                   
                    // Ok, now we have the generated module defn.  Where do we put it?
                    // It should go into the workspace, but the workspace can't handle modules without any vault info.
                    // So for now we create a temp file first, and add from there.

                    // We could use NonExistentVault instead, but that requires some work to implement StoredVaultElement.Module.
                   
                    String tmpDir = System.getProperty("java.io.tmpdir");
                    String fileName = generatedModuleName + ".cal";
                   
                    File tempFile = new File(tmpDir, fileName);
                    String sourceText = moduleDefn.toSourceText();
                    Writer writer = null;
                    try {
                        writer = new BufferedWriter(new FileWriter(tempFile));
                        writer.write(sourceText);
                    } catch (IOException ioe) {
                        generationStatus.add(new Status(Status.Severity.ERROR, "Error writing file: " + tempFile, ioe));

                    } finally {
                        if (writer != null) {
                            try {
                                writer.flush();
                                writer.close();
                            } catch (IOException ioe) {
                                // Not much we can do about this.
                            }
                        }
                    }
                   
                    // Add the module to the workspace.
                    // Note: this can result in errors in cases where the module is erroneous.
                    //  For instance, if generating a foreign import module, this can result in errors if the module refers to
                    //    foreign classes which are not on the classpath.
                    boolean addModuleAttemptSuccessful = false;
                    if (generationStatus.getSeverity().compareTo(Status.Severity.ERROR) < 0) {

                        SimpleCALFileVault simpleCALFileVault = SimpleCALFileVault.getSimpleCALFileVault(tempFile);
                       
                        // This call also calls recompileWorkspace(true).
                        if (simpleCALFileVault != null) {
                            handleAddModuleAttempt(simpleCALFileVault, generatedModuleName, -1, false);
                            addModuleAttemptSuccessful = true;
                        } else {
                            String details = getResourceString("CannotCreateSimpleCALFileVault");
                            showProblemsGeneratingModuleDialog(DetailsDialog.MessageType.ERROR, details);
                        }
                    }
                   
                    // Delete the tempFile.  Don't worry too much about if this fails..
                    tempFile.delete();
                   
                    // Select the module which was compiled, if any.
                    if (addModuleAttemptSuccessful && generationStatus.getSeverity().compareTo(Status.Severity.ERROR) < 0) {
                        getGemBrowser().getBrowserTree().selectDrawerNode(generatedModuleName);
                       
                        // Also display a message.
                        String statusMessage = GemCutterMessages.getString("SM_ModuleGenerated", generatedModuleName);
                        statusMessageManager.displayMessage(this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);

                    }
                   
                    if (generationStatus.getSeverity().compareTo(Status.Severity.WARNING) >= 0) {
                        String details = generationStatus.getDebugMessage();
                        DetailsDialog.MessageType messageType = generationStatus.getSeverity() == Status.Severity.WARNING ?
                                DetailsDialog.MessageType.WARNING : DetailsDialog.MessageType.ERROR;
                       
                        showProblemsGeneratingModuleDialog(messageType, details);
                    }
                   
                } else {
                    // No generated module defn.
               
                    // Recompile the workspace.
                    recompileWorkspace(true);
                   
                    if (lastGemName != null) {
                       
                        // Select the last gem that was saved in the GemBrowser.
                        GemEntity newEntity = perspective.resolveGemEntity(lastGemName);
                       
                        if (newEntity != null) {
                           
                            // The entity may be null if the recompile failed
                            // or the generated source contained some sort of error.
                            getGemBrowser().getBrowserTree().selectGemNode(newEntity);
                        }                   
                    }
                }
            }

            private void showProblemsGeneratingModuleDialog(DetailsDialog.MessageType messageType, String details) {
                String title = getResourceString("WindowTitle");
                String message = getResourceString("ProblemsGeneratingModule");
               
                DetailsDialog dialog = new DetailsDialog(GemCutter.this, title, message, details, messageType);
                dialog.doModal();
            }
        };
       
        return factoryAction;
    }
    /**
     * Reads all installed factory class names from the factory class file and returns
     * an array of Class objects for each class that can be found.
     * @return an array of factory class objects
     */
    private static List<Class<GemGenerator>> getFactoryClasses() {
       
        BufferedReader reader = null;
       
        try {
           
            List<Class<GemGenerator>> factoryClasses = new ArrayList<Class<GemGenerator>>();
           
            InputStream inputStream = GemCutter.class.getResourceAsStream("/gemGenerators.ini");
            reader = new BufferedReader(TextEncodingUtilities.makeUTF8Reader(inputStream));
           
            String className = reader.readLine();
           
            while (className != null) {
               
                try {
                    Class<GemGenerator> classForName = UnsafeCast.<Class<GemGenerator>>unsafeCast(Class.forName(className));
                    factoryClasses.add(classForName);
               
                } catch (ClassNotFoundException ex) {
                    System.out.println("Warning: factory class not found: " + className);
                }
               
                className = reader.readLine();
            }
           
            return factoryClasses;
       
        } catch (Exception ex) {
            System.out.println("Warning: exception reading factory class listing file: " + ex.getLocalizedMessage());
       
        } finally {
           
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                }
            }
        }
       
        return new ArrayList<Class<GemGenerator>>(0);
    }

    /**
     * Returns the action for adding a CodeGem.
     * @return Action
     */
    private Action getAddCodeGemAction() {
       
        if (addCodeGemAction == null) {
            try {
                addCodeGemAction = new AbstractAction (getResourceString("AddCodeGem"),
                                                new ImageIcon(getClass().getResource("/Resources/code.gif"))) {
                                                           
                    private static final long serialVersionUID = -6816895231478591066L;

                    public void actionPerformed(ActionEvent evt) {

                        // Add a Green (Code) Gem
                        DisplayedGem dGem = tableTop.createDisplayedCodeGem(new Point(10,10));
                       
                        // Set this as the Gem we're in the process of adding, and change GUI state
                        setAddingDisplayedGem(dGem);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                };
               
                addCodeGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_CODE_GEM));
                addCodeGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddCodeGemToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return addCodeGemAction;
    }
   
    /**
     * Returns the action for adding an Record Field Selection Gem.
     * @return Action
     */
    private Action getAddRecordFieldSelectionGemAction() {
       
        if (addRecordSelectionGemAction == null) {
            try {
                addRecordSelectionGemAction = new AbstractAction (getResourceString("AddRecordFieldSelectionGem"),
                                                    new ImageIcon(getClass().getResource("/Resources/recordFieldSelectionGem.gif"))) {
                   
                    private static final long serialVersionUID = 8305024624011682911L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // create and display Record Field Selection Gem
                        DisplayedGem dGem = tableTop.createDisplayedRecordFieldSelectionGem(new Point(10,10));
                       
                        // Set this as the Gem we're in the process of adding, and change GUI state
                        setAddingDisplayedGem(dGem);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                };
               
                addRecordSelectionGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_RECORD_FIELD_SELECTION_GEM));
                addRecordSelectionGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddRecordFieldSelectionGemToolTip"));

            } catch (Throwable exc) {
                handleException(exc);
            }
        }
       
        return addRecordSelectionGemAction;
    }
   
   
    /**
     * Returns the action for adding a RecordCreationGem
     * @return the action
     */  
    private Action getAddRecordCreationGemAction() {
       
        if (addRecordCreationGemAction  == null) {
            try {
                addRecordCreationGemAction = new AbstractAction (getResourceString("AddRecordCreationGem"),
                                                    new ImageIcon(getClass().getResource("/Resources/recordCreationGem.gif"))) {
                   

                    private static final long serialVersionUID = 6010375395496990909L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // create and display Record Field Selection Gem
                        DisplayedGem dGem = tableTop.createDisplayedRecordCreationGem(new Point(10,10));
                       
                        // Set this as the Gem we're in the process of adding, and change GUI state
                        setAddingDisplayedGem(dGem);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                };
               
                addRecordCreationGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_RECORD_CREATION_GEM));
                addRecordCreationGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddRecordCreationGemToolTip"));

            } catch (Throwable exc) {
                handleException(exc);
            }
        }
       
        return addRecordCreationGemAction;
    }

   
    /**
     * Returns the action for adding a ValueGem.
     * @return Action
     */
    private Action getAddValueGemAction() {
       
        if (addValueGemAction == null) {
            try {
                addValueGemAction = new AbstractAction (getResourceString("AddValueGem"),
                                                new ImageIcon(getClass().getResource("/Resources/constant.gif"))) {
                                                           
                    private static final long serialVersionUID = 6010375395496990909L;

                    public void actionPerformed(ActionEvent evt) {

                        // Add a Blue (Value) Gem
                        DisplayedGem dGem = tableTop.createDisplayedValueGem(new Point(10,10));
                       
                        // Set this as the Gem we're in the process of adding, and change GUI state
                        setAddingDisplayedGem(dGem);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                };
               
                addValueGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_VALUE_GEM));
                addValueGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddValueGemToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return addValueGemAction;
    }

    /**
     * Return the action for adding a CollectorGem
     * @return Action
     */
    private Action getAddCollectorGemAction() {
       
        if (addCollectorGemAction == null) {
            try {
                addCollectorGemAction = new AbstractAction (getResourceString("AddCollectorGem"),
                                                new ImageIcon(getClass().getResource("/Resources/collector.gif"))) {
                                                           
                    private static final long serialVersionUID = 6732017043606208724L;

                    public void actionPerformed(ActionEvent evt) {

                        // Add a Collector Gem
                        DisplayedGem dGem = getTableTop().createDisplayedCollectorGem(new Point(10,10), tableTop.getTargetCollector());
                       
                        // Set this as the Gem we're in the process of adding, and change GUI state
                        setAddingDisplayedGem(dGem);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                };
               
                addCollectorGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_COLLECTOR_GEM));
                addCollectorGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddCollectorGemToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return addCollectorGemAction;      
    }
   
    /**
     * Return the action for adding a new gem.
     * @return Action
     */
    private Action getAddGemAction() {
       
        if (addGemAction == null) {
       
            addGemAction = new AbstractAction (getResourceString("AddGem"),
                                                      new ImageIcon(getClass().getResource("/Resources/addNewGem.gif"))) {
                                                               
                private static final long serialVersionUID = 1517812122164096209L;

                public void actionPerformed(ActionEvent evt) {
   
                    // If the table top explorer has focus and can display intellicut, then
                    // display intellicut there. If that's not the case check if the table top
                    // panel has focus and let it display intellicut. Otherwise it means the
                    // user must have clicked on the button or menu item, so go into add gem mode.
                   
                    boolean displayed = getTableTopExplorerAdapter().maybeDisplayIntellicut();
                   
                    if (!displayed && getTableTopPanel().isFocusOwner() && !(evt.getSource() instanceof JButton)) {
                        getTableTopPanel().displayIntellicut();
                   
                    } else if (!displayed) {
                        setAddingDisplayedGem(null);
                        enterGUIState(GUIState.ADD_GEM);
                    }
                }
            };
                   
            addGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_INTELLICUT));
            addGemAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_INTELLICUT);
            addGemAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddGemToolTip"));
        }

        return addGemAction;      
    }   

    /**
     * Return the add reflector button.
     * Access to this button is needed to change its text according to context.
     * @return JButton the add reflector button
     */
    private JButton getAddReflectorGemButton() {
        if (addReflectorGemButton == null) {
            try {
                addReflectorGemButton = makeNewButton(getAddReflectorGemAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
       
        return addReflectorGemButton;
    }   

    /**
     * Return the add reflector drop down button.
     * Access to this button is important because its position is needed to display
     * the associated popup menu in the correct location.
     * @return JButton the add reflector drop down button
     */
    private JButton getAddReflectorGemDropDownButton() {
        if (addReflectorGemDropDownButton == null) {
            try {
                addReflectorGemDropDownButton = makeNewButton(getAddReflectorGemDropDownAction());
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
       
        return addReflectorGemDropDownButton;
    }
   
    /**
     * Returns the action that handles adding an ReflectorGem
     * @return Action
     */
    private Action getAddReflectorGemAction() {
       
        if (addReflectorGemAction == null) {
            try {
                addReflectorGemAction = new AbstractAction (getResourceString("AddReflectorGemMenu"),
                        new ImageIcon(getClass().getResource("/Resources/reflector.gif"))) {
                   
                    private static final long serialVersionUID = -7572765024493266843L;

                    public void actionPerformed(ActionEvent evt) {
                        if (currentCollectorForAddingReflector != null) {
                            // Add an Emitter Gem
                            DisplayedGem eGem = tableTop.createDisplayedReflectorGem(new Point(10,10), currentCollectorForAddingReflector);
                           
                            // Set this as the Gem we're in the process of adding, and change GUI state
                            setAddingDisplayedGem(eGem);
                            enterGUIState(GUIState.ADD_GEM);
                        } else {
                            addReflectorGemByDropDown();
                        }
                    }
                };

                addReflectorGemAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_REFLECTOR_GEM));
                addReflectorGemAction.putValue(ACTION_BUTTON_IS_DROP_PARENT_KEY, Boolean.TRUE);
               
                currentCollectorListener.updateReflectorWidgets();
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return addReflectorGemAction;
    }

    /**
     * Returns the action that handles adding an ReflectorGem
     * @return Action
     */
    private Action getAddReflectorGemDropDownAction() {
       
        if (addReflectorGemDropDownAction == null) {
            try {
                addReflectorGemDropDownAction = new AbstractAction (getResourceString("AddReflectorGemDropDown"),
                        new ImageIcon(getClass().getResource("/Resources/dropdownarrow.gif"))) {
                   
                    private static final long serialVersionUID = 3689499397433899552L;

                    public void actionPerformed(ActionEvent evt) {
                        addReflectorGemByDropDown();
                    }
                };
               
                addReflectorGemDropDownAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_REFLECTOR_GEM));
                addReflectorGemDropDownAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddReflectorGemDropDownToolTip"));
                addReflectorGemDropDownAction.putValue(ACTION_BUTTON_IS_DROP_CHILD_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return addReflectorGemDropDownAction;
    }

    /**
     * Display a popup with available collectors.  If a collector is selected,
     * add the corresponding ReflectorGem wherever the user clicks.
     */
    private void addReflectorGemByDropDown() {
       
        // Set up a new popup menu with the appropriate menu items and listeners
        JPopupMenu pop = new JPopupMenu();
        prepareAddReflectorPopup(pop);

        // show the popup directly underneath the button, left-aligned with it
        JButton reflectorButton = getAddReflectorGemDropDownButton();
        Rectangle bounds = reflectorButton.getBounds();
        pop.show(getToolBarPane(), bounds.x, bounds.y + bounds.height);
    }
   
    /**
     * Prepares the popup menu for adding ReflectorGems for display.  Menu items and listeners will be
     * added to the specified menu.
     * CAUTION: any existing menu items in the specified menu will be removed!
     * @param menu JPopupMenu - the menu that is to be prepared for display
     */
    private void prepareAddReflectorPopup(JPopupMenu menu) {
       
        // A listener for selection of menu items
        class ReflectorMenuSelectionListener implements ActionListener {
            CollectorGem collector;
            ReflectorMenuSelectionListener(CollectorGem collector){
                this.collector = collector;
            }
            public void actionPerformed(ActionEvent evt){
                // Add an Emitter Gem
                DisplayedGem eGem = tableTop.createDisplayedReflectorGem(new Point(10,10), collector);
               
                // Set this as the Gem we're in the process of adding, and change GUI state
                setAddingDisplayedGem(eGem);
                enterGUIState(GUIState.ADD_GEM);
               
                // Save this so the add reflector button can add more reflectors
                currentCollectorListener.setReflectorCollector(collector);
            }
        }
       
        // Create listeners to listen for selection of individual menu items.
        List<Gem> collectorList = new ArrayList<Gem>(getTableTop().getGemGraph().getCollectors());
        List<JComponent> menuItems = getGemMenuItems(collectorList);
       
        // Clear the menu of any existing menu items and then add the new ones
        menu.removeAll();
        int numItems = menuItems.size();
        for (int i = 0; i < numItems; i++) {
           
            // Watch out for JSeparators and disabled menu items!
            JComponent item = menuItems.get(i);

            if (item instanceof JMenuItem && ((JMenuItem)item).isEnabled()) {
                CollectorGem cGem = (CollectorGem)collectorList.get(i);
                ((JMenuItem)item).addActionListener(new ReflectorMenuSelectionListener(cGem));
            }
           
            menu.add(item);
        }
    }
   
    /**
     * Return the WorkspaceMenu property value.
     * @return JMenu
     */
    private JMenu getWorkspaceMenu() {

        if (workspaceMenu == null) {
            workspaceMenu = new JMenu();
            workspaceMenu.setName("WorkspaceMenu");
            workspaceMenu.setText(getResourceString("WorkspaceMenu"));
            workspaceMenu.setMargin(new Insets(2, 0, 2, 0));
            workspaceMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_WORKSPACE_MENU);
           
            workspaceMenu.add(getAddModuleSubMenu());
            workspaceMenu.add(getExportModuleSubMenu());
            workspaceMenu.add(makeNewMenuItem(getRemoveModuleAction()));
            workspaceMenu.add(makeNewMenuItem(getWorkspaceVaultStatusAction()));
            workspaceMenu.add(getSyncSubMenu());
            workspaceMenu.addSeparator();
            if (enterpriseSupport.isEnterpriseSupported()) {
                workspaceMenu.add(makeNewMenuItem(getDeployWorkspaceToEnterpriseAction()));
            }
            workspaceMenu.add(makeNewMenuItem(getExportWorkspaceToCarsAction()));
            workspaceMenu.addSeparator();
            workspaceMenu.add(getRenameSubMenu());
            workspaceMenu.addSeparator();
            workspaceMenu.add(makeNewMenuItem(getCompileModifiedAction()));
            workspaceMenu.add(makeNewMenuItem(getRecompileAction()));
            workspaceMenu.addSeparator();
            workspaceMenu.add(makeNewMenuItem(getCreateMinimalWorkspaceAction()));
            workspaceMenu.addSeparator();
            workspaceMenu.add(makeNewMenuItem(getWorkspaceInfoAction()));
        }

        return workspaceMenu;
    }
   
    /**
     * @return the submenu item to add a module to the workspace.
     */
    private JMenuItem getAddModuleSubMenu() {
        if (addModuleSubMenu == null) {
            addModuleSubMenu = makeNewMenu(getResourceString("AddModule"));
            addModuleSubMenu.add(makeNewMenuItem(getAddModuleFromStandardVaultAction()));
            addModuleSubMenu.addSeparator();
            addModuleSubMenu.add(makeNewMenuItem(getAddModuleFromSourceFileAction()));
            if (enterpriseSupport.isEnterpriseSupported()) {
                addModuleSubMenu.add(makeNewMenuItem(getAddEnterpriseModuleAction()));
            }
           
            addModuleSubMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_ADD_MODULE);
            addModuleSubMenu.setToolTipText(getResourceString("AddModuleToolTip"));
        }
       
        return addModuleSubMenu;
    }
   
    /**
     * Return the action for the menu item to add a module from the Standard Vault to the current workspace.
     * @return Action
     */
    private Action getAddModuleFromStandardVaultAction() {

        if (addModuleFromStdVaultAction == null) {
            addModuleFromStdVaultAction = new AbstractAction(getResourceString("AddModuleFromStdVault")) {
                                                       
                private static final long serialVersionUID = 7638468785140208128L;

                public void actionPerformed(ActionEvent evt) {
                    handleAddModuleFromStandardVaultAction();
                }
            };
           
            addModuleFromStdVaultAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_STD_VAULT_MODULE));
            addModuleFromStdVaultAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddModuleFromStdVaultToolTip"));
        }

        return addModuleFromStdVaultAction;
    }

    /**
     * Return the action for the menu item to add a new module to the current workspace using a simple .cal source file.
     * @return Action
     */
    private Action getAddModuleFromSourceFileAction() {

        if (addModuleFromSourceFileAction == null) {
            addModuleFromSourceFileAction = new AbstractAction(getResourceString("AddModuleFromSource")) {
                                                       
                private static final long serialVersionUID = -8550053556955691191L;

                public void actionPerformed(ActionEvent evt) {
                    handleAddModuleFromSourceFileAction();
                }
            };
           
            addModuleFromSourceFileAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_LOCAL_FILE_MODULE));
            addModuleFromSourceFileAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddModuleFromSourceToolTip"));
        }
        return addModuleFromSourceFileAction;
    }

    /**
     * Return the action for the menu item to add a new module to the current workspace from Business Objects Enterprise.
     * @return Action
     */
    private Action getAddEnterpriseModuleAction() {

        if (addEnterpriseModuleAction == null) {
            addEnterpriseModuleAction = new AbstractAction(getResourceString("AddModuleFromEnterprise")) {
                                                       
                private static final long serialVersionUID = 6786374520668468031L;

                public void actionPerformed(ActionEvent evt) {
                    handleAddEnterpriseModuleAction();
                }
            };
           
            addEnterpriseModuleAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ADD_ENTERPRISE_MODULE));
            addEnterpriseModuleAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AddModuleFromEnterpriseToolTip"));
        }
        return addEnterpriseModuleAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to add a module from Enterprise to the current workspace.
     */
    private void handleAddEnterpriseModuleAction() {
        // Attempt to ensure that the user is connected to Enterprise.
        if (!ensureEnterpriseConnectionIsReady()) {
            return;
        }
       
        Vault vault = enterpriseSupport.getEnterpriseVault();
        if (vault == null) {
            return;
        }
       
        ModuleName[] moduleNamesToExclude = getWorkspace().getModuleNames();
       
        // Display the dialog from which the user can select the module to add.
        String dialogTitle = getResourceString("VMCD_AddModuleTitle");
        String dialogMessage = GemCutter.getResourceString("VMCD_ChooseModuleForImportMessage");
        VaultResourceChooserDialog moduleChooserDialog =
                VaultResourceChooserDialog.getModuleChooserDialog(GemCutter.this, dialogTitle, dialogMessage, vault, moduleNamesToExclude);
       
        if (moduleChooserDialog == null) {
            showActionFailureDialog(getResourceString("ErrorDialogTitle"), getResourceString("CannotObtainResourcesFromVault"), null);
            return;
        }
       
        boolean accepted = moduleChooserDialog.doModal();

        // Get the selected module version.
        VaultResourceChooserDialog.SelectedResourceVersion selectedModuleVersion = moduleChooserDialog.getSelectedResourceVersion();
        if (!accepted || selectedModuleVersion == null) {
            return;
        }
       
        // Try to add the module.
        handleAddModuleAttempt(vault, ModuleName.make(selectedModuleVersion.getResourceName()), selectedModuleVersion.getRevisionNumber(), true);
    }

    /**
     * Attempt to instantiate a CEConnectionManager if one does not already exist.
     * If the instantiation fails, inform the user.
     * One or more dialogs may be displayed.
     *
     * TODOEL: This allows only one connection to ce to be present.
     *
     * @return whether the connection to Enterprise is ready.
     */
    private boolean ensureEnterpriseConnectionIsReady() {
       
        // Attempt to ensure that the user is connected to Enterprise.
        try {
            enterpriseSupport.ensureConnected(GemCutter.this);
           
        } catch (Exception e) {
            // Inform the user.
            showActionFailureDialog(getResourceString("ErrorDialogTitle"), getResourceString("CannotInitConnectionToCEMessage"), e);
            return false;
        }
       
        return enterpriseSupport != null && enterpriseSupport.isConnected();
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to add a module from the Standard Vault to the current workspace.
     */
    private void handleAddModuleFromStandardVaultAction() {
        // Get the available modules not already in the workspace.   (The get avail modules operation shouldn't fail on the standard vault).
        Set<ModuleName> availableModulesSet = new HashSet<ModuleName>(Arrays.asList(StandardVault.getInstance().getAvailableModules(new Status("Add Status."))));
        availableModulesSet.removeAll(Arrays.asList(getWorkspace().getModuleNames()));
       
        // Convert to a String array.
        ModuleName[] availableModulesArray = availableModulesSet.toArray(new ModuleName[availableModulesSet.size()]);
        Arrays.sort(availableModulesArray);
       
        // Ask the user which module to remove.
        String title = getResourceString("AddModuleFromStdVaultDialogTitle");
        String message = getResourceString("AddModuleFromStdVaultDialogMessage");       
        ModuleName selectedModuleName = (ModuleName)JOptionPane.showInputDialog(GemCutter.this, message, title, JOptionPane.PLAIN_MESSAGE,
                                                                        null, availableModulesArray, availableModulesArray[0]);

        if (selectedModuleName == null) {
            return;
        }
       
        // Try to add the module.
        handleAddModuleAttempt(StandardVault.getInstance(), selectedModuleName, -1, true);
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to add a module to the current workspace
     *   using a simple .cal source file.
     */
    private void handleAddModuleFromSourceFileAction() {

        FileFilter fileFilter = new ExtensionFileFilter(CALSourcePathMapper.INSTANCE.getFileExtension(), getResourceString("CALFileDescription"));

        String initialDir = getPreferences().get(ADD_MODULE_DIRECTORY_PREF_KEY, ADD_MODULE_DIRECTORY_DEFAULT);
       
        JFileChooser fileChooser = new JFileChooser(initialDir);
        fileChooser.setFileFilter(fileFilter);
       
        int chooserOption = fileChooser.showOpenDialog(this);
        if (chooserOption != JFileChooser.APPROVE_OPTION) {
            return;
        }
       
        File selectedFile = fileChooser.getSelectedFile();
       
        // A path to a file, which for now we assume contains only the .cal source definition.
        SimpleCALFileVault fileVault = SimpleCALFileVault.getSimpleCALFileVault(selectedFile);
        if (fileVault == null) {           
            String title2 = getResourceString("AddModuleFailedTitle");
            String message2 = GemCutterMessages.getString("AddModuleFromSourceFailedMessage", selectedFile.getName());           
            JOptionPane.showMessageDialog(GemCutter.this, message2, title2, JOptionPane.ERROR_MESSAGE);
            return;
        }
       
        // Try to add the module.
        handleAddModuleAttempt(fileVault, fileVault.getModuleName(), 0, true);
    }

    /**
     * Handle the addition of a module to the workspace.
     * This method may display a modal error dialog if errors are encountered.
     *
     * TODOEL: reevaluate all code gems (and later, value gems).
     *
     * @param vault
     * @param moduleName
     * @param revisionNumber
     * @param checkExisting if true, this operation will fail (with appropriate message) if the module already exists in the workspace.
     * If false, the added module will replace any existing module resources in the workspace.
     */
    private void handleAddModuleAttempt(Vault vault, ModuleName moduleName, int revisionNumber, boolean checkExisting) {
        Status addStatus = new Status("Add status");
       
        StoredVaultElement.Module moduleToAdd = vault.getStoredModule(moduleName, revisionNumber, addStatus);

        if (moduleToAdd == null) {
                       
            String title = getResourceString("AddModuleFailedTitle");
            String message;
            if (addStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
                message = getResourceString("GetModuleErrorMessage") + addStatus.getDebugMessage();
            } else {
                message = getResourceString("AddModuleNotFoundMessage") + addStatus.getDebugMessage();
            }
            JOptionPane.showMessageDialog(GemCutter.this, message, title, JOptionPane.ERROR_MESSAGE);
            return;
        }
       
        Status addModuleStatus = new Status(getResourceString("AddModuleStatus"));
       
        if (!getWorkspace().addModule(moduleToAdd, checkExisting, addModuleStatus)) {
            String title = getResourceString("AddModuleFailedTitle");
           
            String message = getResourceString("AddModuleFailedMessage");
            String debugMessage = addModuleStatus.getDebugMessage();
            if (debugMessage != null) {
                message += "\n" + debugMessage;
            }
           
            JOptionPane.showMessageDialog(GemCutter.this, message, title, JOptionPane.ERROR_MESSAGE);
            return;
        }

        // If we are here, the file was successfully added.
        // Note that any gems that were on the tabletop will still exist in the program (so we don't have to handle the case that they don't..)
       
        // mark workspace as changed in Gem Browser
        getGemBrowser().markWorkspaceDirty();       
       
        // recompile the program from the updated workspace
        recompileWorkspace(true);
    }
   
    private JMenuItem getRenameSubMenu() {
        if (renameSubMenu == null) {
            renameSubMenu = makeNewMenu(getResourceString("Rename"));
            renameSubMenu.add(makeNewMenuItem(getRenameGemAction()));
            renameSubMenu.add(makeNewMenuItem(getRenameTypeAction()));
            renameSubMenu.add(makeNewMenuItem(getRenameClassAction()));
            renameSubMenu.add(makeNewMenuItem(getRenameModuleAction()));
        }
       
        return renameSubMenu;
    }
   
    /**
     * @return the submenu item to export a module from the workspace.
     */
    private JMenuItem getExportModuleSubMenu() {
        if (exportModuleSubMenu == null) {
            exportModuleSubMenu = makeNewMenu(getResourceString("ExportModule"));
            exportModuleSubMenu.add(makeNewMenuItem(getExportModuleToJarAction()));
            if (enterpriseSupport.isEnterpriseSupported()) {
                exportModuleSubMenu.add(makeNewMenuItem(getExportModuleToCEAction()));
            }
           
            exportModuleSubMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_EXPORT_MODULE);
            exportModuleSubMenu.setToolTipText(getResourceString("ExportModuleToolTip"));
        }
       
        return exportModuleSubMenu;
    }
   
    /**
     * Return the action for the menu item to export a module from the current workspace to CE.
     * @return Action
     */
    private Action getExportModuleToCEAction() {
        if (exportModuleToCEAction == null) {
            exportModuleToCEAction = new AbstractAction(getResourceString("ExportModuleToCE")) {
                                                       
                private static final long serialVersionUID = 4888254343776966036L;

                public void actionPerformed(ActionEvent evt) {
                    handleExportModuleToCEAction();
                }
            };
           
            exportModuleToCEAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_EXPORT_MODULE_TO_CE));
            exportModuleToCEAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ExportModuleToCEToolTip"));
        }
        return exportModuleToCEAction;
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to export a module from the current workspace to CE.
     */
    private void handleExportModuleToCEAction() {
        // Attempt to ensure that the user is connected to CE.
        if (!ensureEnterpriseConnectionIsReady()) {
            return;
        }
       
        // Ask the user which module to export.
        ModuleName selectedModuleName = showExportModuleChooserDialog();
       
        if (selectedModuleName == null) {
            return;
        }
       
        MetaModule metaModule = getWorkspaceManager().getWorkspace().getMetaModule(selectedModuleName);
        if (metaModule == null) {
            String title = getResourceString("ExportModuleFailedDialogTitle");
            String message = getResourceString("GetModulesErrorMessage");
            JOptionPane.showMessageDialog(GemCutter.this, message, title, JOptionPane.ERROR_MESSAGE);
        }
       
        Status status = new Status("Put status.");
        int addedRevisionNum = enterpriseSupport.getEnterpriseVault().putStoredModule(selectedModuleName, getWorkspace(), status);
        if (status.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
            // Inform the user.
            showActionFailureDialog(getResourceString("ExportModuleFailedDialogTitle"),
                                    getResourceString("ExportModuleFailedDialogMessage") + "\n" + status.getDebugMessage(),
                                    null);
            return;

        } else if (status.getSeverity().compareTo(Status.Severity.WARNING) >= 0) {
            String title = getResourceString("ExportModuleWarningsDialogTitle");
            String message = getResourceString("ExportModuleWarningsDialogMessage") + status.getDebugMessage();
            JOptionPane.showMessageDialog(GemCutter.this, message, title, JOptionPane.ERROR_MESSAGE);
           
        }
       
        // Display a status message.
        String statusMessage = GemCutterMessages.getString("SM_ModuleExported", selectedModuleName, new Integer(addedRevisionNum));
        statusMessageManager.displayMessage(this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);

    }
   
    /**
     * Return the action for the menu item to export a module from the current workspace to a .jar file.
     * @return Action
     */
    private Action getExportModuleToJarAction() {
        if (exportModuleToJarAction == null) {
            exportModuleToJarAction = new AbstractAction(getResourceString("ExportModuleToJar")) {
                                                       
                private static final long serialVersionUID = 9186654570603119228L;

                public void actionPerformed(ActionEvent evt) {
                    handleExportModuleToJarAction();
                }
            };
           
            exportModuleToJarAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_EXPORT_MODULE_TO_JAR));
            exportModuleToJarAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ExportModuleToJarToolTip"));
        }
        return exportModuleToJarAction;
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to export a module from the current workspace to a .jar file.
     */
    private void handleExportModuleToJarAction() {
        // Ask the user which module to export.
        ModuleName selectedModuleName = showExportModuleChooserDialog();
       
        if (selectedModuleName == null) {
            return;
        }

        // Create the file chooser at a default initial directory.
        String initialDir = getPreferences().get(EXPORT_MODULE_DIRECTORY_PREF_KEY, ADD_MODULE_DIRECTORY_DEFAULT);
        JFileChooser fileChooser = new JFileChooser(initialDir);
       
        // Create and set the file filter.
        FileFilter fileFilter = new ExtensionFileFilter(JAR_FILE_EXTENSION, getResourceString("JarFiles"));

        fileChooser.setFileFilter(fileFilter);

        // Also accept the accept all filter.
        fileChooser.setAcceptAllFileFilterUsed(true);
       
        // The default file name is the name of the module + ".jar".
        fileChooser.setSelectedFile(new File(initialDir, selectedModuleName + "." + JAR_FILE_EXTENSION));
       
        // Show the dialog.
        int chooserOption = fileChooser.showSaveDialog(this);
       
        // Do nothing if dialog isn't approved.
        if (chooserOption != JFileChooser.APPROVE_OPTION) {
            return;
        }
       
        // Get the file which was selected.
        File selectedFile = fileChooser.getSelectedFile();
       
        // Save the directory the user browsed to for next time
        String lastDir = selectedFile.getParentFile().getAbsolutePath();
        getPreferences().put(EXPORT_MODULE_DIRECTORY_PREF_KEY, lastDir);
       
        // Confirm overwrite if the file exists.
        if (selectedFile.exists()) {
            String title = getResourceString("ConfirmOverwriteTitle");
            String message = GemCutterMessages.getString("ConfirmOverwriteMessage", selectedModuleName);           
            int continueChoice = JOptionPane.showConfirmDialog(GemCutter.this, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
            if (continueChoice != JOptionPane.OK_OPTION) {
                return;
            }
        }

        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(selectedFile);
            ModulePackager.writeModuleToJar(getWorkspace(), selectedModuleName, outputStream);
            outputStream.close();
           
            // Display a status message.
            String statusMessage = GemCutterMessages.getString("SM_ModuleExported", selectedModuleName, new Integer(0));
            statusMessageManager.displayMessage(this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);

        } catch (IOException e) {
            // Inform the user.
            showActionFailureDialog(getResourceString("ExportModuleFailedDialogTitle"), getResourceString("ExportModuleFailedDialogMessage"), e);

        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e1) {
                }
            }
        }

    }
   
    /**
     * Display the export module dialog.
     * @return the module selected by the user for export.
     */
    private ModuleName showExportModuleChooserDialog() {
        // Get the modules which are already in the workspace.
        ModuleName[] availableModulesArray = getWorkspace().getModuleNames();
        Arrays.sort(availableModulesArray);
       
        // Ask the user which module to export.
        String title = getResourceString("ExportModuleDialogTitle");
        String message = getResourceString("ExportModuleDialogMessage");       
        ModuleName selectedModuleName = (ModuleName)JOptionPane.showInputDialog(GemCutter.this, message, title, JOptionPane.PLAIN_MESSAGE,
                                                                        null, availableModulesArray, availableModulesArray[0]);

        return selectedModuleName;
    }
   
    /**
     * Display a dialog notifying the user of a failure to perform some action.
     * @param title the title of the dialog.
     * @param message the dialog message.
     * @param throwable the throwable which caused the failure, if any.  May be null.
     */
    private void showActionFailureDialog(String title, String message, Throwable throwable) {
        if (throwable != null) {
            message += "\n" + throwable.getLocalizedMessage();
        }
        JOptionPane.showMessageDialog(GemCutter.this, message, title, JOptionPane.ERROR_MESSAGE);
    }
   
    /**
     * Return the action for the menu item to remove a module from the current workspace.
     * @return Action
     */
    private Action getRemoveModuleAction() {

        if (removeModuleAction == null) {
            removeModuleAction = new AbstractAction (getResourceString("RemoveModule")) {
                                                       
                private static final long serialVersionUID = 9032351347191560711L;

                public void actionPerformed(ActionEvent evt) {
                    handleRemoveModuleAction();
                }
            };
           
            removeModuleAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_REMOVE_MODULE));
            removeModuleAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RemoveModuleToolTip"));
        }
        return removeModuleAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to remove a module from the current workspace.
     * TODO: make some of these actions undoable.
     */
    private void handleRemoveModuleAction() {

        // Get the list of modules, and sort.
        ModuleName[] moduleNames = getWorkspace().getModuleNames();
        Arrays.sort(moduleNames);
       
        // Remove "Prelude" from the list, as CAL programs assume this exists.
        List<ModuleName> moduleNameList = new ArrayList<ModuleName>(Arrays.asList(moduleNames));
        moduleNameList.remove(CAL_Prelude.MODULE_NAME);
        moduleNames = moduleNameList.toArray(new ModuleName[moduleNameList.size()]);
       
        String dialogTitle = getResourceString("RemoveModuleDialogTitle");
        if (moduleNameList.isEmpty()) {           
            String message = getResourceString("RemoveModuleNoModulesMessage");
            JOptionPane.showMessageDialog(GemCutter.this, message, dialogTitle, JOptionPane.ERROR_MESSAGE);
            return;
        }

        // Ask the user which module to remove.
       
        String message = getResourceString("RemoveModuleDialogMessage");
       
        ModuleName selectedModuleName = (ModuleName)JOptionPane.showInputDialog(GemCutter.this, message, dialogTitle, JOptionPane.PLAIN_MESSAGE,
                                                                        null, moduleNames, moduleNames[0]);

        if (selectedModuleName == null) {
            return;
        }
       
        // Actually remove the module.
        doRemoveModuleUserAction(selectedModuleName);
    }
   
    /**
     * Do the work necessary to carry out a user-initiated action to remove a module from the GemCutter workspace.
     * @param moduleToRemove the name of the module to remove.
     */
    void doRemoveModuleUserAction(ModuleName moduleToRemove) {
       
        // Check if we are removing the current module
        // Note that the second check is necessary in the case that a program has been modified in such a way as to make it invalid
        //   (in which case, the working module name will be pointing to a module which does not exist).
        ModuleName currentModule = getWorkingModuleName();
        boolean removingCurrentModule = currentModule.equals(moduleToRemove) &&
                                        workspaceManager.getWorkspace().getNMetaModules() > 0;
       
        if (removingCurrentModule) {           
            String dialogMessage = getResourceString("RemoveModuleRemovingCurrentModule");
            String dialogTitle = getResourceString("RemoveModuleDialogTitle");
           
            boolean okToRemove = promptSaveCurrentTableTopIfNonEmpty(dialogMessage, dialogTitle);
            if (!okToRemove){
                // If cannot save or user cancelled
                return;
            }

        } else {
           
            // Check if removing the module will cause the current module to be removed as well.
            for (final ModuleName dependantModule : getWorkspaceManager().getDependentModuleNames(moduleToRemove)) {
                if (dependantModule.equals(currentModule)){
                    removingCurrentModule = true;
                    break;
                }
            }

            if (removingCurrentModule) {

                // If tabletop is not empty, prompt user to save, else prompt to confirm removing current module
                if (tableTop.getDisplayedGems().size() > 1) {
                    String dialogMessage = getResourceString("RemoveModuleRemovingModuleWillRemoveCurrentModule");
                    String dialogTitle = getResourceString ("RemoveModuleDialogTitle");

                    boolean okToRemove = promptSaveCurrentTableTopIfNonEmpty(dialogMessage, dialogTitle);
                    if (!okToRemove){
                        return;
                    }

                } else {
                    String dialogMessage = getResourceString ("RemoveModuleRemovingModuleWillRemoveCurrentModule_emptyTabletop");
                    String dialogTitle = getResourceString ("RemoveModuleDialogTitle");

                    int confirmOkToRemove = JOptionPane.showConfirmDialog(GemCutter.this, dialogMessage, dialogTitle,
                            JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);

                    if (confirmOkToRemove != JOptionPane.OK_OPTION) {
                        return;
                    }
                }
            }
        }

        Status removeStatus = new Status("Remove status");
        getWorkspaceManager().removeModule(moduleToRemove, false, removeStatus);
       
        // update the perspective if the working module no longer exists (eg. because of compile failure..)
        if (perspective.getWorkingModule() == null) {
            ModuleName newWorkingModuleName = getInitialWorkingModuleName(workspaceManager.getWorkspace());
            if (newWorkingModuleName != null) {
                if (removingCurrentModule) {
                    changeModuleAndNewTableTop(newWorkingModuleName, true);
                } else {
                    perspective.setWorkingModule(newWorkingModuleName);                   
                }
            }

            // Change the current module if we just removed it.
            if (removingCurrentModule) {
                // clear the undo stack and dirty edit.
                extendedUndoManager.discardAllEdits();
                editToUndoWhenNonDirty = null;
                updateUndoWidgets();
            }
            // Also update the window title.
            updateWindowTitle();
        }

        // mark workspace as changed in Gem Browser
        getGemBrowser().markWorkspaceDirty()
       
        // Refresh the gem browser and navigator to show any new gems
        getGemBrowser().refresh();
        getNavigatorOwner().refresh();
       
        // Show any problems which were encountered during module resource removal.
        if (removeStatus.getSeverity().compareTo(Status.Severity.WARNING) >= 0 ) {
            String dialogTitle = getResourceString("RemoveModuleDialogTitle");
            String message = "Problems were encountered:\n"  + removeStatus.getDebugMessage();
            JOptionPane.showMessageDialog(GemCutter.this, message, dialogTitle, JOptionPane.WARNING_MESSAGE);
        }
    }

    /**
     * Return the action for the menu item to dump the current vault status of modules in the workspace
     * TODOEL: TEMP - this is somewhat of a debug menu item!
     * @return Action
     */
    private Action getWorkspaceVaultStatusAction() {

        if (workspaceVaultStatusAction == null) {
            workspaceVaultStatusAction = new AbstractAction (getResourceString("WorkspaceVaultStatus")) {
                                                       
                private static final long serialVersionUID = -5941578304595235616L;

                public void actionPerformed(ActionEvent evt) {
                    handleWorkspaceVaultStatusAction();
                }
            };
           
            workspaceVaultStatusAction.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_V));
            workspaceVaultStatusAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("WorkspaceVaultStatusToolTip"));
        }
        return workspaceVaultStatusAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to see the vault status of modules
     *   in the workspace.
     */
    private void handleWorkspaceVaultStatusAction() {

        // Just dump the text to a details dialog.
        VaultStatus vaultStatus = getWorkspace().getVaultStatus();
        DetailsDialog detailsDialog = new DetailsDialog(GemCutter.this, getResourceString("WorkspaceVaultStatusDialogTitle"), getResourceString("WorkspaceCurrentVaultStatus"),
                                                        vaultStatus.getStatusString(), DetailsDialog.MessageType.PLAIN);
        detailsDialog.setDetailsVisible(true);
        detailsDialog.doModal();
    }

    /**
     * @return the submenu item to sync the workspace.
     */
    private JMenuItem getSyncSubMenu() {
        if (syncSubMenu == null) {
            syncSubMenu = makeNewMenu(getResourceString("Sync"));
            syncSubMenu.add(makeNewMenuItem(getSyncWorkspaceToHeadAction()));
            if (enterpriseSupport.isEnterpriseSupported()) {
                syncSubMenu.add(makeNewMenuItem(getSyncWorkspaceToEnterpriseDeclarationAction()));
            }
           
            syncSubMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_SYNC);
            syncSubMenu.setToolTipText(getResourceString("SyncToolTip"));
        }
       
        return syncSubMenu;
    }
   
    /**
     * @return the action for the menu item to sync the workspace with the head revisions of the modules in their associated vaults.
     */
    private Action getSyncWorkspaceToHeadAction() {
        if (syncToHeadAction == null) {
            syncToHeadAction = new AbstractAction(getResourceString("SyncWorkspaceToHead")) {
                                                       
                private static final long serialVersionUID = -3139343996877603543L;

                public void actionPerformed(ActionEvent evt) {
                    handleSyncWorkspaceToHeadAction();
                }
            };
           
            syncToHeadAction.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_H));
            syncToHeadAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SyncWorkspaceToHeadToolTip"));
        }
        return syncToHeadAction;
    }

    /**
     * @return the action for the menu item to sync the workspace with the head revisions of the modules in their associated vaults.
     */
    private Action getSyncWorkspaceToEnterpriseDeclarationAction() {
        if (syncToEnterpriseDeclarationAction == null) {
            syncToEnterpriseDeclarationAction = new AbstractAction(getResourceString("SyncWorkspaceToEnterpriseDeclaration")) {
                                                       
                private static final long serialVersionUID = 6036990146764617078L;

                public void actionPerformed(ActionEvent evt) {
                    handleSyncWorkspaceToEnterpriseDeclarationAction();
                }
            };
           
            syncToEnterpriseDeclarationAction.putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_E));
            syncToEnterpriseDeclarationAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SyncWorkspaceToEnterpriseDeclarationToolTip"));
        }
        return syncToEnterpriseDeclarationAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to sync the workspace to the head revisions of its modules
     *   in the associated vaults.
     */
    private void handleSyncWorkspaceToHeadAction() {
        List<ModuleName> moduleRevisionList = Arrays.asList(getWorkspace().getModuleNames());
        doSyncModulesToLatestUserAction(moduleRevisionList);
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to sync the workspace to a workspace declaration in Enterprise.
     */
    private void handleSyncWorkspaceToEnterpriseDeclarationAction() {
        // Attempt to ensure that the user is connected to CE.
        if (!ensureEnterpriseConnectionIsReady()) {
            return;
        }
       
        Vault ceVault = enterpriseSupport.getEnterpriseVault();
        if (ceVault != null) {
            handleSyncWorkspaceToDeclarationAction(ceVault);
        }
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to sync a number of modules in the workspace
     * with the latest revisions available in their associated vaults.
     * @param moduleNames the names of the modules to sync.
     */
    void doSyncModulesToLatestUserAction(List<ModuleName> moduleNames) {
       
        // This may take a while, so set the cursor.
        Cursor oldCursor = getCursor();
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
       
        try {
            Status syncStatus = new Status(getResourceString("SyncWorkspaceStatus"));
            CALWorkspace workspace = getWorkspace();
            CALWorkspace.SyncInfo syncInfo = new CALWorkspace.SyncInfo();
           
            for (final ModuleName moduleName : moduleNames) {
                CALWorkspace.SyncInfo newSyncInfo = workspace.syncModuleToRevision(moduleName, -1, false, syncStatus);
                syncInfo.addInfo(newSyncInfo);
            }
           
            handleUserSyncPerformed(syncInfo, syncStatus);
           
        } finally {
            setCursor(oldCursor);
        }
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to sync the workspace to a declaration
     *   which is contained within an indicated vault.
     */
    private void handleSyncWorkspaceToDeclarationAttempt(Vault vault, String declarationName, int revisionNumber) {
        Status syncStatus = new Status(getResourceString("SyncWorkspaceStatus"));
       
        // Instantiate a provider.
        WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider =
                new VaultWorkspaceDeclarationProvider(vault, declarationName, revisionNumber);

        // This may take a while, so set the cursor.
        Cursor oldCursor = getCursor();
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
       
        try {
            // Sync to the workspace declaration.
            CALWorkspace.SyncInfo syncInfo = getWorkspaceManager().syncWorkspaceToDeclaration(workspaceDeclarationProvider, syncStatus);
            if (syncInfo != null) {
                handleUserSyncPerformed(syncInfo, syncStatus);
            }
       
        } finally {
            // Reset the cursor.
            setCursor(oldCursor);
        }
       
        // Deal with errors.
        if (syncStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
            String message = "Problems encountered while constructing the workspace:\n" + syncStatus.getDebugMessage();
            syncStatus.add(new Status(Status.Severity.ERROR, message, null));
        }
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to sync the workspace with a declaration in a vault.
     * @param vault the vault from which to retrieve the workspace declaration
     */
    private void handleSyncWorkspaceToDeclarationAction(Vault vault) {

        // Display the dialog from which the user can select the workspace declaration.
        String dialogTitle = getResourceString("VMCD_SelectWorkspaceTitle");
        String dialogMessage = GemCutter.getResourceString("VMCD_ChooseWorkspaceForSyncMessage");
        VaultResourceChooserDialog workspaceChooserDialog =
                VaultResourceChooserDialog.getWorkspaceDeclarationChooserDialog(GemCutter.this, dialogTitle, dialogMessage, vault);
       
        if (workspaceChooserDialog == null) {
            showActionFailureDialog(getResourceString("ErrorDialogTitle"), getResourceString("CannotObtainResourcesFromVault"), null);
            return;
        }
       
        boolean accepted = workspaceChooserDialog.doModal();

        // Get the selected module version.
        VaultResourceChooserDialog.SelectedResourceVersion selectedWorkspaceVersion = workspaceChooserDialog.getSelectedResourceVersion();
        if (!accepted || selectedWorkspaceVersion == null) {
            return;
        }
       
        // Try to sync the workspace.
        handleSyncWorkspaceToDeclarationAttempt(vault, selectedWorkspaceVersion.getResourceName(), selectedWorkspaceVersion.getRevisionNumber());
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to sync a number of modules in the workspace
     * with revisions available in their associated vaults.
     * @param moduleRevisions the names and revisions of the modules to sync.
     * @param force whether the sync should be forced.  If true, any user changes will be clobbered.
     */
    void doSyncModulesUserAction(List<ModuleRevision> moduleRevisions, boolean force) {
       
        // This may take a while, so set the cursor.
        Cursor oldCursor = getCursor();
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
       
        try{
            Status syncStatus = new Status(getResourceString("SyncWorkspaceStatus"));
            CALWorkspace workspace = getWorkspace();
            CALWorkspace.SyncInfo syncInfo = new CALWorkspace.SyncInfo();
           
            for (final ModuleRevision moduleRevision : moduleRevisions) {
                CALWorkspace.SyncInfo newSyncInfo =
                    workspace.syncModuleToRevision(moduleRevision.getModuleName(), moduleRevision.getRevisionNumber(), force, syncStatus);
                syncInfo.addInfo(newSyncInfo);
            }
           
            handleUserSyncPerformed(syncInfo, syncStatus);
           
        } finally {
            setCursor(oldCursor);
        }
    }
   
    /**
     * Handle the interaction with the user when the sync is performed.
     * @param syncInfo the info about the sync which was performed.
     * @param syncStatus the status object which was tracking the sync operation.
     */
    private void handleUserSyncPerformed(CALWorkspace.SyncInfo syncInfo, Status syncStatus) {
       
        if (syncStatus.getSeverity().compareTo(Status.Severity.WARNING) >= 0) {
            String title = getResourceString("SyncWorkspaceWarningTitle");
            String message = getResourceString("SyncWorkspaceWarningMessage");
            DetailsDialog detailsDialog = new DetailsDialog(GemCutter.this, title, message,
                                                            syncStatus.getDebugMessage(), DetailsDialog.MessageType.WARNING);
            detailsDialog.setDetailsVisible(true);
            detailsDialog.doModal();
        }
       
        // Display the result of the sync.
        Set<ResourceIdentifier> updatedResourceIdentifierSet = syncInfo.getUpdatedResourceIdentifiers();
        Set<ResourceIdentifier> resourceImportFailureSet = syncInfo.getResourceImportFailures();
        Set<ResourceIdentifier> syncConflictIdentifierSet = syncInfo.getSyncConflictIdentifiers();
        Set<ResourceIdentifier> deletedResourceIdentifierSet = syncInfo.getDeletedResourceIdentifiers();
       
        StringBuilder sb = new StringBuilder();
        sb.append(getResourceString("SyncWorkspaceUpdatedResources"));
        dumpIdentifierSet(updatedResourceIdentifierSet, sb);
       
        if (!deletedResourceIdentifierSet.isEmpty()) {
            sb.append(getResourceString("SyncWorkspaceDeletedResources"));
            dumpIdentifierSet(deletedResourceIdentifierSet, sb);
        }
       
        if (!syncConflictIdentifierSet.isEmpty()) {
            sb.append(getResourceString("SyncWorkspaceConflicts"));
            dumpIdentifierSet(syncConflictIdentifierSet, sb);
        }
       
        if (!resourceImportFailureSet.isEmpty()) {
            sb.append(getResourceString("SyncWorkspaceFailed"));
            dumpIdentifierSet(resourceImportFailureSet, sb);
        }
       
        String syncResultMessage = sb.toString();
       
        String title = getResourceString("SyncWorkspaceResultTitle");
        String message = getResourceString("SyncWorkspaceResultMessage");
        DetailsDialog detailsDialog = new DetailsDialog(GemCutter.this, title, message,
                                                        syncResultMessage, DetailsDialog.MessageType.PLAIN);
       
        detailsDialog.setDetailsVisible(true);
        detailsDialog.doModal();
       
        // Compile modified modules.
        recompileWorkspace(true);
    }

    /**
     * A helper method to syncWorkspaceAction() to dump the contents of an identifier set into a string builder.
     * @param identifierSet the identifiers to dump.
     * @param sb the stringBuilder into which the identifiers will be dumped.
     */
    private void dumpIdentifierSet(Set<ResourceIdentifier> identifierSet, StringBuilder sb) {
       
        if (identifierSet.isEmpty()) {
            sb.append(getResourceString("SyncWorkspaceNone"));

        } else {
            // Convert to an array and sort.
            List<ResourceIdentifier> identiferList = new ArrayList<ResourceIdentifier>(identifierSet);
            String[] identifierStrings = new String[identiferList.size()];
           
            int index = 0;
            for (final ResourceIdentifier resourceIdentifier : identifierSet) {
                identifierStrings[index] = resourceIdentifier.toString();
                index++;
            }
            Arrays.sort(identifierStrings);
           
            for (final String identifierString : identifierStrings) {
                sb.append("  " + identifierString + "\n");
            }

        }
    }
   
    /**
     * Return the action for the switch workspace menu item
     * @return Action
     */
    private Action getSwitchWorkspaceAction() {
       
        if (switchWorkspaceAction == null) {
            switchWorkspaceAction = new AbstractAction (getResourceString("SwitchWorkspace")) {
                                                       
                private static final long serialVersionUID = 7632815811866012148L;

                public void actionPerformed(ActionEvent evt) {
                    handleSwitchWorkspaceAction();
                }
            };
           
            switchWorkspaceAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_SWITCH_WORKSPACE));
            switchWorkspaceAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("SwitchWorkspaceToolTip"));
        }
        return switchWorkspaceAction;
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to switch to a different workspace.
     */
    private void handleSwitchWorkspaceAction() {
       
        SwitchWorkspaceDialog dialog = new SwitchWorkspaceDialog(this, workspaceManager);
        centerWindow(dialog);
        dialog.setVisible(true);

        if (!dialog.isOKSelected()) {
            return;
        }
       
        handleSwitchWorkspaceAction(dialog.getNextWorkspaceDeclarationStreamProvider());
    }

    /**
     * Handle the situation where the user has indicated that they would like to switch to a different workspace.
     * Switches GemCutter to the specified workspace declaration except when user decides to cancel or if saving the current tabletop failed
     * @param nextWorkspaceDeclarationStreamProvider
     */
    private void handleSwitchWorkspaceAction(final WorkspaceDeclaration.StreamProvider nextWorkspaceDeclarationStreamProvider) {
       
        if (!promptSaveCurrentTableTopIfNonEmpty(null, null)){
            return;
        }
       
        gemCutterSplashScreen = new GemCutterSplashScreen(this);
        gemCutterSplashScreen.pack();
       
        gemCutterSplashScreen.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        centerWindow(gemCutterSplashScreen);
       
        getTableTopPanel().enableMouseEvents(false);

        // Keep the GUI locked while compiling.
        final GUIState oldGUIState = getGUIState();
        setEnabled(false);
        enterGUIState(GUIState.LOCKED_DOWN);
       
        // Create the workspace loading thread
        Thread thread = new Thread() {
            public void run() {
                try {
                    // Update the preferred working module if the user is working on something.
                    if (getTableTop().getGemGraph().getGems().size() > 1) {
                        preferredWorkingModuleName = getWorkingModuleName();
                    }
                   
                    String clientID = WorkspaceConfiguration.getDiscreteWorkspaceID(DEFAULT_WORKSPACE_CLIENT_ID);
                    GemCutter.this.workspaceManager = WorkspaceManager.getWorkspaceManager(clientID);
                    initWorkspace(nextWorkspaceDeclarationStreamProvider);

                    final CALWorkspace workspace = workspaceManager.getWorkspace();
                   
                   
                    // Find number of modules to be loaded and pass this information to progress bar
                    int nModules = workspace.getModuleNames().length;
                    gemCutterSplashScreen.setProgressBarMaxValue(nModules);
                   
                    // Compile specified module or all modules in workspace
                    long compileStartTime = System.currentTimeMillis();
                    boolean foundErrors = compileWorkspace(false);
                    final String statusMessage;
                    if (!foundErrors) {
                        statusMessage = GemCutterMessages.getString("SM_RecompilationFinished", Double.toString((System.currentTimeMillis() - compileStartTime)/1000.0));               
                    } else {               
                        statusMessage = GemCutterMessages.getString("SM_RecompilationErrors");               
                    }
                   
                    SwingUtilities.invokeAndWait(new Runnable() {
                        public void run() {
                            statusMessageManager.displayMessage(this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);
                           
                            // The new working module, if any.
                            ModuleName newWorkingModuleName;
                           
                            // Change to the preferred working module if it's not the current module, and it exists.
                            if (preferredWorkingModuleName != null && workspace.getMetaModule(preferredWorkingModuleName) != null) {
                                newWorkingModuleName = preferredWorkingModuleName;
                            } else {
                                newWorkingModuleName = getInitialWorkingModuleName(workspace);
                            }
                           
                            // Create the perspective
                            MetaModule initialWorkingModule = workspace.getMetaModule(newWorkingModuleName);
                            perspective = new Perspective(workspace, initialWorkingModule);
                           
                            // Clear the TableTop before resetting the runners, so that value editors can be closed
                            // by the original ValueEditorHierarchyManager.
                            newTableTop();
                           
                            // Reset the runners
                            try{
                                initRunners();
                            } catch (ValueEntryException exception) {
                                JOptionPane.showMessageDialog(GemCutter.this, exception.getMessage() + "\nCause: " + exception.getCause().getMessage() + "\n\nGemCutter will shut-down.",
                                        "Error in initializing the value entry handlers:", JOptionPane.ERROR_MESSAGE);
                            }  

                            // the perspective has changed, so anything that caches a perspective must be reset
                            forgetSearchDialog();
                           
                            // Update the preferred working module.
                            preferredWorkingModuleName = newWorkingModuleName;
                           
                            // Now, create a new the TableTop.
                            newTableTop();
                           
                            // clear the undo stack and dirty edit.
                            extendedUndoManager.discardAllEdits();
                            editToUndoWhenNonDirty = null;
                            updateUndoWidgets();
                           
                            // Also update the window title.
                            updateWindowTitle();
                           
                            // set workspace name in Gem Browser to include name of workspace file
                            getGemBrowser().setWorkspaceNodeName(nextWorkspaceDeclarationStreamProvider.getName());
                           
                            // Refresh the gem browser and navigator to show any new gems
                            getGemBrowser().reinitialize(perspective);
                            getNavigatorOwner().refresh();
                           
                            // Ensure the module is visible.
                            getGemBrowser().getBrowserTree().selectDrawerNode(newWorkingModuleName);
                        }
                    });
                   
                } catch (InterruptedException e) {
                    IllegalStateException ex = new IllegalStateException("This thread should not be interrupted by anything.");
                    ex.initCause(e);
                    throw ex;
                } catch (InvocationTargetException e) {
                    IllegalStateException ex = new IllegalStateException("The invokeAndWait call should always succeed.");
                    ex.initCause(e);
                    throw ex;
                } finally {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            getTableTopPanel().enableMouseEvents(true);
                            enterGUIState(oldGUIState);
                            GemCutter.this.setEnabled(true);
                            gemCutterSplashScreen.dispose();
                            gemCutterSplashScreen = null;
                        }
                    });
                }
            }
        };
       
        // start the thread then launch the splash screen (which will be closed by the thread when it is done)
        thread.start();
        gemCutterSplashScreen.setVisible(true);
    }

    /**
     * Return the action for the deploy workspace menu item
     * @return Action
     */
    private Action getDeployWorkspaceToEnterpriseAction() {

        if (deployWorkspaceToEnterpriseAction == null) {
            deployWorkspaceToEnterpriseAction = new AbstractAction (getResourceString("DeployWorkspaceToEnterprise")) {
                                                       
                private static final long serialVersionUID = -3305904595279750182L;

                public void actionPerformed(ActionEvent evt) {
                    handleDeployWorkspaceToEnterpriseAction();
                }
            };
           
            deployWorkspaceToEnterpriseAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_DEPLOY_WORKSPACE));
            deployWorkspaceToEnterpriseAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("DeployWorkspaceToEnterpriseToolTip"));
        }
        return deployWorkspaceToEnterpriseAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to deploy the current workspace.
     */
    private void handleDeployWorkspaceToEnterpriseAction() {

        // Attempt to ensure that the user is connected to CE.
        if (!ensureEnterpriseConnectionIsReady()) {
            return;
        }
       
        Vault ceVault = enterpriseSupport.getEnterpriseVault();
        if (ceVault != null) {
            handleDeployWorkspaceAction(ceVault);
        }
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to deploy the current workspace.
     * @param vault the vault to which to deploy the new workspace.
     */
    private void handleDeployWorkspaceAction(Vault vault) {

        Status deployStatus = new Status("Deploy status");

        // Map from module name to the latest revision identical to the current form of that module, if any.
        Map<ModuleName, Integer> moduleNameToExistingRevisionMap = new HashMap<ModuleName, Integer>();
       
        // Iterate over the modules in the workspace.
        ModuleName[] moduleNames = getWorkspace().getModuleNames();
       
        DeployWorkspaceDialog.ModuleRevisionInfo[] moduleRevisions = new DeployWorkspaceDialog.ModuleRevisionInfo[moduleNames.length];
        for (int i = 0; i < moduleNames.length; i++) {
            ModuleName moduleName = moduleNames[i];
           
            // TODOEL: should we try to add getLatestIdenticalRevision() to the vault interface?
            int latestIdenticalRevision =
                enterpriseSupport.getLatestIdenticalRevisionFromMaybeEnterpriseVault(vault, moduleName, getWorkspace(), deployStatus);

            if (latestIdenticalRevision > 0) {
                // An identical revision already exists.
                moduleRevisions[i] = new DeployWorkspaceDialog.ModuleRevisionInfo(moduleName, latestIdenticalRevision, false);
                moduleNameToExistingRevisionMap.put(moduleName, new Integer(latestIdenticalRevision));
           
            } else {
                // We must deploy a new revision of the module.
                RevisionHistory revisionHistory = vault.getModuleRevisionHistory(moduleName);
                int latestRevision = revisionHistory.getLatestRevision();
                int revisionToDeploy = (latestRevision < 0) ? 1 : latestRevision + 1;
                moduleRevisions[i] = new DeployWorkspaceDialog.ModuleRevisionInfo(moduleName, revisionToDeploy, true);
            }
        }
       
        // Handle any errors (in vaultStatus) getting the latest revision info.
        if (deployStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
            JOptionPane.showMessageDialog(this, deployStatus.getDebugMessage(),
                                          getResourceString("DeployWorkspaceFailedTitle"), JOptionPane.ERROR_MESSAGE);
            return;
        }
       
       
        // Display the dialog for deploying the workspace.
        String[] availableWorkspaceDeclarations = vault.getAvailableWorkspaceDeclarations(deployStatus);
       
        DeployWorkspaceDialog deployWorkspaceDialog = new DeployWorkspaceDialog(GemCutter.this, moduleRevisions, availableWorkspaceDeclarations);
        boolean accepted = deployWorkspaceDialog.doModal();
       
        if (!accepted) {
            return;
        }

        // Map from module name to the deployed revision for that module.
        Map<ModuleName, Integer> moduleNameToDeployedRevisionMap = new TreeMap<ModuleName, Integer>();   

        // Get the result, and deploy...
        DeployWorkspaceDialog.DeployDialogResult result = deployWorkspaceDialog.getResult();

        ModuleName[] modulesToDeploy = result.getModulesToDeploy();
        for (final ModuleName moduleToDeploy : modulesToDeploy) {
            Integer existingRevisionInteger = moduleNameToExistingRevisionMap.get(moduleToDeploy);
            if (existingRevisionInteger != null) {
                // Don't need to add the module to the vault.  Just reuse the revision number
                moduleNameToDeployedRevisionMap.put(moduleToDeploy, existingRevisionInteger);
               
            } else {
                // Actually add the module to the vault.
                int addedRevisionNumber = vault.putStoredModule(moduleToDeploy, getWorkspace(), deployStatus);
               
                // Remember the revision number which was added, so we can use it in the workspace declaration.
                moduleNameToDeployedRevisionMap.put(moduleToDeploy, new Integer(addedRevisionNumber));
            }
        }
       
        // Handle any errors (in vaultStatus) adding the module revisions.
        if (deployStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
            JOptionPane.showMessageDialog(this, deployStatus.getDebugMessage(),
                                          getResourceString("DeployWorkspaceFailedTitle"), JOptionPane.ERROR_MESSAGE);
            return;
        }
       
        // Now deploy the workspace file..
        String workspaceDeclarationName = result.getWorkspaceName();
       
        String vaultDescriptor = vault.getVaultProvider().getVaultDescriptor();
        String vaultLocation = vault.getLocationString();
       
        VaultElementInfo[] vaultModuleInfo = new VaultElementInfo[moduleNameToDeployedRevisionMap.size()];
       
        int index = 0;
        for (final Map.Entry<ModuleName, Integer> mapEntry : moduleNameToDeployedRevisionMap.entrySet()) {
            String moduleName = mapEntry.getKey().toString();
            int deployedRevisionNum = mapEntry.getValue().intValue();
           
            vaultModuleInfo[index] = VaultElementInfo.makeBasic(vaultDescriptor, moduleName, vaultLocation, deployedRevisionNum);
            index++;
        }
       
        // Create the workspace declaration.
        WorkspaceDeclaration workspaceDeclaration = new WorkspaceDeclaration(vaultModuleInfo);
       
        // Put the declaration in the vault.
        vault.putWorkspaceDeclaration(workspaceDeclarationName, workspaceDeclaration, deployStatus);

        // Handle any errors (in vaultStatus) adding the workspace deployment.
        if (deployStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
            JOptionPane.showMessageDialog(this, deployStatus.getDebugMessage(),
                                          getResourceString("DeployWorkspaceFailedTitle"), JOptionPane.ERROR_MESSAGE);
            return;
        }
    }
   
    /**
     * @return the action for the export to Cars menu item
     */
    private Action getExportWorkspaceToCarsAction() {
       
        if (exportWorkspaceToCarsAction == null) {
            exportWorkspaceToCarsAction = new AbstractAction(getResourceString("ExportWorkspaceToCars")) {
                                                       
                private static final long serialVersionUID = -3900731741152188465L;

                public void actionPerformed(ActionEvent evt) {
                    handleExportWorkspaceToCarsAction();
                }
            };
           
            exportWorkspaceToCarsAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_EXPORT_WORKSPACE_TO_CARS));
            exportWorkspaceToCarsAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ExportWorkspaceToCarsToolTip"));
        }
        return exportWorkspaceToCarsAction;
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to export the current workspace to one or more Cars.
     */
    private void handleExportWorkspaceToCarsAction() {
       
        ExportCarDialog dialog = new ExportCarDialog(this, workspaceManager.getInitialWorkspaceDeclarationName());
        centerWindow(dialog);
        dialog.setVisible(true);

        if (!dialog.isOKSelected()) {
            return;
        }
       
        Set<String> carsToExclude = Collections.emptySet();
        CarBuilder.BuilderOptions options = new CarBuilder.BuilderOptions(
            dialog.shouldSkipModulesAlreadyInCars(),
            dialog.shouldGenerateCorrespWorkspaceDecl(),
            dialog.shouldOmitCarSuffixInWorkspaceDeclName(),
            dialog.shouldBuildSourcelessModules(),
            carsToExclude,
            dialog.shouldGenerateCarJarSuffix());
       
        if (dialog.shouldBuildSingleCar()) {
            File selectedFile = dialog.getSingleCarOutputDirectory();
           
            // Confirm overwrite if the file exists.
            if (selectedFile.exists()) {
                String title = getResourceString("ConfirmOverwriteTitle");
                String message = getResourceString("ConfirmOverwriteCarMessage");           
                int continueChoice = JOptionPane.showConfirmDialog(GemCutter.this, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
                if (continueChoice != JOptionPane.OK_OPTION) {
                    return;
                }
            }
           
            buildSingleCar(selectedFile, options);
           
        } else if (dialog.shouldBuildOneCarPerWorkspaceDecl()) {
            buildOneCarPerWorkspaceDeclaration(dialog.getOneCarPerWorkspaceDeclOutputDirectory(), options);
        }
    }

    /**
     * Builds a Car from the current workspace and writes it out to the specified output file.
     * @param outputDirectory the directory to which Car files will be written.
     * @param options configuration options for the Car builder.
     */
    private void buildSingleCar(final File outputDirectory, final CarBuilder.BuilderOptions options) {
        // Set up the progress monitor for the potentially length export process
        ExportCarProgressMonitor monitor = new ExportCarProgressMonitor();
       
        // Configure the CarBuilder
        final CarBuilder.Configuration config =
            CarBuilder.Configuration.makeConfigOptionallySkippingModulesAlreadyInCars(workspaceManager, options);
        config.setMonitor(monitor);
       
        final String carName = CarBuilder.makeCarNameFromSourceWorkspaceDeclName(workspaceManager.getInitialWorkspaceDeclarationName());
       
        // Set up a SwingWorker to run the export process in a separate thread.
        SwingWorker worker = new SwingWorker() {
            public Object construct() {
                try {
                    CarBuilder.buildCar(config, outputDirectory, carName, options);
                   
                    // Display a status message.
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            String statusMessage = GemCutterMessages.getString("SM_CarExported", carName);
                            statusMessageManager.displayMessage(GemCutter.this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);
                        }
                    });
                   
                } catch (final IOException e) {
                    // Inform the user.
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            showActionFailureDialog(getResourceString("ExportWorkspaceToCarFailedDialogTitle"), getResourceString("ExportWorkspaceToCarFailedDialogMessage"), e);
                        }
                    });
                }
                return null;
            }
        };
       
        // Start the CarBuilder worker and then launch the progress monitor (which is modal and would block the UI until
        // either the progress reaches 100% or the monitor is canceled)
        worker.start();
        monitor.showDialog();
        // there is no interesting value to get from the worker, but a call to get() effectively does a join
        // on the worker thread
        worker.get();
    }
   
    /**
     * Builds one Car file per workspace declaration file that constitutes the initial declaration of the CAL workspace.
     * @param outputDirectory the directory to which Car files will be written.
     * @param options configuration options for the Car builder.
     */
    private void buildOneCarPerWorkspaceDeclaration(final File outputDirectory, final CarBuilder.BuilderOptions options) {
        // Set up the progress monitor for the potentially length export process
        final ExportCarProgressMonitor monitor = new ExportCarProgressMonitor();
       
        // Set up a SwingWorker to run the export process in a separate thread.
        SwingWorker worker = new SwingWorker() {
            public Object construct() {
                try {
                    CarBuilder.buildOneCarPerWorkspaceDeclaration(workspaceManager, monitor, outputDirectory, options);
                   
                    // Display a status message.
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            String statusMessage = GemCutterMessages.getString("SM_CarExported", outputDirectory.getAbsolutePath());
                            statusMessageManager.displayMessage(GemCutter.this, statusMessage, StatusMessageDisplayer.MessageType.TRANSIENT, true);
                        }
                    });
                   
                } catch (final IOException e) {
                    // Inform the user.
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            showActionFailureDialog(getResourceString("ExportWorkspaceToCarFailedDialogTitle"), getResourceString("ExportWorkspaceToCarFailedDialogMessage"), e);
                        }
                    });
                }
                return null;
            }
        };
       
        // Start the CarBuilder worker and then launch the progress monitor (which is modal and would block the UI until
        // either the progress reaches 100% or the monitor is canceled)
        worker.start();
        monitor.showDialog();
        // there is no interesting value to get from the worker, but a call to get() effectively does a join
        // on the worker thread
        worker.get();
    }
   
    /**
     * @return Action the action that handles the "Rename Gem" functionality
     */
    private Action getRenameGemAction() {
       
        if (renameGemAction == null) {
            try {
                renameGemAction = new AbstractAction(getResourceString("RenameGemAction"), RenameRefactoringDialog.FUNCTION_ICON) {                   
                    private static final long serialVersionUID = 3320148498474606760L;

                    public void actionPerformed(ActionEvent evt) {
                        showRenameEntityDialog(RenameRefactoringDialog.EntityType.Gem, null);
                    }
                };
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return renameGemAction;
    }
   
    /**
     * @return Action the action that handles the "Rename Type" functionality
     */
    private Action getRenameTypeAction() {
       
        if (renameTypeAction == null) {
            try {
                renameTypeAction = new AbstractAction(getResourceString("RenameTypeAction"), RenameRefactoringDialog.TYPECONS_ICON) {
                    private static final long serialVersionUID = -417842886714530380L;

                    public void actionPerformed(ActionEvent evt) {
                        showRenameEntityDialog(RenameRefactoringDialog.EntityType.TypeConstructor, null);
                    }
                };
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return renameTypeAction;
    }
   
    /**
     * @return Action the action that handles the "Rename Type Class" functionality
     */
    private Action getRenameClassAction() {
       
        if (renameClassAction == null) {
            try {
                renameClassAction = new AbstractAction(getResourceString("RenameClassAction"), RenameRefactoringDialog.TYPECLASS_ICON) {
                    private static final long serialVersionUID = 685835542162469962L;

                    public void actionPerformed(ActionEvent evt) {
                        showRenameEntityDialog(RenameRefactoringDialog.EntityType.TypeClass, null);
                    }
                };
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return renameClassAction;
    }
   
    /**
     * @return Action the action that handles the "Rename Module" functionality
     */
    private Action getRenameModuleAction() {
       
        if (renameModuleAction == null) {
            try {
                renameModuleAction = new AbstractAction(getResourceString("RenameModuleAction"), RenameRefactoringDialog.MODULE_ICON) {
                   
                    private static final long serialVersionUID = -6072806933029026625L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        showRenameModuleDialog(null);
                    }
                };
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return renameModuleAction;
    }
   
    /**
     * Shows the "Rename Gem" refactoring dialog, then waits for it to close before recompiling
     * (if any refactorings are made)
     * @param fromName entity to rename; null if the dialog is to provide ability to choose
     */
    void showRenameEntityDialog(RenameRefactoringDialog.EntityType entityType, String fromName) {
        tableTop.getUndoableEditSupport().beginUpdate();
        RenameRefactoringDialog renameDialog =
            new RenameRefactoringDialog(GemCutter.this, getWorkspaceManager(), perspective, fromName, null, entityType, isAllowPreludeRenamingMode(), isAllowDuplicateRenamingMode());       
       
        // Display refactoring dialog and recompile dirty modules if any changes are made       
        RenameRefactoringDialog.Result renameResult = renameDialog.display();
        if (renameResult != null) {
            QualifiedName qualifiedFromName = QualifiedName.makeFromCompoundName(renameResult.getFromName());
            QualifiedName qualifiedToName = QualifiedName.makeFromCompoundName(renameResult.getToName());
            try {
                GemCutterRenameUpdater designUpdater = new GemCutterRenameUpdater(new Status("GemGraph update status"), getTypeChecker(), qualifiedToName, qualifiedFromName, renameResult.getCategory());
                designUpdater.updateTableTop(this);
            } catch (GemEntityNotPresentException fanpe) {
                // Couldn't reload the functional agent for one of the gems.              
                JOptionPane.showMessageDialog(this, "Error reloading functional agent: " + fanpe.getEntityName(),
                        "Reload error", JOptionPane.WARNING_MESSAGE);
            }          
            tableTop.getUndoableEditSupport().postEdit(
                    new UndoableRenameGemEdit(qualifiedFromName.getUnqualifiedName(), qualifiedToName.getUnqualifiedName(), qualifiedFromName.getModuleName(), renameResult.getEntityType(), this));
            tableTop.getUndoableEditSupport().endUpdate();
            recompileWorkspace(true);           
        } else {
            tableTop.getUndoableEditSupport().endUpdateNoPost();
        }
    }
   
    /**
     * Shows the "Rename Module" dialog
     * @param moduleName  module to rename; null if the dialog is to provide the ability to choose
     */
    void showRenameModuleDialog(ModuleName moduleName) {
        final String moduleNameString = (moduleName == null) ? null : moduleName.toSourceText();
        RenameRefactoringDialog renameDialog =
            new RenameRefactoringDialog(GemCutter.this, getWorkspaceManager(), perspective, moduleNameString, null, RenameRefactoringDialog.EntityType.Module, isAllowPreludeRenamingMode(), false);
       
        // Display refactoring dialog and recompile dirty modules if any changes are made
        RenameRefactoringDialog.Result renameResult = renameDialog.display();
        if (renameResult != null) {
            QualifiedName qualifiedFromName = QualifiedName.make(ModuleName.make(renameResult.getFromName()), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING);
            QualifiedName qualifiedToName = QualifiedName.make(ModuleName.make(renameResult.getToName()), Refactorer.Rename.UNQUALIFIED_NAME_FOR_MODULE_RENAMING);
            try {
                GemCutterRenameUpdater designUpdater = new GemCutterRenameUpdater(new Status("GemGraph update status"), getTypeChecker(), qualifiedToName, qualifiedFromName, renameResult.getCategory());
                designUpdater.updateTableTop(this);
            } catch (GemEntityNotPresentException fanpe) {
                // Couldn't reload the functional agent for one of the gems.
                JOptionPane.showMessageDialog(this, "Error reloading functional agent: " + fanpe.getEntityName(),
                        "Reload error", JOptionPane.WARNING_MESSAGE);
            }
            tableTop.getUndoableEditSupport().postEdit(
                    new UndoableRenameModuleEdit(renameResult.getFromName(), renameResult.getToName(), this));
           
            // If the working module was renamed, update the perspective's reference to it.
            if (perspective.getWorkingModuleName().equals(ModuleName.make(renameResult.getFromName()))) {
                perspective.setWorkingModule(ModuleName.make(renameResult.getToName()));
            }
            recompileWorkspace(true);
        }
    }

    /**
     * Return the action for the recompile menu item
     * @return Action
     */
    private Action getRecompileAction() {
       
        if (recompileAction == null) {
            recompileAction = new AbstractAction (getResourceString("RecompileWorkspace")) {
                                                       
                private static final long serialVersionUID = -5508747529637517065L;

                public void actionPerformed(ActionEvent evt) {
                    // recompile all modules from the source provider.
                    recompileWorkspace(false);
                }
            };
           
            recompileAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_RECOMPILE));
            recompileAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RecompileWorkspaceToolTip"));
        }
        return recompileAction;
    }
   
    /**
     * Return the action for the recompile menu item
     * @return Action
     */
    private Action getCompileModifiedAction() {
       
        if (compileModifiedAction == null) {
            compileModifiedAction = new AbstractAction (getResourceString("CompileModifiedModules")) {
                                                       
                private static final long serialVersionUID = 2551065637046377474L;

                public void actionPerformed(ActionEvent evt) {
                    // recompile dirty modules from the source provider.
                    recompileWorkspace(true);
                }
            };
           
            compileModifiedAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_COMPILE_MODIFIED));
        }
        return compileModifiedAction;
    }
   
    /**
     * Return the action for the create minimal workspace menu item
     */
    private Action getCreateMinimalWorkspaceAction() {
       
        if (createMinimalWorkspaceAction == null) {
            createMinimalWorkspaceAction = new AbstractAction (getResourceString("CreateMinimalWorkspace")) {
                                                       
                private static final long serialVersionUID = -8571845374052703275L;

                public void actionPerformed(ActionEvent evt) {
                    handleCreateMinimalWorkspaceAction();
                }
            };
           
            createMinimalWorkspaceAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_CREATE_MINIMAL_WORKSPACE));
            createMinimalWorkspaceAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("CreateMinimalWorkspaceToolTip"));
        }
        return createMinimalWorkspaceAction;
    }
   
    /**
     * Handle the situation where the user has indicated that they would like to create a minimal workspace.
     */
    private void handleCreateMinimalWorkspaceAction() {
       
        CreateMinimalWorkspaceDialog dialog = new CreateMinimalWorkspaceDialog(this, workspaceManager.getWorkspace());
        centerWindow(dialog);
        dialog.setVisible(true);

        if (!dialog.isOKSelected()) {
            return;
        }
       
        final File outputFile = dialog.getOutputFile();
        final String workspaceDeclaration = dialog.getMinimalWorkspaceDeclaration();
       
        IOException ex = null;
       
        FileWriter fw = null;
        try {
            fw = new FileWriter(outputFile);
            fw.write(workspaceDeclaration);
            fw.flush();
        } catch (IOException e) {
            ex = e;
        } finally {
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    ex = e;
                }
            }
        }
       
        if (ex != null) {
            showActionFailureDialog(getResourceString("CreateMinimalWorkspaceFailedDialogTitle"), getResourceString("CreateMinimalWorkspaceFailedDialogMessage"), ex);
            return;
        }
       
        if (dialog.shouldSwitchAfter()) {
            handleSwitchWorkspaceAction(new WorkspaceDeclaration.StreamProvider() {
                public String getName() {
                    return outputFile.getName();
                }
                public String getLocation() {
                    String parentDir = outputFile.getParent();
                    if (parentDir == null) {
                        return "(temporary)";
                    } else {
                        return parentDir;
                    }
                }
                public String getDebugInfo(VaultRegistry vaultRegistry) {
                    return "from file: " + outputFile.getAbsolutePath();
                }
                public InputStream getInputStream(VaultRegistry vaultRegistry, Status status) {
                    return new ByteArrayInputStream(TextEncodingUtilities.getUTF8Bytes(workspaceDeclaration));
                }
            });
        }
    }

    /**
     * Return the action for the workspace info menu item
     * @return Action
     */
    private Action getWorkspaceInfoAction() {
       
        if (workspaceInfoAction == null) {
            workspaceInfoAction = new AbstractAction (getResourceString("WorkspaceInfo")) {
                                                       
                private static final long serialVersionUID = -1981432576429432927L;

                public void actionPerformed(ActionEvent evt) {
                    handleWorkspaceInfoAction();
                }
            };
           
            workspaceInfoAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_WORKSPACE_INFO));
            workspaceInfoAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("WorkspaceInfoToolTip"));
        }
        return workspaceInfoAction;
    }

    /**
     * Handle the situation where the user has indicated that they would like to see more info about the current workspace.
     */
    private void handleWorkspaceInfoAction() {

        String title = getResourceString("WorkspaceInfo_Title");
        String message = getResourceString("WorkspaceInfo_Message");

        // Current workspace
       
        StringBuilder details = new StringBuilder();

        details.append(getResourceString("WorkspaceInfo_Declaration"));
        details.append("     " + getWorkspaceManager().getInitialWorkspaceDeclarationName() + "\n");
        details.append("        - " + getWorkspaceManager().getInitialWorkspaceDeclarationDebugInfo() + "\n");
       
        CALWorkspace workspace = getWorkspace();
        details.append(getResourceString("WorkspaceInfo_Location") + workspace.getWorkspaceLocationString() + "\n");

        // Modules loaded       
        details.append(getResourceString("WorkspaceInfo_ModulesLoaded"));
        ModuleName[] moduleNames = workspace.getModuleNames();
        Arrays.sort(moduleNames);
       
        int nModules = moduleNames.length;
        if (nModules > 0) {
            for (int i = 0; i < nModules; i++) {
                details.append("     " + moduleNames[i]);
               
                VaultElementInfo vaultInfo = workspace.getVaultInfo(moduleNames[i]);

                if (vaultInfo != null) {
                    long moduleRevision = vaultInfo.getRevision();

                    String descriptor = vaultInfo.getVaultDescriptor();
                    String locationString = vaultInfo.getLocationString();
                   
                    details.append("     " + descriptor);
                    if (locationString != null) {
                        details.append("(" + locationString + ")");
                    }
                    details.append("  " + GemCutterMessages.getString("WorkspaceInfo_Revision", new Long(moduleRevision)));
                }

                details.append("\n");
               
                String debugInfo = workspace.getDebugInfoForModule(moduleNames[i]);
               
                if (debugInfo != null) {
                    details.append("        - ").append(debugInfo).append("\n");
                }
            }
        } else {           
            details.append(getResourceString("WorkspaceInfo_None"));
        }
       
        DetailsDialog dialog = new DetailsDialog(GemCutter.this, title, message, details.toString(), (Icon)null);
        dialog.setDetailsVisible(true);
        dialog.doModal();
    }

    /**
     * Do the work necessary to carry out a user-initiated action to change the current working module in the GemCutter
     * @param newWorkingModule the name of the new working module.
     */
    void doChangeModuleUserAction(ModuleName newWorkingModule) {
       
        // Prompt the user if the current tabletop needs be to saved
        if (!promptSaveCurrentTableTopIfNonEmpty(null, null)){
            return;
        }
       
        // Undo edits are not needed because creating a new tabletop will clear the undo stack
       
        // Change the module.
        changeModuleAndNewTableTop(newWorkingModule, true);

        // Update the GemCutter window title.
        updateWindowTitle();
    }

    /**
     * Performs the Add Type Declarations refactoring on the module named targetModule.
     * The operation happens on its own thread with a status display.
     * @param targetModule String name of the module to refactor
     */
    void doAddTypeDeclsUserAction(final ModuleName targetModule) {

        Thread refactoringThread = new AbstractThreadWithSimpleModalProgressDialog("type-decl adder thread", this, GemCutterMessages.getString("AddTypedeclsTitle"), 0, 3) {
            public void run() {
               
                Refactorer typeDeclsAdder = new Refactorer.InsertTypeDeclarations(perspective.getWorkspace().asModuleContainer(), targetModule, -1, -1, -1, -1);
               
                showMonitor();
                setStatus(GemCutterMessages.getString("CalculatingModificationsStatus"));
               
                try {
                    ExtendedUndoableEditSupport undoableEditSupport = tableTop.getUndoableEditSupport();
                    undoableEditSupport.beginUpdate();
           
                    CompilerMessageLogger logger = new MessageLogger();
                    typeDeclsAdder.calculateModifications(logger);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileCalculatingModifications"));
                        return;
                    }
                   
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("ApplyingModificationsStatus"));
                   
                    typeDeclsAdder.apply(logger);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileApplyingModifications"));
                        return;
                    }
                   
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("RecompilingStatus"));
   
                    recompileWorkspace(true);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileRecompiling"));
                        return;
                    }
                                       
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("SuccessStatus"));
   
                    UndoableRefactoringEdit addTypeDeclsEdit = new UndoableRefactoringEdit(GemCutter.this, typeDeclsAdder, GemCutterMessages.getString("AddTypedeclsPresentationName"));
                    undoableEditSupport.setEditName(addTypeDeclsEdit.getPresentationName());
                    undoableEditSupport.postEdit(addTypeDeclsEdit);
           
                    // End the update.
                    undoableEditSupport.endUpdate();
                   
                    // Make sure that the dialog is active for long enough to be visible
                    try {
                        sleep(750);
                    } catch(InterruptedException e) {
                         // who cares, really
                    }
                } finally {
               
                    closeMonitor();
                }
            }
        };
       
        refactoringThread.start();   
    }
   
    /**
     * Helper function to show the messages contained in logger, with some explanatory
     * text.
     * @param logger CompilerMessageLogger containing the messages to show
     * @param localizedDescriptionText String to show above the list of messages
     */
    void showCompilationErrors(CompilerMessageLogger logger, String localizedDescriptionText) {
       
        // If this logger contains no warnings/errors, then there's nothing to show
        if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.WARNING) < 0) {
            return;
        }
       
        final boolean warningsOnly = logger.getMaxSeverity() == CompilerMessage.Severity.WARNING;
        CompilerMessageDialog messageDialog = new CompilerMessageDialog(this, warningsOnly);
        messageDialog.addMessages(logger);
        messageDialog.setDescriptionText(localizedDescriptionText);
        messageDialog.setVisible(true);
    }
   
    /**
     * Performs the Clean Imports refactoring on the module named targetModule.
     * The operation happens on its own thread with a status display.
     * @param targetModule String name of the module to refactor
     */
    void doCleanImportsUserAction(final ModuleName targetModule) {

        Thread refactoringThread = new AbstractThreadWithSimpleModalProgressDialog("import-cleaner thread", this, GemCutterMessages.getString("CleanImportsTitle"), 0, 3) {
            public void run() {
               
                Refactorer importCleaner = new Refactorer.CleanImports(perspective.getWorkspace().asModuleContainer(), targetModule, false);
               
                showMonitor();
                setStatus(GemCutterMessages.getString("CalculatingModificationsStatus"));
               
                try {
                    ExtendedUndoableEditSupport undoableEditSupport = tableTop.getUndoableEditSupport();
                    undoableEditSupport.beginUpdate();
           
                    CompilerMessageLogger logger = new MessageLogger();
                    importCleaner.calculateModifications(logger);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileCalculatingModifications"));
                        return;
                    }
                   
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("ApplyingModificationsStatus"));
                   
                    importCleaner.apply(logger);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileApplyingModifications"));
                        return;
                    }
                   
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("RecompilingStatus"));
   
                    recompileWorkspace(true);
                   
                    if(logger.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) {
                        closeMonitor();
                        showCompilationErrors(logger, getResourceString("ErrorWhileRecompiling"));
                        return;
                    }
                   
                    incrementProgress();
                    setStatus(GemCutterMessages.getString("SuccessStatus"));
   
                    UndoableRefactoringEdit cleanImportsEdit = new UndoableRefactoringEdit(GemCutter.this, importCleaner, GemCutterMessages.getString("CleanImportsPresentationName"));
                    undoableEditSupport.setEditName(cleanImportsEdit.getPresentationName());
                    undoableEditSupport.postEdit(cleanImportsEdit);
           
                    // End the update.
                    undoableEditSupport.endUpdate();
                   
                    // Make sure that the dialog is active for long enough to be visible
                    try {
                        sleep(750);
                    } catch(InterruptedException e) {
                         // who cares, really
                    }
                } finally {
               
                    closeMonitor();
                }
            }
        };
       
        refactoringThread.start();
    }
   
    /**
     * Change to the given module, and create a new table top
     * @param targetModuleName the module to which to switch.
     * @param ensureVisibleInBrowser ensure the node for the module is visible in the browser.
     */
    void changeModuleAndNewTableTop(final ModuleName targetModuleName, boolean ensureVisibleInBrowser) {
        // Update the perspective
        perspective.setWorkingModule(targetModuleName);
       
        // Update the gem browser.
        getGemBrowser().refresh();
       
        // Update the preferred working module.
        preferredWorkingModuleName = targetModuleName;
       
        // Ensure the module is visible.
        // Note: the new module will be always be selected, disregarding the previous selection state
        if (ensureVisibleInBrowser) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    getGemBrowser().getBrowserTree().selectDrawerNode(targetModuleName);
                }
            });
        }
       
        // New tableTop.
        newTableTop();
    }
   
    /**
     * Return the RunMenu property value.
     * @return JMenu
     */
    private JMenu getRunMenu() {
       
        if (runMenu == null) {
            try {
                runMenu = makeNewMenu(null);
                runMenu.setName("RunMenu");
                runMenu.setText(getResourceString("RunMenu"));
                runMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_RUN_MENU);
               
                runMenu.add(getRunMenuItem());
                runMenu.add(getRunSubMenu());
                runMenu.add(makeNewMenuItem(getStopAction()));
                runMenu.add(makeNewMenuItem(getResetAction()));

                runMenu.addMenuListener(new MenuListener() {
               
                    // The listener that the popup is currently using.
                    private RunDropDownMenuListener currentListener;
               
                    public void menuSelected(MenuEvent evt) {
                   
                        JPopupMenu popup = ((JMenu)evt.getSource()).getPopupMenu();
                        RunDropDownMenuListener menuListener = new RunDropDownMenuListener();
                       
                        // Add the new listener
                        popup.addPopupMenuListener(menuListener);
                        runMenuItem.addChangeListener(menuListener);

                        // Add the menu item to the listeners internal menuitem->gem map
                        DisplayedGem displayedGem = getTableTop().getDisplayedGem(currentGemToRun);
                        menuListener.addMenuItem(runMenuItem, displayedGem);
                       
                        // We don't want the default run gem to be focused whenever the menu is shown
                        menuListener.setFocusOnPopup(false);
                   
                        // Remove the old listener
                        popup.removePopupMenuListener(currentListener);
                        runMenuItem.removeChangeListener(currentListener);

                        currentListener = menuListener;
                    }
               
                    public void menuDeselected(MenuEvent evt) {}
               
                    public void menuCanceled(MenuEvent evt) {}
                });
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return runMenu;
    }
   
    /**
     * Returns the run menu item to run the last run gem. Access to this item is
     * important so it can re replaced by the resume menu item while a gem is running.
     * @return JMenu
     */
    private JMenuItem getRunMenuItem() {
       
        if (runMenuItem == null) {
            runMenuItem = makeNewMenuItem (getRunAction());
            runMenuItem.setEnabled(false);
        }
       
        return runMenuItem;
    }
   
    /**
     * Returns the run menu drop down item to run any runnable gem.
     * @return JMenu
     */
    private JMenu getRunSubMenu() {
       
        if (runSubMenu == null) {
           
            runSubMenu = makeNewMenu(getResourceString("RunGemDropDown"));

            // Add a listener that will update the popup menu items whenever the menu is selected
            runSubMenu.addMenuListener(new MenuListener() {
               
                // The listener that the popup is currently using.
                private PopupMenuListener currentListener;
               
                public void menuSelected(MenuEvent evt) {
                   
                    JMenu menu = (JMenu) evt.getSource();
                    JPopupMenu popup = menu.getPopupMenu();
                   
                    // Remove the old listener
                    popup.removePopupMenuListener(currentListener);
                   
                    // Update the menu's popup menu with the latest list of menu items.
                    currentListener = prepareRunDropDownMenu(popup);
                }
               
                public void menuDeselected(MenuEvent evt) {
                }
               
                public void menuCanceled(MenuEvent evt) {
                }
            });
        }
       
        return runSubMenu;
    }
   
    /**
     * Returns the run button property value.
     * It is important to have access to this button because its location is necessary in displaying
     * the associated popup menu in the correct location.
     * @return JButton
     */
    private JButton getRunButton() {
        if (runButton == null) {
            try {
                // Build the button from the Run action
                runButton = makeNewButton(getRunAction());
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return runButton;
    }

    /**
     * Returns the Play drop down button property value.
     * It is important to have access to this button to enable/disable it if Gems can be run.
     */
    private JButton getRunDropDownButton() {
        if (runDropDownButton == null) {
            try {
                runDropDownButton = makeNewButton(getRunDropDownAction());
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
       
        return runDropDownButton;
    }
       
    /**
     * Return the action that handles running a gem.
     * @return Action
     */
    private Action getRunAction() {
       
        if (runAction == null) {
            try {
                runAction = new AbstractAction (getResourceString("RunGem"),
                                            new ImageIcon(getClass().getResource("/Resources/play.gif"))) {
                                                           
                    private static final long serialVersionUID = 5000876884559109717L;

                    public void actionPerformed(ActionEvent evt) {
                        DisplayedGem displayedGem = getTableTop().getDisplayedGem(currentGemToRun);
                        if (displayedGem != null) {
                            runTarget(displayedGem);
                        } else {
                            displayRunDropDownMenu();
                        }
                    }
                };
               
                runAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_RUN));
                runAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_RUN);
                runAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RunGemToolTip"));
                runAction.putValue(ACTION_BUTTON_IS_DROP_PARENT_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
       
        return runAction;
    }
   
    /**
     * Return the action that handles popping up the run gem menu when the
     * run gem drop down button is clicked.
     *
     */
    private Action getRunDropDownAction() {
       
        if (runDropDownAction == null) {
            try {
                runDropDownAction = new AbstractAction (getResourceString("RunGemDropDown"),
                                            new ImageIcon(getClass().getResource("/Resources/dropdownarrow.gif"))) {
                                                           
                    private static final long serialVersionUID = 4602841745380963356L;

                    public void actionPerformed(ActionEvent evt) {
                        displayRunDropDownMenu();
                    }
                };
               
                runDropDownAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("RunGemDropDownToolTip"));
                runDropDownAction.putValue(ACTION_BUTTON_IS_DROP_CHILD_KEY, Boolean.TRUE);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return runDropDownAction;
    }
   
    /**
     * Displays the pop up menu listing all roots when the play button is pressed.
     * Names of Collector Gems appear first in alphabetical order.  Names of anonymous gems come
     * second in alphabetical order (multiple occurrences of names will have an ordinal appended to
     * the name - assignment of ordinal is arbitrary).  ValueGems appear third.
     */
    public void displayRunDropDownMenu() {

        // Build a new popup menu and remember it in case we need to close it later.
        runDropDownMenu = new JPopupMenu();
       
        prepareRunDropDownMenu(runDropDownMenu);

        // Show the popup directly underneath the button, left-aligned with it
        Rectangle bounds = getRunDropDownButton().getBounds();
        runDropDownMenu.show(getToolBarPane(), bounds.x, bounds.y + bounds.height);
    }
   
    /**
     * Prepares the run button popup menu for display.  Appropriate listeners and menu items
     * are added to the menu. 
     * @param menu JPopupMenu - the popup menu that needs to be prepared
     * @return PopupMenuListener
     */
    private PopupMenuListener prepareRunDropDownMenu(JPopupMenu menu) {

        // Get a list of all roots and get the list of menu items for the roots
        // getGemMenuItems() will sort rootList so that the order matches the ordering of the menu items
        List<Gem> rootList = new ArrayList<Gem>(getTableTop().getGemGraph().getRoots());
        List<JComponent> menuItems = getGemMenuItems(rootList);
       
        // Remove any existing menu items
        menu.removeAll();

        // Create the listener to listen for menu or menu item events and add it to the popup menu
        RunDropDownMenuListener menuListener = new RunDropDownMenuListener();
        menu.addPopupMenuListener(menuListener);

        if (rootList.isEmpty()) {
            // There will be a menu item indicating that there are no gems to run
            menu.add((JMenuItem)menuItems.get(0));
           
        } else {
            // Now loop thru the menu items and set up the menu listener with each one
            Iterator<Gem> rootIterator = rootList.iterator();
           
            for (final JComponent component : menuItems) {
                // There may be some JSeperators in the list of menu items so watch out!

                if (component instanceof JMenuItem) {
                   
                    Gem gem = rootIterator.next();

                    if (!gem.isRunnable()) {
                        component.setEnabled(false);
                    }

                    DisplayedGem dGem = getTableTop().getDisplayedGem(gem);
                    menuListener.addMenuItem((JMenuItem)component, dGem);
                    ((JMenuItem)component).addActionListener(menuListener);
                    ((JMenuItem)component).addChangeListener(menuListener);
                }
               
                menu.add(component);
            }
        }
       
        return menuListener;
    }

    /**
     * Returns a list of menu items (and separators) for the Gem names.  Collectors will be first,
     * FunctionalAgents and CodeGems will be second, and Values will be third.  Each section will
     * be in alphabetical order and separated by a JSeparator.  Names that occur more than once
     * will have an ordinal appended after the name.
     * CAUTION: A JSeparator will be used to separate the different sections of the list.
     * @param gemList the list of gems that need menu items. 
     *   This list will will be sorted to match the order of the menu items.
     * @return List list of menu items (and separators) for the supplied gems
     */
    private List<JComponent> getGemMenuItems(List<Gem> gemList) {
       
        int numGems = gemList.size();
        ArrayList<JComponent> menuItems = new ArrayList<JComponent>();
           
        // if there are no gems then return a menu item labeled "none"
        if (numGems == 0) {
            JMenuItem emptyItem = new JMenuItem(getResourceString("EmptyListLabel"));
            emptyItem.setEnabled(false);
            menuItems.add(emptyItem);
            return menuItems;
        }
       
        // split the gemList into 3 lists - collectorGems, other named gems, valueGems
        List<Gem> collectorGems = new ArrayList<Gem>();
        List<Gem> otherNamedGems = new ArrayList<Gem>();
        List<Gem> valueGems = new ArrayList<Gem>();
        for (final Gem gem : gemList) {
            if (gem instanceof CollectorGem) {
                collectorGems.add(gem);
            } else if (gem instanceof FunctionalAgentGem || gem instanceof CodeGem || gem instanceof ReflectorGem || gem instanceof RecordFieldSelectionGem || gem instanceof RecordCreationGem) {
                otherNamedGems.add(gem);
            } else if (gem instanceof ValueGem) {
                valueGems.add(gem);
            } else {
                throw new IllegalArgumentException("TableTop.getGemMenuItems: gem not a Collector, FunctionalAgent, Emitter, Code, or Value");
            }
        }
       
        // clear the gemList and rebuild it with gems in the same order as the menu items
        gemList.clear();
       
        // sort collectors and add menu items for them
        if (!collectorGems.isEmpty()) {
            collectorGems = GemGraph.sortNamedGemsInAlphabeticalOrder(collectorGems);
           
            CollectorGem targetCollector = tableTop.getTargetCollector();
           
            for (final Gem gem : collectorGems) {
                CollectorGem collectorGem = (CollectorGem)gem;
                JMenuItem menuItem = new JMenuItem((collectorGem).getUnqualifiedName());
               
                if (collectorGem == tableTop.getTargetCollector()) {
                    // Set an icon.
                    menuItem.setIcon(new ImageIcon(getClass().getResource("/Resources/targetCollector.gif")));
                   
                    // add to the beginning.
                    menuItems.add(0, UIUtilities.fixMenuItem(menuItem));

                } else {
                    menuItems.add(UIUtilities.fixMenuItem(menuItem));
                }
            }
           
            // add the collectorGems to the empty gemList.  Recall that the target collector should come first.
            gemList.addAll(collectorGems);
            if (gemList.remove(targetCollector)) {
                gemList.add(0, targetCollector);
            }
           
        }
       
        // sort the functional agent and code gems and add menu items for them
        int numOtherNamedGems = otherNamedGems.size();
        if (numOtherNamedGems > 0) {
            // if there are any items in gemList then put a separator in the pop up menu
            if (gemList.size() > 0) {
                menuItems.add(new JSeparator());
            }
           
            // sort the other named gems into alphabetical order
            otherNamedGems = GemGraph.sortNamedGemsInAlphabeticalOrder(otherNamedGems);
           
            // set up some variables for looping thru the named gems
            String currName = ((NamedGem)otherNamedGems.get(0)).getUnqualifiedName();
            String nextName, addName;
            int ordinal = 0;
            for (int i = 0; i < numOtherNamedGems; i++){
   
                // the last gem name must be compared against a nextName of NULL so take
                // care of this here
                if (i < (numOtherNamedGems - 1)) {
                    nextName = ((NamedGem)otherNamedGems.get(i+1)).getUnqualifiedName();
                } else {
                    nextName = null;
                }
       
                if (currName.equals(nextName)) {
                    // current and next names are the same so increment ordinal and set addName
                    ordinal++;
                    addName = currName + " (" + ordinal + ")";
                } else {
                    // current and next names are different so set addName and clear ordinal
                    if (ordinal == 0) {
                        addName = currName;
                    } else {
                        ordinal++;
                        addName = currName + " (" + ordinal + ")";
                    }
           
                    ordinal = 0;
                }
           
                // set the currName equal to nextName and make a menu item for addName
                currName = nextName;
                menuItems.add(UIUtilities.fixMenuItem(new JMenuItem(addName)));
            }
   
            // add the named gems to gemList
            gemList.addAll(otherNamedGems);
        }
       
        // make menu items for the value gems
        if (!valueGems.isEmpty()) {
            // if there are any items in gemList then put a separator in the pop up menu
            if (gemList.size() > 0) {
                menuItems.add(new JSeparator());
            }
           
            int numValues = valueGems.size();
            for (int i = 1; i <= numValues; i++) {
                menuItems.add(UIUtilities.fixMenuItem(new JMenuItem(getResourceString("ValueLabel") + " (" + i + ")")));
            }
           
            // add the valueGems to gemList
            gemList.addAll(valueGems);
        }
       
        // return the menu items
        return menuItems;
    }
   
    /**
     * todoSN - hopefully this is only temporary so that the Run menu item is shown
     *          in edit mode and the Resume menu item is shown in run mode.
     *
     * Returns the Resume menu item.
     * @return JMenuItem
     */
    private JMenuItem getResumeMenuItem() {
       
        if (resumeMenuItem == null) {
            resumeMenuItem = makeNewMenuItem(getResumeRunAction());
        }
        return resumeMenuItem;
    }
   
    /**
     * todoSN - hopefully this is only temporary so that the Run menu item is shown
     *          in edit mode and the Resume menu item is shown in run mode.
     *
     * Returns the Resume button.
     * @return JButton
     */
    private JButton getResumeRunButton() {
       
        if (resumeRunButton == null) {
            resumeRunButton = makeNewButton(getResumeRunAction());
        }
        return resumeRunButton;
    }
   
    /**
     * Returns the action that resumes running after values have been entered for arguments in run mode.
     * @return Action
     */
    Action getResumeRunAction() {
       
        if (resumeRunAction == null) {
            try {
                resumeRunAction = new AbstractAction(getResourceString("ResumeRunGem"),
                                                new ImageIcon(getClass().getResource("/Resources/play.gif"))) {
                                                           
                    private static final long serialVersionUID = -4069447251180282590L;

                    public void actionPerformed(ActionEvent evt) {
                        // Disable the reset action after resume
                        getResetAction().setEnabled(false);
                        progressBar.setIndeterminate(true);
                        getStatusBarPane().add(progressBar, "East");
                        getStatusBarPane().revalidate();
                        runTarget(null);                       
                        getStatusMessageDisplayer().setMessageFromResource(this, "SM_Executing", StatusMessageDisplayer.MessageType.DEFERENTIAL);
                    }
                };
               
                resumeRunAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_RESUME));
                resumeRunAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_RESUME);
                resumeRunAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ResumeRunGemToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return resumeRunAction;
    }

    /**
     * Return the action for the Stop button and menu item
     * @return Action
     */
    Action getStopAction() {
       
        if (stopAction == null) {
            try {
                stopAction = new AbstractAction(getResourceString("StopRunGem"),
                                                new ImageIcon(getClass().getResource("/Resources/stop.gif"))) {
                                                           
                    private static final long serialVersionUID = -5572265564782672623L;

                    public void actionPerformed(ActionEvent evt) {
                        stopExecution();
                    }
                };
               
                stopAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_STOP));
                stopAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_STOP);
                stopAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("StopRunGemToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return stopAction;
    }

    /**
     * Terminate an execution.
     */
    private void stopExecution() {
        // Notify the execute lock to perform a quit
        displayedGemRunner.stopExecution();
    }

    /**
     * Return the action for reseting the argument values when in run mode.
     * @return Action
     */
    Action getResetAction() {
       
        if (resetAction == null) {
            try {
                resetAction = new AbstractAction (getResourceString("ResetArgs"),
                                                new ImageIcon(getClass().getResource("/Resources/reset.gif"))) {
                                                           
                    private static final long serialVersionUID = 8274405610098956489L;

                    public void actionPerformed(ActionEvent evt) {
                        displayedGemRunner.resetArgumentValues();
                        argumentExplorer.resetArgumentValues();
                    }
                };
               
                resetAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_RESET));
                resetAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_RESET);
                resetAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("ResetArgsToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
            resetAction.setEnabled(false);
        }
        return resetAction;
    }

    /**
     * Return the HelpMenu property value.
     * @return JMenu
     */
    private JMenu getHelpMenu() {
        if (helpMenu == null) {
            try {
                helpMenu = new JMenu();
                helpMenu.setName("HelpMenu");
                helpMenu.setText(getResourceString("HelpMenu"));
                helpMenu.setMargin(new Insets(2, 0, 2, 0));
                helpMenu.setMnemonic(GemCutterActionKeys.MNEMONIC_HELP_MENU);
               
                helpMenu.add(makeNewMenuItem(getHelpTopicsAction()));
                helpMenu.add(makeNewMenuItem(getAboutBoxAction()));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return helpMenu;
    }
   
    /**
     * Return the action that handles the Help Topics.
     * @return Action
     */
    private Action getHelpTopicsAction() {
       
        if (helpTopicsAction == null) {
            try {
                helpTopicsAction = new AbstractAction (getResourceString("HelpTopics")) {
                                                           
                    private static final long serialVersionUID = 2196764468259315329L;

                    public void actionPerformed(ActionEvent evt) {
                       
                        // Pass the event off to the help system launcher
                        getHelpSystemLauncher().actionPerformed(evt);
                    }
                };
               
                helpTopicsAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_HELP_TOPICS));
                helpTopicsAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("HelpTopicsToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return helpTopicsAction;
    }
   
    /**
     * Get the action listener that launches the online help system.
     * @return ActionListener
     */
    private ActionListener getHelpSystemLauncher() {
       
        if (helpSystemLauncher == null) {
            try {
                // Load the help set and get a help broker for it.
                java.net.URL helpSetURL = GemCutter.class.getResource("/JavaHelp/gemcutterhelp.hs");
                javax.help.HelpSet helpSet = new javax.help.HelpSet(null, helpSetURL);
                javax.help.HelpBroker helpBroker = helpSet.createHelpBroker();

                helpSystemLauncher = new javax.help.CSH.DisplayHelpFromSource(helpBroker);
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
       
        return helpSystemLauncher;
    }
   
    /**
     * Return the action that opens the "About" dialog.
     * @return Action
     */
    private Action getAboutBoxAction() {
       
        if (aboutBoxAction == null) {
            try {
                aboutBoxAction = new AbstractAction (getResourceString("AboutBox")) {
                                                           
                    private static final long serialVersionUID = -6456685392775005139L;

                    public void actionPerformed(ActionEvent evt) {
                        showAboutBox();
                    }
                };
               
                aboutBoxAction.putValue(Action.MNEMONIC_KEY, new Integer(GemCutterActionKeys.MNEMONIC_ABOUT_BOX));
                aboutBoxAction.putValue(Action.SHORT_DESCRIPTION, getResourceString("AboutBoxToolTip"));
               
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return aboutBoxAction;
    }
   
    public void showAboutBox() {
        /* Create the AboutBox dialog */
        GemCutterAboutBox aGemCutterAboutBox = new GemCutterAboutBox(this, true);
        Dimension dialogSize = aGemCutterAboutBox.getPreferredSize();
        Dimension frameSize = getSize();
        Point loc = getLocation();
        aGemCutterAboutBox.setLocation((frameSize.width - dialogSize.width) / 2 + loc.x, (frameSize.height - dialogSize.height) / 2 + loc.y);
        aGemCutterAboutBox.setVisible(true);
    }

    /**
     * Creates a new JButton and configures it for use in the GemCutter tool bar.
     * @param action Action - the action used to configure the JButton
     * @return JButton
     */
    private JButton makeNewButton(Action action) {
       
        JButton newButton = new JButton();
       
        // Specify some of the customized characteristics here.
        newButton.setAction(action);
        newButton.setText(null);
        newButton.setMargin(new Insets(0, 0, 0, 0));
       
        // Clear the mnemonic so that the buttons are activated by them.
        newButton.setMnemonic(KeyEvent.KEY_LOCATION_UNKNOWN);
       
        /* This takes care of making the drop down buttons look
         * like one button by raising the border of both of them at
         * the same time.
         */
        newButton.addMouseListener(new ToolBarButtonMouseListener (newButton));
       
        return newButton;
    }

    /**
     * Creates a new JCheckBoxMenuItem and configures it for use in the GemCutter menu bar.
     * @param action Action - the action used to configure the JCheckBoxMenuItem
     * @return JCheckBoxMenuItem
     */
    static private JCheckBoxMenuItem makeNewCheckBoxMenuItem(Action action) {
        return (JCheckBoxMenuItem)UIUtilities.fixMenuItem(new JCheckBoxMenuItem(action));
    }
   
    /**
     * Creates a new JMenuItem and configures it for use in the GemCutter menu bar.
     * @param action Action - the action used to configure the JMenuItem
     * @return JMenuItem
     */
    static JMenuItem makeNewMenuItem(Action action) {
        return UIUtilities.fixMenuItem(new JMenuItem(action));
    }
   
    /**
     * This method should only be called from the AWT thread.
     * @return true if a search is currently being performed, or false otherwise.
     */
    boolean isSearchPending() {
        if(searchDialog == null) {
            return false;
        }
       
        return searchDialog.isSearchPending();
    }
   
    /**
     * If no search dialog is currently being displayed, then create one and pop it up
     * @return The SearchDialog object that is being displayed
     */
    SearchDialog showSearchDialog() {
       
        if(searchDialog == null) {
            searchDialog = new SearchDialog(GemCutter.this, perspective);
           
            if(searchDialog.hasSavedPosition()) {
                searchDialog.setPositionToSaved();
            } else {
                searchDialog.setLocation(getX(), getY());
            }
            searchDialog.addWindowListener(new WindowAdapter() {
                public void windowClosed(WindowEvent e) {
                    forgetSearchDialog();
                }
            });
           
            searchDialog.setVisible(true);
        }
       
        return searchDialog;
    }

    /**
     * Called by the searchDialog to signal that it has been closed.
     */
    private void forgetSearchDialog() {
        if (searchDialog != null) {
            searchDialog.savePosition();
            searchDialog = null;
        }
    }
   
    /**
     * Creates a new JMenu and configures it for use in the GemCutter menu bar.
     * @param text the label of the menu item
     * @return JMenu
     */
    static JMenu makeNewMenu(String text) {
        return (JMenu)UIUtilities.fixMenuItem(new JMenu(text));
    }

    /**
     * Given the name of a resource, return the associated string.
     * @param resourceName String the name of the resource
     * @return the associated string.
     */
    public static String getResourceString(String resourceName) {
        return GemCutterMessages.getString(resourceName);
    }
   
    /**
     * Given the name of a resource, return the associated string.
     * @param resourceName String the name of the resource
     * @param resourceArgument the argument to the resource string.
     * @return the associated string.
     */
    public static String getResourceString(String resourceName, Object resourceArgument) {
        return GemCutterMessages.getString(resourceName, resourceArgument);
    }

    /**
     * Gets the appropriate cursor given the gem
     * @param gemToAdd
     * @return Cursor
     */
    static Cursor getCursorForAddGem(Gem gemToAdd) {
        if (gemToAdd instanceof ValueGem) {
            return addValueGemCursor;
           
        } else if (gemToAdd instanceof CodeGem) {
            return addCodeGemCursor;
   
        } else if (gemToAdd instanceof CollectorGem) {
            return addCollectorGemCursor;
   
        } else if (gemToAdd instanceof ReflectorGem) {
            if (gemToAdd.getNInputs() == 0) {
                return addOvalReflectorGemCursor;
            } else {
                return addTriangularReflectorGemCursor;
            }
       
        } else if (gemToAdd instanceof RecordFieldSelectionGem) {
            return addRecordFieldSelectionGemCursor;   
       
        } else if (gemToAdd instanceof RecordCreationGem) {
            return addRecordCreationGemCursor;   
           
        } else if (gemToAdd instanceof FunctionalAgentGem) {
            return addFunctionGemCursor;

        } else {
            // The default cursor (?)
            return addFunctionGemCursor;
        }
    }

    /**
     * Returns a reference to the clipboard used to store the gems that
     * are cut/copied on the tabletop
     * @return clipBoard
     */
    Clipboard getClipboard() {
        if (clipboard == null) {
            clipboard = new Clipboard("GemCutter");
        }
        return clipboard;
    }
   
    /**
     * Return the OverviewPanel property value.
     * @return OverviewPanel
     */
    private TableTopExplorerAdapter getTableTopExplorerAdapter() {
        if (tableTopExplorerAdapter == null) {
            try {
                tableTopExplorerAdapter = new TableTopExplorerAdapter(this);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return tableTopExplorerAdapter;
    }
   
    /**
     * Return the OverviewPanel property value.
     * @return OverviewPanel
     */
    TableTopExplorer getTableTopExplorer() {
        return getTableTopExplorerAdapter().getTableTopExplorer();
    }

    /**
     * @return the intellicut manager for the gem cutter.
     */
    IntellicutManager getIntellicutManager() {
        return intellicutManager;
    }

    /**
     * Returns whether or not the output should be in debug mode (string output) or
     * use the value entry mechanism.
     * @return boolean
     */
    public boolean isDebugOutputMode() {
        return ((Boolean)getDebugOutputAction().getValue("InDebugOutputMode")).booleanValue();
    }
   
    /**
     * Returns whether or not we should allow renaming of entities in the Prelude module.
     * @return boolean
     */
    public boolean isAllowPreludeRenamingMode() {
        return ((Boolean)getAllowPreludeRenamingAction().getValue("InAllowRenamingMode")).booleanValue();
    }

    /**
     * Returns whether or not we should allow renaming of entities to names that already exist
     * @return boolean
     */
    public boolean isAllowDuplicateRenamingMode() {
        return ((Boolean)getAllowDuplicateRenamingAction().getValue("InAllowDuplicateRenamingMode")).booleanValue();
    }

    /**
     * Handle necessary effects of GUI state changes.
     * @param newGUIstate the new GUI state to change to
     */
    public void enterGUIState(GUIState newGUIstate) {

        if (guiState == newGUIstate) {
            // Nothing to do
            return;
        }
   
        // Firstly, handle things we always do when leaving the old state.
       
        if (guiState == GUIState.ADD_GEM) {
               
            // Not adding a gem anymore, so enable the edit controls.
               
            // Reset the cursor and the status message.
            setCursor(null);
            getGlassPane().setCursor(null);
            setStatusFlag(null);
            statusMessageManager.clearMessage(this);

            // Enable drag and drop from the browser and hide the glass pane.
            getBrowserTree().setEnabledDragAndDrop(true);
            getGlassPane().setVisible(false);
           
            // Loop through any open code editors and reset their cursors and hide their glass panes.
            for (final DisplayedGem dGem : getTableTop().getDisplayedGems()) {
                Gem gem = dGem.getGem();
               
                if (gem instanceof CodeGem && getTableTop().isCodeEditorVisible((CodeGem)gem)) {
                    CodeGemEditor editor = getTableTop().getCodeGemEditor((CodeGem)gem);
                    editor.setCursor(null);
                    editor.getGlassPane().setVisible(false);
                }
            }      
           
   
        } else if (guiState == GUIState.RUN) {
           
            // Not running anymore, so re-enable edit controls.
            enableEditActions(true);

            getTableTop().checkSelectionButtons();

            // Update run controls.
            setTargetRunningButtons(false);
            getResetAction().setEnabled(false);
            getTableTop().setRunning(false);

            // Indicate that we're not running anymore.
            setStatusFlag(null);
            statusBarPane.remove(progressBar);
            statusMessageManager.clearMessage(getDisplayedGemRunner());
            statusMessageManager.clearMessage(this);
           
        } else if (guiState == GUIState.LOCKED_DOWN) {

            // Not locked down anymore, so re-enable all controls.
            enableEditActions(true);
            getRunAction().setEnabled(true);           

            getTableTop().checkSelectionButtons();
        }
   
        // Now handle things we always do when entering a specific state.
       
        if (newGUIstate == GUIState.ADD_GEM) {
           
            // Adding a new gem. Determine the correct cursor to use and disable edit controls.
            Gem gemToAdd = displayedGemToAdd == null ? null : displayedGemToAdd.getGem();
       
            if (gemToAdd instanceof ValueGem) {
                statusMessageManager.setMessageFromResource(this, "SM_AddValueGem", StatusMessageDisplayer.MessageType.PERSISTENT);

            } else if (gemToAdd instanceof CodeGem) {
                statusMessageManager.setMessageFromResource(this, "SM_AddCodeGem", StatusMessageDisplayer.MessageType.PERSISTENT);
               
            } else if (gemToAdd instanceof CollectorGem) {
                statusMessageManager.setMessageFromResource(this, "SM_AddCollectorGem", StatusMessageDisplayer.MessageType.PERSISTENT);
               
            } else if (gemToAdd instanceof ReflectorGem) {
                String name = ((ReflectorGem)gemToAdd).getUnqualifiedName();
                statusMessageManager.setMessageFromResource(this, "SM_AddReflectorGem", name + ".", StatusMessageDisplayer.MessageType.PERSISTENT);
            } else {
                statusMessageManager.setMessageFromResource(this, "SM_AddFunctionGem", StatusMessageDisplayer.MessageType.PERSISTENT);
            }
           
            Cursor cursor = getCursorForAddGem(gemToAdd);
            setCursor(cursor);
            getGlassPane().setCursor(cursor);

            // Disable drag and drop from the gem browser and turn on the glass pane so
            // that we can cancel "add gem" mode whenever the user clicks outside the TableTop.
            getBrowserTree().setEnabledDragAndDrop(false);
            getGlassPane().setVisible(true);

            // Loop through any open code editors and make their glass panes visible.
            // This is so the 'add gem' cursor works everywhere.
            for (final DisplayedGem dGem : getTableTop().getDisplayedGems()) {
                Gem gem = dGem.getGem();
               
                if (gem instanceof CodeGem && getTableTop().isCodeEditorVisible((CodeGem)gem)) {
                    CodeGemEditor editor = getTableTop().getCodeGemEditor((CodeGem)dGem.getGem());
                    editor.setCursor(cursor);
                    editor.getGlassPane().setVisible(true);
                }
            }               

            // Let the user know we are adding a gem.
            setStatusFlag(getResourceString("AddGem"));

        } else if (newGUIstate == GUIState.RUN) {

            // Disable the editing controls and enable the run controls.
            enableEditActions(false);
            getTableTop().setRunning(true);

            setStatusFlag(getResourceString("RunningFlag"));

        } else if (newGUIstate == GUIState.LOCKED_DOWN) {
           
            // Disable all controls if we are locked down.
            enableEditActions(false);
            getRunAction().setEnabled(false);           
        }
   
        // Set the new state
        guiState = newGUIstate;
    }

    /**
     * Enable or disable GemCutter edit actions.
     * @param enable true to enable, false to disable.
     */
    private void enableEditActions(boolean enable) {
        getNewAction().setEnabled(enable);
        getOpenAction().setEnabled(enable);
        getSaveGemAction().setEnabled(enable);
        getSelectAllAction().setEnabled(enable);
        getArrangeGraphAction().setEnabled(enable);                                              
        getFitTableTopAction().setEnabled(enable);
        getAddGemAction().setEnabled(enable);
        getAddValueGemAction().setEnabled(enable);
        getAddCodeGemAction().setEnabled(enable);
        getAddRecordCreationGemAction().setEnabled(enable);
        getAddRecordFieldSelectionGemAction().setEnabled(enable);
        getAddCollectorGemAction().setEnabled(enable);
        getAddReflectorGemAction().setEnabled(enable);
        getAddReflectorGemDropDownAction().setEnabled(enable);
        getRemoveModuleAction().setEnabled(enable);
        getSwitchWorkspaceAction().setEnabled(enable);
        getDeployWorkspaceToEnterpriseAction().setEnabled(enable);
        getRecompileAction().setEnabled(enable);
        getCompileModifiedAction().setEnabled(enable);
        getDeleteAction().setEnabled(enable);

        if (enable) {
            if (extendedUndoManager.canUndo()) {
                getUndoAction().setEnabled(true);
                getUndoDropDownAction().setEnabled(true);
            }
           
            if (extendedUndoManager.canRedo()) {
                getRedoAction().setEnabled(true);
                getRedoDropDownAction().setEnabled(true);
            }

        } else {
            getUndoAction().setEnabled(false);
            getUndoDropDownAction().setEnabled(false);
            getRedoAction().setEnabled(false);
            getRedoDropDownAction().setEnabled(false);
        }
       
        if (enable) {
            getPasteAction().setEnabled(getTableTop().canPasteToGemGraph(getClipboard().getContents(this)));
            getTableTop().checkSelectionButtons();
            getCopySpecialMenu().setEnabled(true);
            getCopySpecialImageAction().setEnabled(true);
            getCopySpecialTargetSourceAction().setEnabled(true);
           
        } else {
            getCutAction().setEnabled(false);
            getCopyAction().setEnabled(false);
            getPasteAction().setEnabled(false);
            getCopySpecialMenu().setEnabled(false);
            getCopySpecialImageAction().setEnabled(false);
            getCopySpecialTextAction().setEnabled(false);
            getCopySpecialTargetSourceAction().setEnabled(false);
        }
       
        Component[] menuItems = generateMenu.getMenuComponents();
        for (final Component menuItem : menuItems) {
            menuItem.setEnabled(enable);
        }

        getBrowserTree().setEnabledDragAndDrop(enable);
        getTableTopExplorer().enableMouseInputs(enable);
        getArgumentExplorer().enableMouseInputs(enable);
        getSearchAction().setEnabled(enable);
    }
   
    /**
     * Get the current GUI State.
     * @return GUIState the current state
     */
    public GUIState getGUIState() {
        return guiState;
    }

    /**
     * true if we are using photolook, false otherwise
     * @return boolean
     */
    boolean isPhotoLook() {
        return (backgroundImage != null);
    }
   
    /**
     * Resets the current background to what is stored in the preferences value.
     */
    void resetBackground() {
       
        String fileName = getPreferences().get(BACKGROUND_FILE_NAME_PREF_KEY, backgrounds.get(0).snd());
       
        try {
            URL url = GemCutter.class.getResource(fileName);
            if (url != null) {
                backgroundImage = ImageIO.read(url);
            } else {
                backgroundImage = null;
            }
        } catch (IOException e) {
            backgroundImage = null;
        }

        getTableTopPanel().setBackground(backgroundImage);
        getTableTopExplorer().getExplorerTree().setBackgroundImage(backgroundImage);
        getOverviewPanel().repaint();
    }
   
    /**
     * Returns the displayed Gem (Value, Code, etc.) that is being added.
     * @return DisplayedGem the (displayed) gem being added
     */
    DisplayedGem getAddingDisplayedGem() {
        return displayedGemToAdd;   
    }

    /**
     * Set the DisplayedGem (Value, Code, etc.) that is being added.
     * @param newAddingGem DisplayedGem
     */
    void setAddingDisplayedGem(DisplayedGem newAddingGem) {
        // update the adding gem
        displayedGemToAdd = newAddingGem;
    }

    /**
     * Get the GemCutter's instance of the CALCompiler.
     * @return CALCompiler the GemCutter's instance of the CALCompiler.
     */
    Compiler getCompiler() {
        return workspaceManager.getCompiler();
    }

    /**
     * Get the TypeChecker from the GemCutter's compiler.
     * @return TypeChecker the type checker
     */
    public TypeChecker getTypeChecker() {
        return workspaceManager.getTypeChecker();
    }

    /**
     * Get TypeCheckInfo for the GemCutter.
     * @return TypeCheckInfo the typecheck info for the current state of the tabletop
     */
    TypeCheckInfo getTypeCheckInfo() {
        return getTypeChecker().getTypeCheckInfo(getWorkingModuleName());
    }
   
    /**
     * Get the TypeColourManager in use in this GemCutter.
     * @return TypeColourManager the colour manager
     */
    public TypeColourManager getTypeColourManager() {
        return typeColours;
    }
   
    /**
     * @return the current workspace manager.
     */
    public WorkspaceManager getWorkspaceManager() {
        return workspaceManager;
    }

    /**
     * Get the current workspace.
     * @return the current workspace
     */
    public CALWorkspace getWorkspace() {
        return workspaceManager.getWorkspace();
    }

    /**
     * Get the current GemCutter perspective.
     * @return Perspective the current program.
     */
    public Perspective getPerspective() {
        return perspective;
    }
   
    /**
     * @return the code analyzer for code gems in the GemCutter.
     */
    CodeAnalyser getCodeGemAnalyser() {
        return new CodeAnalyser(getTypeChecker(), getPerspective().getWorkingModuleTypeInfo(), true, false);
    }
   
    /**
     * @return the GemCutter's connection context
     */
    public ConnectionContext getConnectionContext() {
        return new ConnectionContext(getPerspective().getWorkingModuleTypeInfo(), getValueEditorManager());
    }
   
    /**
     * Get the name of the current working module.
     * @return the name of the current working module, or null if there is no current working module.
     */
    public ModuleName getWorkingModuleName() {
        if (perspective == null) {
            return null;
        }
        return perspective.getWorkingModuleName();
    }
    /**
     * Get the name of the initial working module.
     * @param workspace the workspace from which the module should be obtained.
     * @return the name of the initial working module or null if there are no valid modules
     */
    private ModuleName getInitialWorkingModuleName(CALWorkspace workspace) {

        // First check whether a system property has been set for this.
        ModuleName workingModuleName = ModuleName.maybeMake(System.getProperty(GEMCUTTER_PROP_MODULE));
        if (workingModuleName == null || workspace.getMetaModule(workingModuleName) == null) {
            workingModuleName = null;
           
            // For now, return the name of the last metamodule in the program that starts with "GemCutter".
            int nModules = workspace.getNMetaModules();
            for (int i = nModules - 1; i > -1; i--) {
                ModuleName moduleName = workspace.getNthMetaModule(i).getName();
                if (moduleName.toSourceText().startsWith("GemCutter")) {
                    workingModuleName = moduleName;
                    break;
                }
            }
        }
       
        if (workingModuleName == null) {
            int nModules = workspace.getNMetaModules();
           
            // Pick the module with the most imports.
            // In the event of a tie, pick the later one, as it will tend to depend on earlier ones.
            int maxImportedModules = 0;
            for (int i = 0; i < nModules; i++) {
                MetaModule nthMetaModule = workspace.getNthMetaModule(i);
                int nImportedModules = nthMetaModule.getNImportedModules();
               
                if (nImportedModules >= maxImportedModules) {
                    maxImportedModules = nImportedModules;
                    workingModuleName = nthMetaModule.getName();
                }
            }
        }
       
        return workingModuleName;           
    }
   
    /**
     * Set the preferred working module, assuming that the GemCutter has just started up, but the current module has already been set.
     */
    private void setInitialPreferredWorkingModuleName() {
        ModuleName moduleProp = ModuleName.maybeMake(System.getProperty(GEMCUTTER_PROP_MODULE));
        if (moduleProp != null) {
            preferredWorkingModuleName = moduleProp;
        } else {
            preferredWorkingModuleName = getWorkingModuleName();
        }
    }

    /**
     * Get the CALRunner for displayed gems used by this instance of the GemCutter.
     * @return DisplayedGemRunner the displayed gem runner
     */
    DisplayedGemRunner getDisplayedGemRunner() {
        return displayedGemRunner;
    }

    /**
     * Returns the ValueRunner used for instantiating value nodes..
     * @return ValueRunner
     */
    ValueRunner getValueRunner() {
        return valueRunner;
    }
   
    /**
     * Returns the ValueEditorManager
     * @return ValueEditorManager
     */
    ValueEditorManager getValueEditorManager() {
        return valueEditorManager;
    }
   
    /**
     * Returns the ValueEditorHierarchyManager responsible for maintaining the main ValueEditorHiearchy (notably that of the TableTop
     * @return ValueEditorHierarchyManager
     */
    ValueEditorHierarchyManager getValueEditorHierarchyManager() {
        return tableTopEditorHierarchyManager;
    }
   
    NavigatorAdapter getNavigatorOwner() {
        return navigatorOwner;
    }
   
    static Preferences getPreferences() {
        return Preferences.userNodeForPackage(GemCutter.class);
    }

    /**
     * Enable/Disable the target running buttons. Note that the buttons are in alphabetic order.
     * @param running whether the buttons should be setup for the Run state
     */
    void setTargetRunningButtons (boolean running) {
   
        getStopAction().setEnabled(running);
       
        // Hopefully this is temporary - if stop is disabled (its false) we want the Run menu item and button
        // displayed rather than the resume menu item and button
        if (running) {
           
            JPopupMenu runPop = getRunMenu().getPopupMenu();
            int index = runPop.getComponentIndex(getRunMenuItem());
            if (index >= 0) {
                runPop.remove(index);
                runPop.add(getResumeMenuItem(), index);
            }
           
            index = runPop.getComponentIndex(getRunSubMenu());
            if (index >= 0) {
                runPop.remove(index);
            }
           
            JToolBar toolbar = getToolBarPane();
            index = toolbar.getComponentIndex(getRunButton());
            if (index >= 0) {
                toolbar.remove(index);
                toolbar.add(getResumeRunButton(), index);
               
                // The toolbar doesn't properly set the button border if you add/remove
                // a button at this point. Therefore we manually set the correct border
                // from a toolbar button that is already in the toolbar.
                getResumeRunButton().setBorder(getAddReflectorGemButton().getBorder());
            }
           
            index = toolbar.getComponentIndex(getRunDropDownButton());
            if (index >= 0) {
                toolbar.remove (index);
            }
           
        } else {

            JPopupMenu runPop = getRunMenu().getPopupMenu();
            int index = runPop.getComponentIndex(getResumeMenuItem());
            if (index >= 0) {
                runPop.remove(index);
                runPop.add(getRunMenuItem(), index);
                runPop.add(getRunSubMenu(), index + 1);
            }
           
            JToolBar toolbar = getToolBarPane();
            index = toolbar.getComponentIndex(getResumeRunButton());
            if (index >= 0) {
                toolbar.remove(index);
                toolbar.add(getRunButton(), index);
                toolbar.add(getRunDropDownButton(), index + 1);
               
                // See above.
                getRunButton().setBorder(getAddReflectorGemButton().getBorder());
                getRunDropDownButton().setBorder(getAddReflectorGemButton().getBorder());
            }
        }
    }
   
    /**
     * Execute the supercombinator defined by the Target.
     * @param targetDisplayedGem the target to be run.
     */
    void runTarget(DisplayedGem targetDisplayedGem) {
       
        DisplayedGem displayedGem = getTableTop().getDisplayedGem(currentGemToRun);
       
        // Update the currentGemToRun accordingly
        if (targetDisplayedGem != null && displayedGem != targetDisplayedGem) {
            currentGemToRunListener.setGem(targetDisplayedGem.getGem());
        }

        displayedGemRunner.runTargetDisplayedGem(targetDisplayedGem);
    }

    /**
     * @return Returns the targetRunnableListener.
     */
    public TargetRunnableListener getTargetRunnableListener() {
        return targetRunnableListener;
    }
   
    /**
     * Sets the visibility of the Play button popup menu and the Run menu item
     * to false.
     */
    void closeRunPopupMenus() {
       
        getRunSubMenu().getPopupMenu().setVisible(false);
       
        if (runDropDownMenu != null) {
            runDropDownMenu.setVisible(false);
        }
    }
   
    /**
     * Return the status message manager for the GemCutter
     * @return StatusMessageManager the status message manager for the GemCutter
     */
    StatusMessageDisplayer getStatusMessageDisplayer() {
        return statusMessageManager;
    }

    /**
     * Set the status flag (message) on the status bar.
     * @return the old flag
     * @param flag String the new flag (Null clears the message)
     */
    String setStatusFlag(String flag) {
        // 'Swap' labels
        JLabel statusLabel = getStatusMsg1();
        String oldFlag = statusLabel.getText();
   
        // A null flag is short for 'ready', otherwise just set the given text
        if (flag == null) {
            flag = getResourceString("ReadyFlag");
        }  
       
        statusLabel.setText(flag);
        return oldFlag;
    }

    /**
     * Toggle whether the Status bar is present.
     */
    public void viewStatusBar() {
        getStatusBarPane().setVisible(!(getStatusBarPane().isVisible()));
    }

    /**
     * Display the controls for argument entry.
     * This included a Navigation Tool Bar containing JButtons for arguments, and the value panels in the arguments pane.
     * @param inputToEditorMap map from input to editor, for those inputs for which the currently
     *  running gem requires arguments.  An iterator on this map should return the inputs in the correct order.
     */
    void showArgumentControls(Map<PartInput, ValueEditor> inputToEditorMap) {

        // Add a navigation toolbar.
       
        // get the main ToolBar and figure out how tall the nav buttons need to be
        JToolBar toolBar = getToolBarPane();
        Insets toolBorder = toolBar.getInsets();
        Insets navBorder = navigationToolBar.getInsets();
        Border buttonBorder = getAddReflectorGemButton().getBorder();
        int buttonHeight = toolBar.getHeight() - toolBorder.top - toolBorder.bottom - navBorder.top - navBorder.bottom;
   
        // Set up the navigation buttons.
        int argIndex = 0;
        for (final Map.Entry<PartInput, ValueEditor> mapEntry : inputToEditorMap.entrySet()) {
            Gem.PartInput inputPart = mapEntry.getKey();
            ValueEditor editor = mapEntry.getValue();
           
            // Create a navigation button, and hook it up to its action.
            JButton navigationButton = new JButton(String.valueOf(argIndex + 1));
            navigationButton.addActionListener(new ParameterNavigationActionListener(editor));
            navigationButton.setMargin(new Insets(0, 0, 0, 0));
   
            // Set its tooltip.
            DisplayedGem displayedGem = getTableTop().getDisplayedGem(inputPart.getGem());
            String tooltipMessage = GemCutterMessages.getString("GemArgumentNavToolTip", Integer.toString(inputPart.getInputNum() + 1), displayedGem.getDisplayText());
            navigationButton.setToolTipText(tooltipMessage);

            // Constrain the button's height.
            Dimension dim = navigationButton.getPreferredSize();
            navigationButton.setPreferredSize(new Dimension(dim.width, buttonHeight));
           
            // Add the button to the toolbar.
            navigationToolBar.add(navigationButton);
           
            // For some reason the JToolbar doesn't get the button borders right.
            // So here we steal the border from another button and set it.
            navigationButton.setBorder(buttonBorder);

            argIndex++;
        }
       
        // make the Reset button's height the same as the nav buttons and add it at the end of the nav bar
        JButton resetButton = makeNewButton(getResetAction());
        Dimension dim = resetButton.getPreferredSize();
        resetButton.setPreferredSize(new Dimension(dim.width, buttonHeight));
        navigationToolBar.add(resetButton);
        resetButton.setBorder(buttonBorder);
       
        // Add the toolbar.
        toolBar.add(navigationToolBar);
        toolBar.validate();
       
        // if it's the target that's running, put the argument explorer into run mode.
        if (getTableTop().isRunning(getTableTop().getTargetDisplayedCollector())) {
            getArgumentExplorer().showArgumentControls(inputToEditorMap);
           
            // If the argument explorer is available, show it so that argument entry is facilitated.
            if (getExplorerArgumentsPane().indexOfComponent(getArgumentExplorer()) >= 0) {
                explorerArgumentsPaneEditModeSelectedTab = getExplorerArgumentsPane().getSelectedComponent();
                getExplorerArgumentsPane().setSelectedComponent(getArgumentExplorer());
         
            } else {
                explorerArgumentsPaneEditModeSelectedTab = null;
            }
        } else {
            explorerArgumentsPaneEditModeSelectedTab = null;
        }
    }
   
    /**
     * Hide the controls for argument entry.
     */
    void hideArgumentControls() {
   
        getToolBarPane().remove(navigationToolBar);
        navigationToolBar.removeAll();
        getToolBarPane().repaint();
       
        // put the argument explorer into edit mode.
        getArgumentExplorer().hideArgumentControls();
       
        // Restore any previous tab selection state, if the previously-selected tab is still available.
        if (explorerArgumentsPaneEditModeSelectedTab != null) {
            int componentIndex = getExplorerArgumentsPane().indexOfComponent(explorerArgumentsPaneEditModeSelectedTab);
            if (componentIndex >= 0) {
                getExplorerArgumentsPane().setSelectedIndex(componentIndex);
            }
        }
    }

    /**
     * Toggle whether the Navigation toolbar is present.
     */
    public void viewToolBar() {
        getToolBarPane().setVisible(!getToolBarPane().isVisible());
    }

    /**
     * Set up highlighting for undo and redo drop down menus. 
     * When a menu item is "armed" (under the mouse pointer) all items appearing before it also have a
     * selected appearance to indicate that the first n items will be undone.
     * @param popupMenu JPopupMenu the menu to which to listen for arming changes.
     */
    private void setupUndoRedoDropDownMenuHighlighting(final JPopupMenu popupMenu) {
        final int nMenuItems = popupMenu.getComponentCount();

        // Create an item change armer
        // a local class that (dis)arms all the items up to the changed item.
        final ChangeListener itemChangeArmer = new ChangeListener() {

            public void stateChanged(ChangeEvent e) {

                JMenuItem changedItem = (JMenuItem)e.getSource();
                boolean armed = changedItem.isArmed();

                // arm the menu items up to the item whose state changed
                for (int i = 0; i < nMenuItems; i++) {

                    JMenuItem someItem = (JMenuItem)popupMenu.getComponent(i);

                    if (someItem == changedItem) {
                        break;
                    }
                    someItem.setArmed(armed);
                }
            }
        };

        // Add listener to the menu items to be notified when they're armed or disarmed
        // Note that disarming always occurs before any arming, so here arming will always be
        // for items {0..n} for n >= 0.
        for (int i = 0; i < nMenuItems; i++) {
            JMenuItem menuItem = (JMenuItem)popupMenu.getComponent(i);
            menuItem.addChangeListener(itemChangeArmer);
        }
       
        // Add a popup menu listener that will strip off the item change armer as a listener
        // when the popup menu disappears
        popupMenu.addPopupMenuListener(new PopupMenuListener() {
            public void popupMenuCanceled(PopupMenuEvent e){}
            public void popupMenuWillBecomeVisible(PopupMenuEvent e){}

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e){
                for (int i = 0; i < nMenuItems; i++) {
                    JMenuItem menuItem = (JMenuItem)popupMenu.getComponent(i);
                    menuItem.removeChangeListener(itemChangeArmer);
                }
            }
        });
    }
   
    /**
     * Displays the pop up menu listing all redo edits available when the redo drop down button is pressed.
     * Edits are displayed in reverse chronological order.  When an edit is selected, all edits to that point
     * are redone.
     */
    public void displayRedoDropDownMenu() {

        UndoableEdit[] redoableEdits = extendedUndoManager.getRedoableEdits();

        // a listener for selection for popup menu items
        class RedoPopupSelectionListener implements ActionListener {

            private final int numRedos; // the number of redos to redo

            RedoPopupSelectionListener(int numRedos){
                this.numRedos = numRedos;
            }

            public void actionPerformed(ActionEvent e){
                // redo (numRedos) redos
                for (int i = 0; i < numRedos; i++) {
                    redo();
                }
            }
        }

        // figure out how many edits to display
        int numDisplayedEdits = Math.min(redoableEdits.length, MAX_DISPLAYED_UNDOS);

        // create a popup menu with redoable edits
        JPopupMenu popupMenu = new JPopupMenu();
        for (int i = 0; i < numDisplayedEdits; i++) {
            UndoableEdit edit = redoableEdits[i];
            JMenuItem menuItem = new JMenuItem(edit.getPresentationName());
            popupMenu.add(menuItem);
            menuItem.addActionListener(new RedoPopupSelectionListener(i + 1));
        }

        // The popup menu should highlight the items that will be undone
        setupUndoRedoDropDownMenuHighlighting(popupMenu);

        // show the popup directly underneath the redo button, left-aligned with it
        Rectangle bounds = getRedoButton().getBounds();
        popupMenu.show(getToolBarPane(), bounds.x, bounds.y + bounds.height);
    }

    /**
     * Displays the pop up menu listing all undo edits available when the undo drop down button is pressed.
     * Edits are displayed in reverse chronological order.  When an edit is selected, all edits to that point
     * are undone.
     */
    public void displayUndoDropDownMenu() {

        UndoableEdit[] undoableEdits = extendedUndoManager.getUndoableEdits();

        // a listener for selection for popup menu items
        class UndoPopupSelectionListener implements ActionListener {

            private final int numUndos; // the number of undos to undo

            UndoPopupSelectionListener(int numUndos){
                this.numUndos = numUndos;
            }

            public void actionPerformed(ActionEvent e){
                // undo (numUndos) undos
                for (int i = 0; i < numUndos; i++) {
                    undo();
                }
            }
        }

        // figure out how many edits to display
        int numDisplayedEdits = Math.min(undoableEdits.length, MAX_DISPLAYED_UNDOS);

        // create a popup menu with undoable edits
        JPopupMenu popupMenu = new JPopupMenu();

        for (int i = 0; i < numDisplayedEdits; i++) {

            UndoableEdit edit = undoableEdits[i];
            JMenuItem menuItem = new JMenuItem(edit.getPresentationName());
            popupMenu.add(menuItem);
            menuItem.addActionListener(new UndoPopupSelectionListener(i + 1));
        }

        // The popup menu should highlight the items that will be undone
        setupUndoRedoDropDownMenuHighlighting(popupMenu);

        // show the popup directly underneath the undo button, left-aligned with it
        Rectangle bounds = getUndoButton().getBounds();
        popupMenu.show(getToolBarPane(), bounds.x, bounds.y + bounds.height);
    }
   
    /**
     * Redo.
     */
    private void redo() {
       
        try {
            // attempt redo
            extendedUndoManager.redo();
            getTableTop().updateCodeGemEditors();
            getTableTop().updateForGemGraph();
        } catch(CannotRedoException e) {
            extendedUndoManager.discardAllEdits();
        } finally {
            // make sure the undo widgets are updated
            updateUndoWidgets();
        }
    }

    /**
     * Undo.
     */
    private void undo() {

        try {
            // attempt undo
            extendedUndoManager.undo();
            getTableTop().resizeForGems();
            getTableTop().updateCodeGemEditors();
            getTableTop().updateForGemGraph();

        } catch(CannotUndoException e) {
            extendedUndoManager.discardAllEdits();           
        } finally {
            // make sure the undo widgets are updated
            updateUndoWidgets();
        }
    }

    /**
     * Update the enabled/disabled state of the undo and redo buttons and menu items.
     */
    private void updateUndoWidgets() {

        boolean canUndo = extendedUndoManager.canUndo();
        boolean canRedo = extendedUndoManager.canRedo();
        String undoText = extendedUndoManager.getUndoPresentationName();
        String redoText = extendedUndoManager.getRedoPresentationName();

        getUndoAction().setEnabled(canUndo);
        getRedoAction().setEnabled(canRedo);

        getUndoDropDownButton().setEnabled(canUndo);
        getRedoDropDownButton().setEnabled(canRedo);

        getUndoMenuItem().setText(undoText);
        getRedoMenuItem().setText(redoText);

        getUndoAction().putValue(Action.SHORT_DESCRIPTION, undoText);
        getRedoAction().putValue(Action.SHORT_DESCRIPTION, redoText);

        // Update the state of the paste action.
        // TODOEL: the "updateUndoWidgets" method isn't the right place for this.
        getPasteAction().setEnabled(getTableTop().canPasteToGemGraph(getClipboard().getContents(this)));
       
        // Update the window title in case the dirty flag changed.
        updateWindowTitle();
    }
   
    /**
     * @return whether the GemCutter is considered to be in the "dirty" state.
     * This will cause the GemCutter to prompt the user to save if losing work might happen.
     */
    private boolean isDirty() {
        return extendedUndoManager.editToBeUndone() != editToUndoWhenNonDirty;
    }

    /**
     * Update the window title to show the name of the gem being currently edited.
     */
    private void updateWindowTitle() {
        String windowTitle = getResourceString("WindowTitle");
       
        // Check for the case where there isn't a working module
        // This can happen on startup, before compilation has occurred.
        ModuleName workingModuleName = getWorkingModuleName();
        if (workingModuleName != null) {
       
            windowTitle += " - " + QualifiedName.make(workingModuleName, getTableTop().getTargetCollector().getUnqualifiedName()).getQualifiedName();
            if (isDirty()) {
                windowTitle += "*";
            }
        }
        setTitle(windowTitle);
    }

    /**
     * Returns the current Collector which should be used for adding new reflectors.
     * @return CollectorGem
     */
    CollectorGem getCollectorForAddingReflector() {
        return currentCollectorForAddingReflector;
    }

    /**
     * @return whether the GemCutter is currently in a saveable state.
     */
    private boolean inSaveableState() {
        // First get all the collectors in order
        Set<CollectorGem> collectorSet = getTableTop().getGemGraph().getCollectors();
           
        // Check for collectors we can save.
        for (final CollectorGem cGem : collectorSet) {
            if (cGem.isRunnable()) {
                return true;
            }
        }

        // We can't save any collectors.
        return false;
    }
   
    /**
     * Save the given gem. This delegates to the persistence manager to do the actual saving
     * and simply takes care of the UI part. The user will be presented with a dialog for
     * selecting the gem to save and its visibility
     * @return true if gem has been saved successfully; false if cancel, close or error in saving
     */
    boolean saveGem() {

        // If we can't save any collectors, display an appropriate dialog and exit
        if (!inSaveableState()) {
            String titleString = getResourceString("CannotSaveDialogTitle");
            String message = getResourceString("NonSaveableGemError");
            JOptionPane.showMessageDialog(this, message, titleString, JOptionPane.ERROR_MESSAGE);
            return false;
        }
   
        CollectorGem targetCollector = getTableTop().getTargetCollector();
        String unqualifiedGemName = targetCollector.getUnqualifiedName();
        QualifiedName gemName = QualifiedName.make(getWorkingModuleName(), unqualifiedGemName);
       
        GemCutterSaveDialog saveDialog = new GemCutterSaveDialog(this, gemName);
        centerWindow(saveDialog);

        // This will block until the user closes the dialog.
        saveDialog.setVisible(true);
   
        if (!saveDialog.isDialogAccepted()) {
            return false;
        }

        Status saveStatus = persistenceManager.saveGem(targetCollector, saveDialog.getScope());
       
        if (saveStatus.getSeverity() != Status.Severity.ERROR) {
            // Get the entity for the newly saved gem.
            GemEntity newGemEntity = getWorkspace().getGemEntity(gemName);
           
            BrowserTree browserTree = getGemBrowser().getBrowserTree();
            //BrowserTreeModel browserTreeModel = (BrowserTreeModel) browserTree.getModel();
           
            // Select the newly saved gem in the gem browser.
            browserTree.selectGemNode(newGemEntity);
           
            // Refresh the tree node associated with this gem entity
            //browserTreeModel.getTreeNode(newGemEntity).refreshNode();
           
            if (saveStatus.getSeverity() == Status.Severity.WARNING) {
                JOptionPane.showMessageDialog(this, saveStatus.getDebugMessage(), getResourceString("SaveGem_SavedWithWarnings"), JOptionPane.WARNING_MESSAGE);
            } else {
                statusMessageManager.displayMessage(this, getResourceString("SaveGem_SaveSuccessful"), StatusMessageDisplayer.MessageType.TRANSIENT, true);
            }

            // Update the dirty edit
            editToUndoWhenNonDirty = extendedUndoManager.editToBeUndone();
            updateWindowTitle();
       
        } else {
            JOptionPane.showMessageDialog(this, saveStatus.getDebugMessage(), getResourceString("SaveGem_SaveFailed"), JOptionPane.ERROR_MESSAGE);
            return false;
        }
        return true;
   
    }
   
    /**
     * Workaround for sun bug id 4711700
     * A bug in the native image loading code sometimes causes the Windows file chooser to throw a
     *   NullPointerException when instantiated in jdk 1.4.2.
     */
    public static void ensureJFileChooserLoadable() {

        if (UIManager.getLookAndFeel().getName().startsWith("Windows")) {
            JFileChooser fileChooser = null;
           
            while (fileChooser == null) {
                try {
                    fileChooser = new JFileChooser();
                } catch (NullPointerException e) {
                }
            }
        }
    }
   
    /**
     * Loads the gem design for the given entity and puts it on the table top.
     * If the given entity is null it will present a dialog with a list of all available designs.
     * @param gemEntity the entity whose design to load (null to prompt the user for it)
     * @throws IllegalArgumentException if there is no design for the given entity
     */
    void openGemDesign(GemEntity gemEntity) {
       
        if (!promptSaveCurrentTableTopIfNonEmpty(null, null)){
            return;
        }
       
        Status loadStatus = new Status("GemDesignLoadStatus");
       
        if (gemEntity != null) {
            persistenceManager.loadGemDesign(gemEntity, loadStatus);
            if (loadStatus.isOK()) {
                // select the gem in the browser
                getGemBrowser().getBrowserTree().selectGemNode(gemEntity);
            }
        } else {
           
            // Display a dialog for the user to pick a design to load.
            FileFilter fileFilter = new ExtensionFileFilter(XMLPersistenceConstants.XML_FILE_EXTENSION, getResourceString("OpenDesign_FileDescription"));
       
            String initialDir = getPreferences().get(OPEN_DESIGN_DIRECTORY_PREF_KEY, OPEN_DESIGN_DIRECTORY_DEFAULT);
           
            JFileChooser fileChooser = new JFileChooser(initialDir);
            fileChooser.setFileFilter(fileFilter);
       
            int chooserOption = fileChooser.showOpenDialog(this);
            if (chooserOption != JFileChooser.APPROVE_OPTION) {
                return;
            }
           
            // Save the directory the user browsed to for next time
            String lastDir = fileChooser.getSelectedFile().getParentFile().getAbsolutePath();
            getPreferences().put(OPEN_DESIGN_DIRECTORY_PREF_KEY, lastDir);
   
            String fileName = fileChooser.getSelectedFile().getAbsolutePath();
            File designFile = new File(fileName);
           
            GemDesign gemDesign = GemDesign.loadGemDesign(designFile, loadStatus);
            QualifiedName designName = gemDesign.getDesignName();
           
            if (designName != null && getWorkspace().getMetaModule(designName.getModuleName()) == null) {

                // If the module the design is from does not exist, ask the user if he wants
                // to load the design in the current module.
               
                int result = JOptionPane.showConfirmDialog(this,
                                                           getResourceString("OpenDesign_ModuleNotOpen"),                                                          
                                                           getResourceString("OpenDesign_ConfirmDialogTitle"),
                                                           JOptionPane.OK_CANCEL_OPTION);
               
                if (result == JOptionPane.CANCEL_OPTION) {
                    return;
                }
            }
           
            persistenceManager.loadGemDesign(gemDesign, loadStatus);
        }
       
        if (!loadStatus.isOK()) {
            String title = getResourceString("WarningDialogTitle");
            String message = getResourceString("OpenErrorsWarning");
            String details = loadStatus.getDebugMessage();
            DetailsDialog dialog = new DetailsDialog(this, title, message, details, DetailsDialog.MessageType.WARNING);
            dialog.doModal();
        }
           
        // reset the table top explorer
        getTableTop().addGemGraphChangeListener(getTableTopExplorer());
        getTableTopExplorerAdapter().getTableTopExplorer().rebuildTree();
           
        // clear the undo stack and dirty edit.
        extendedUndoManager.discardAllEdits();
        editToUndoWhenNonDirty = null;
        updateUndoWidgets();
    }
   
    /**
     * Sets the given locale into GemCutter's preferences.
     * @param locale the locale to be set into the preferences.
     */
    static void setLocaleToPreferences(Locale locale) {
        getPreferences().put(LOCALE_PREF_KEY, LocaleUtilities.localeToCanonicalString(locale));
    }
   
    /**
     * @return the locale preference from GemCutter's preferences.
     */
    public static Locale getLocaleFromPreferences() {
        String localeString = getPreferences().get(LOCALE_PREF_KEY, "");
        if (localeString.equals("")) {
            return LocaleUtilities.INVARIANT_LOCALE;
        } else {
            return LocaleUtilities.localeFromCanonicalString(localeString);
        }
    }
   
    /**
     * Prompts the user with a dialog to save the current table top if it is not empty. Do nothing otherwise.
     * Should be called before any action that will erase the current table top.
     * (Exit, New, Change module, Remove module...etc)
     *
     * @param message the message to display; could be null to use default message
     * @param title title of the dialog window,; could be null to use default title
     * @return true if tabletop is saved successfully or the tabletop is empty;
     * false if cancel, don't save, close or save failed
     *
     */
    private boolean promptSaveCurrentTableTopIfNonEmpty(String message, String title) {
       
        boolean toContinue = true;

        // If the table top has any changes or gems.
        if (isDirty() && tableTop.getDisplayedGems().size() > 1) {
           
            // 3 methods to capture user action are implemented in the JOptionPane below.
            // Button & Action listener (mouse), mnemonics for buttons (<Alt-letter>) and keyEventDispatcher (<letter>)
           
           
            String dialogMessage = message;
            String dialogTitle = title;
            // Use default strings if the arguments are null
            if (dialogMessage == null) {
                dialogMessage = getResourceString("SaveTabletopDialogMessage");
            }
            if (dialogTitle == null) {
                dialogTitle = getResourceString("SaveTabletopDialogTitle");
            }

            final JOptionPane pane = new JOptionPane(dialogMessage, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION);
            JDialog dialog = pane.createDialog(GemCutter.this, dialogTitle);
           
            // Display strings for the options
            final String save = getResourceString("SaveTabletopDialogSave");
            final String dontSave = getResourceString("SaveTabletopDialogNoSave");
            final String cancel = getResourceString("SaveTabletopDialogCancel");
            final String[] optionNames = { save, dontSave, cancel };
           
            final int saveMnemonic = GemCutterActionKeys.MNEMONIC_DIALOG_OPTION_SAVE;
            final int dontSaveMnemonic = GemCutterActionKeys.MNEMONIC_DIALOG_OPTION_DONTSAVE;
            final int cancelMnemonic = GemCutterActionKeys.MNEMONIC_DIALOG_OPTION_CANCEL;
           
            // ActionListener for mouse action on the buttons
            ActionListener setPaneValueWhenClickedListener = new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    String actionCommand = e.getActionCommand();
                    if (actionCommand.equals(save)) {
                        pane.setValue(save);

                    } else if (actionCommand.equals(dontSave)) {
                        pane.setValue(dontSave);

                    } else if (actionCommand.equals(cancel)) {
                        pane.setValue(cancel);
                    }
                }
            };
            // Options to pass into the dialog
            JButton saveBtn = new JButton(save);
            saveBtn.setMnemonic(saveMnemonic);
            saveBtn.addActionListener(setPaneValueWhenClickedListener);

            JButton dontSaveBtn = new JButton(dontSave);
            dontSaveBtn.setMnemonic(dontSaveMnemonic);
            dontSaveBtn.addActionListener(setPaneValueWhenClickedListener);
           
            JButton cancelBtn = new JButton (cancel);
            cancelBtn.setMnemonic(cancelMnemonic);
            cancelBtn.addActionListener(setPaneValueWhenClickedListener);       

            JButton[] optionBtns = { saveBtn, dontSaveBtn, cancelBtn };
            pane.setOptions(optionBtns);

            // Set the default on the Don's Save button
            pane.setInitialValue(dontSaveBtn);
            pane.selectInitialValue();
           
            // The key event dispatcher used to catch the mnemonics for the options (without ALT)
            final KeyEventDispatcher mnemonicKeyEventDispatcher = new KeyEventDispatcher() {
               
                public boolean dispatchKeyEvent(KeyEvent evt) {
                    // Only process the KEY_PRESSED events and ignore KEY_RELEASED and KEY_TYPED events for the same keystroke
                    // If the type check is not implemented, the GemcutterSaveDialog's dispatchKeyEvent will process a
                    // different event from the same key stroke.
                    if (evt.getID() == KeyEvent.KEY_PRESSED) {
                        if (evt.getKeyCode() == saveMnemonic) {
                            evt.consume();
                            pane.setValue(save);
                            return true;
                      
                        } else if (evt.getKeyCode() == dontSaveMnemonic) {
                            evt.consume();
                            pane.setValue(dontSave);
                            return true;
                       
                        } else if (evt.getKeyCode() == cancelMnemonic) {
                            evt.consume();
                            pane.setValue(cancel);
                            return true;
                        }
                    }
                    return false;
                }
            };
            KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(mnemonicKeyEventDispatcher);

            dialog.setVisible(true);

            // Remove the key event dispatcher
            KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(mnemonicKeyEventDispatcher);
           
            // Get the user selected value from the option pane
            Object selectedOption = pane.getValue();
            int userChoice = JOptionPane.CANCEL_OPTION;
            if (selectedOption != null) {
                for (int i = 0; i < optionNames.length; i++) {
                    if (optionNames[i].equals(selectedOption)) {
                        userChoice = i;
                        break;
                    }
                }
            }

            // Determine the result based on the selected value
            if (userChoice == JOptionPane.YES_OPTION) {
                // If unable to save the gem or the save action is cancelled
                if (!saveGem()) {
                    return false;
                }

            } else if (userChoice == JOptionPane.CANCEL_OPTION || userChoice == JOptionPane.CLOSED_OPTION) {
                return false;
            }
        }
        return toContinue;
    }

}
TOP

Related Classes of org.openquark.gems.client.GemCutter$LintOptionChooser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.