/* * 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; } }