/* * 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. */ /* * EnumeratedDataTypeGemGenerator.java * Creation date: Jan 12, 2004 * By: Iulian Radu */ package org.openquark.gems.client.generators; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.dnd.DragSource; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.prefs.Preferences; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JToolTip; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openquark.cal.compiler.ClassInstance; import org.openquark.cal.compiler.DataConstructor; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.QualifiedName; import org.openquark.cal.compiler.Scope; import org.openquark.cal.compiler.SourceModelUtilities; import org.openquark.cal.compiler.TypeChecker; import org.openquark.cal.compiler.TypeClass; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration; import org.openquark.cal.compiler.SourceModel.ModuleDefn; import org.openquark.cal.compiler.SourceModel.TopLevelSourceElement; import org.openquark.cal.module.Cal.Core.CAL_Prelude; import org.openquark.cal.services.IdentifierUtils; import org.openquark.cal.services.Perspective; import org.openquark.gems.client.GemCutter; import org.openquark.gems.client.ValueRunner; import org.openquark.gems.client.utilities.MouseClickDragAdapter; import org.openquark.gems.client.utilities.PreferencesHelper; import org.openquark.gems.client.valueentry.ValueEditorManager; /** * A gem generator for enumerated data types. * * An enumeration is a non-polymorphic type, containing * a list of constructors with 0-arity. Enumeration types generated * by this generator are instances of the Eq class, and optionally of the Ord, Enum, and * Bounded classes as well. * * @author Iulian Radu */ public class EnumeratedDataTypeGemGenerator implements GemGenerator { /** The icon to use for the generator. */ private static final Icon GENERATOR_ICON = new ImageIcon(GemGenerator.class.getResource("/Resources/nav_typeconstructor.gif")); /** * @see org.openquark.gems.client.generators.GemGenerator#launchGenerator(javax.swing.JFrame, org.openquark.cal.services.Perspective, org.openquark.gems.client.ValueRunner, org.openquark.gems.client.valueentry.ValueEditorManager, org.openquark.cal.compiler.TypeChecker) */ public GemGenerator.GeneratedDefinitions launchGenerator(JFrame parent, Perspective perspective, ValueRunner valueRunner, ValueEditorManager valueEditorManager, TypeChecker typeChecker) { if (parent == null || perspective == null) { throw new NullPointerException(); } final EnumeratedTypeGemGeneratorDialog generatorUI = new EnumeratedTypeGemGeneratorDialog(parent, perspective); generatorUI.setResizable(true); generatorUI.setVisible(true); return new GemGenerator.GeneratedDefinitions() { public ModuleDefn getModuleDefn() { return null; } public Map<String, String> getSourceElementMap() { return generatorUI.getSourceDefinitions(); } }; } /** * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorMenuName() */ public String getGeneratorMenuName() { return GeneratorMessages.getString("ETGF_FactoryMenuName"); } /** * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorTitle() */ public String getGeneratorTitle() { return GeneratorMessages.getString("ETGF_FactoryTitle"); } /** * @see org.openquark.gems.client.generators.GemGenerator#getGeneratorIcon() */ public Icon getGeneratorIcon() { return GENERATOR_ICON; } } /** * This is the user interface class for either of the Java factories. * @author Frank Worsley */ class EnumeratedTypeGemGeneratorDialog extends JDialog { /* Dialog icons */ private static final long serialVersionUID = 164116234901065344L; /** The icon to use for error messages. */ private static final Icon ERROR_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/error.gif")); /** The icon to use for warning messages. */ private static final Icon WARNING_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/warning.gif")); /** The icon to use if everything is ok. */ private static final Icon OK_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/checkmark.gif")); /** The up icon **/ private static final Icon UP_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/up.gif")); /** The down icon **/ private static final Icon DOWN_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/down.gif")); /** The enumeration icon **/ private static final Icon ENUMERATION_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/enum.gif")); /** The correction icon **/ private static final Icon CORRECTION_ICON = new ImageIcon(GemCutter.class.getResource("/Resources/undo.gif")); /* Preference key names. */ private static final String DIALOG_PROPERTIES_PREF_KEY = "dialogProperties"; /* Options for derived instances. */ /* - these instances of DerivedInstancesOption are meant to be compared against using reference equality, i.e. with == */ private static final DerivedInstancesOption[] DERIVED_INSTANCES_OPTIONS = new DerivedInstancesOption[] { DerivedInstancesOption.EQ_ONLY_DERIVED_INSTANCES_OPTION, DerivedInstancesOption.EQ_ORD_BOUNDED_ENUM_DERIVED_INSTANCES_OPTION }; /* Dialog Components */ /** Text field for entering the name of the new data type */ private JComboBox gemNameField; /** The radio button for selecting private scope. */ private final JRadioButton privateButton = new JRadioButton(GeneratorMessages.getString("PrivateLabel")); /** The radio button for selecting public scope. */ private final JRadioButton publicButton = new JRadioButton(GeneratorMessages.getString("PublicLabel")); /** The button group for the radio buttons. */ private final ButtonGroup buttonGroup = new ButtonGroup(); /** The combo box for selecting which type classes the generated enumeration should be a derived instance of. */ private JComboBox derivedInstancesField; /** The OK button for the dialog. */ private JButton okButton; /** The cancel button for the dialog. */ private JButton cancelButton; /** The label for displaying status messages. */ private final JLabel statusLabel = new TippedLabel(); /** Text field for entering the enumeration value */ private JTextField valueField; /** The list control representing the enumeration list */ private SwapList enumListControl; /** The encapsulating scroll pane for the list control */ private JScrollPane enumListScrollPane; /** The Add button for the dialog. */ private JButton addButton; /** The Remove button for the dialog. */ private JButton removeButton; /** The shift Up button for the dialog. */ private JButton upButton; /** The shift Down button for the dialog. */ private JButton downButton; /* End dialog components */ /** * The enumeration value which is added when the "ADD" button * is pushed. This is a validated version of the valueField. */ private String suggestedValue = ""; /** * The last typed enumeration name */ private String lastNameTyped = ""; /** * The number of suggested corrections to the enumeration name * field, which were added to the name combo box. */ private int suggestedNameCount; /** * List containing names of all enumerated types within the current module. * Once populated, this list is ordered ascendingly. */ private List<String> allEnumerationNamesList = new ArrayList<String>(); /** The perspective this UI is running in. */ private final Perspective perspective; /* Error codes returned by update state methods */ /** No error */ private final int ERROR_NONE = 0; /** Entered text value already exists in CAL compilation */ private final int ERROR_EXISTING_ENTITY = 1; /** * Entered text is empty */ private final int ERROR_EMPTY = 3; /** * Entered text value is already stored in this or related controls */ private final int ERROR_ALREADY_STORED = 4; /** * Entered text is invalid, and no suggestions possible */ private final int ERROR_NO_SUGGESTIONS = 5; /** * Entered text is invalid, but valid strings are suggested */ private final int ERROR_CORRECTED = 6; /** * Flag indicating wether the user is editing an existing enumeration. */ private boolean editingExistingEnumeration; /** * Flag indicating if an enumeration was modified */ private boolean modifiedEnumeration; /** * Flag indicating if a valid enumeration name was entered */ private boolean validEnumName; /** * (String->String) -- source name to source code. * The list of source definitions we want to create. Ordered by insertion order, so that * definitions we want to create first will be created first. */ private final Map<String, String> sourceDefinitions = new LinkedHashMap<String, String>(); /** Enforced minimum size of this dialog */ private final Dimension minimumSize; /** Timer will refresh window size whenever the dialog is resized **/ private final Timer resizeTimer; /* Inner class declarations */ /** * Mouse wheel listener for JComboBox class. On wheel rotate, * this listener shifts the current selection appropriately. * * @author Iulian Radu */ public class ComboBoxWheelListener implements MouseWheelListener { public void mouseWheelMoved(MouseWheelEvent e) { int items = ((JComboBox)e.getSource()).getItemCount(); int delta = ((JComboBox)e.getSource()).getSelectedIndex() + e.getWheelRotation(); if (items == 0) { delta = -1; } else { if (delta < 0) { delta = 0; } else if (delta > items - 1) { delta = items - 1; } } ((JComboBox)e.getSource()).setSelectedIndex(delta); } } /** * Extended JList class implementing capable of handling * shift up/down and mouse drag operations, * * Uses DefaultListModel for list models. * * @author Iulian Radu */ public class SwapList extends JList { private static final long serialVersionUID = -2905640590624599682L; /** Flag indicating if mouse is really dragging an item */ private boolean isDragging = false; /** * If list gains focus and nothing is selected, select first * item if it exists. */ private class ListFocusListener extends FocusAdapter { @Override public void focusGained(FocusEvent e) { int len = getModel().getSize(); int selected = getSelectedIndex(); if (len > 0 && (selected < 0 ||selected > len - 1)) { setSelectedIndex(0); } } } /** * Key listener: * Listen for DELETE keypress, and remove selected item on action. * Listen for ESCAPE keypress, and abort drag or discard dialog on action. */ private class ListKeyListener extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_DELETE) { removeSelected(); } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { if (!enumListControl.isDragging) { dispose(); } else { // Abort drag process isDragging = false; setCursor(null); mouseHandler.updateSeparator(-1); repaint(); } } } } /** * Inner class to handle mouse drag events for swapping items on the list. * Creation date: (20/01/04 9:48 AM) * @author Iulian Radu */ private class MouseHandler extends org.openquark.gems.client.utilities.MouseClickDragAdapter { /** The index of the item clicked, if any. */ private int indexClicked; /** The position of the separator between panels. */ private int separatorPosition = -1; /** * Constructor for the Mouse Handler */ private MouseHandler() { isDragging = false; } /** * Move the drag mode into the aborted state. */ @Override protected void abortDrag() { super.abortDrag(); isDragging = false; setCursor(DragSource.DefaultMoveNoDrop); clearSelection(); indexClicked = -1; } /** * Select clicked item, and cancel drag if second button pushed. */ @Override public void mousePressed(MouseEvent e){ indexClicked = ((JList)e.getSource()).locationToIndex(e.getPoint()); // Are dragging and second button clicked; abort if ((e.getButton() == MouseEvent.BUTTON3) && isDragging) { isDragging = false; setCursor(null); updateSeparator(-1); repaint(); } } /** * Select clicked item. * @return boolean true if the click was a double click */ @Override public boolean mouseReallyClicked(MouseEvent e){ boolean doubleClicked = super.mouseReallyClicked(e); indexClicked = ((JList)e.getSource()).locationToIndex(e.getPoint()); return doubleClicked; } /** * On drag motion, update separator. * * @param e MouseEvent the relevant event * @param where Point the (possibly adjusted from e) coordinates of the drag * @param wasDragging boolean True: this is a continuation of a drag. False: first call upon transition * from pressed to drag. */ @Override public void mouseReallyDragged(MouseEvent e, Point where, boolean wasDragging) { if (isDragging) { // ignore anything that is not a left mouse button if (!SwingUtilities.isLeftMouseButton(e)) { return; } // looks nicer if we clear the selection setSelectedIndex(indexClicked); // Update the on-screen separator int newIndex = locationToSeparatorIndex(e.getPoint()); if (((JList)e.getSource()).getModel().getSize() > 0) { updateSeparator(newIndex); } int i = ((JList)e.getSource()).locationToIndex(e.getPoint()); if (i > -1 && i < ((JList)e.getSource()).getModel().getSize()){ setCursor(DragSource.DefaultMoveDrop); } else { setCursor(DragSource.DefaultMoveNoDrop); } } } /** * Carry out setup appropriate to enter the drag state. * @param e MouseEvent the mouse event which triggered entry into the drag state. */ @Override public void enterDragState(MouseEvent e) { super.enterDragState(e); // ignore anything that is not a left mouse button if (!SwingUtilities.isLeftMouseButton(e)) { return; } // Get the part which the user clicked indexClicked = ((JList)e.getSource()).locationToIndex(e.getPoint()); isDragging = true; } /** * Carry out setup appropriate to exit the drag state. * Dragged item is swapped into the finished drag position. * @param e MouseEvent the mouse event which caused an exit from the drag state */ @Override public void exitDragState(MouseEvent e) { super.exitDragState(e); if (isDragging) { isDragging = false; setCursor(null); // Where are we now? int index = locationToSeparatorIndex(e.getPoint()); // If we've finished dragging. Finish up and do the appropriate moves // Undraw the last separator updateSeparator(-1); if (indexClicked != -1) { // if it's not a valid place to drop -> just go back to the old order if (index < 0) { index = indexClicked; } // Do nothing if nothing changed. if (index != indexClicked) { DefaultListModel model = (DefaultListModel)getModel(); Object item = model.getElementAt(indexClicked); model.remove(indexClicked); if (index > indexClicked) { index = index-1; } if (index >= model.getSize()) { model.addElement(item); } else { model.add(index,item); } } setSelectedIndex(index); } else { // Clicked another mouse button, set selected to original setSelectedIndex(indexClicked); } setCursor(null); indexClicked = -1; repaint(); } } /** * Convert a location in the JList to the index of the separator location to which it corresponds. * Creation date: (09/07/2001 6:16:28 PM) * @param p Point the location * @return int the index of the separator location to which the point corresponds. -1 if it doesn't correspond * to a sensible location. */ private int locationToSeparatorIndex(Point p) { // first check to see if the point is outside the variables display Rectangle visibleRect = getVisibleRect(); if (!visibleRect.contains(p)) { return -1; } // index is the number of panel midpoints above p int index = 0; int numVarPanels = getModel().getSize(); while (index < numVarPanels) { Rectangle rect = getCellBounds(index, index); // compare with the Y coordinate halfway down varPan if (p.getY() < (rect.getY() + rect.getHeight() / 2)) { break; } index++; } // can only place the separator among other arguments int maxIndex = numVarPanels; if (index > maxIndex) { index = maxIndex; } return index; } /** * Draw or undraw a separator between elements in the JList * Creation date: (09/07/2001 6:15:57 PM) * @param index int the index at which to draw the separator * @param undraw boolean true to undraw, false to draw * @return int the index at whcih the separator was really drawn */ private int drawSeparator(int index, boolean undraw) { if (index < 0) { // don't draw return -1; } // Get a graphics object Graphics2D g2d = (Graphics2D)getGraphics(); // Find the appropriate color and enter paint mode if (undraw) { g2d.setColor(getBackground()); } else { g2d.setColor(Color.black); } g2d.setPaintMode(); // preliminary setup int numVarPanels = getModel().getSize(); if (numVarPanels < index) { index = numVarPanels; } // declare a rectangle for the main part of the separator Rectangle rect; if (index < numVarPanels) { rect = getCellBounds(index, index); } else { // below the last element in the list - move to the bottom of the cell rect = getCellBounds(index - 1, index - 1); rect.y += rect.height; } // adjust the height and y-coordinate of the separator rect.height = 1; rect.y -= 1; rect.x -=1; // an additional bleb at the front Polygon poly = new Polygon(); poly.addPoint(rect.x, rect.y-4); poly.addPoint(rect.x, rect.y+rect.height+4); poly.addPoint(rect.x+4, rect.y+(rect.height/2)); // Draw the separator g2d.draw(rect); g2d.fill(poly); // Free the graphics object g2d.dispose(); return index; } /** * Paint the separator. * Creation date: (09/07/2001 6:17:55 PM) */ public void paintSeparator() { SwingUtilities.invokeLater(new Thread() { @Override public void run() { // It's possible that the user stopped dragging before this Thread was run. // In which case, we don't need to re-draw the separator. if (isUsefulDragMode(dragMode)) { drawSeparator(separatorPosition, false); } } }); } /** * Update the displayed position of the separator. * Creation date: (12/07/2001 1:26:51 PM) * @param index int the new position of the separator */ void updateSeparator(int index) { // preliminary setup int numVarPanels = getModel().getSize(); if (numVarPanels < index) { index = numVarPanels; } // check if anything to do if (separatorPosition == index) { return; } // Turn off separator at last position if (separatorPosition > -1) { drawSeparator(separatorPosition, true); } // Turn on separator at this position drawSeparator(index, false); // update separator position separatorPosition = index; } } /** Constructor */ public SwapList() { super(); initialize(); } /** * Constructor * * @param l list model to use */ public SwapList(DefaultListModel l) { super(l); initialize(); } /** * Initializes listeners for this object */ private void initialize() { super.addKeyListener(new ListKeyListener()); super.addFocusListener(new ListFocusListener()); addMouseListener(mouseHandler); addMouseMotionListener(mouseHandler); } /** * Shifts up the selected element * @return true if shift was successful, false if not */ public boolean shiftUp() { int i = getSelectedIndex(); if (i > 0) { return swapElements(i,i-1); } else { return false; } } /** * Shifts down the selected element * * @return true if shift was successful, false if not */ public boolean shiftDown() { int i = getSelectedIndex(); if (i >= 0 && i < (super.getModel().getSize() - 1)) { return swapElements(i, i+1); } else { return false; } } /** * Swaps elements at the specified indices. * * @param i index of first element * @param j index of second element * @return true if swap was successful; false if not */ public boolean swapElements(int i, int j) { DefaultListModel m = (DefaultListModel) super.getModel(); if ((i >= 0 && i <= (m.getSize() - 1)) && (j >= 0 && j <= (m.getSize() - 1)) && (i != j)) { Object s = m.get(i); m.set(i, m.get(j)); m.set(j, s); setSelectedIndex(j); return true; } else { return false; } } /** * Removes the currently selected item, if any. * * @return true if remove was successful, false otherwise */ public boolean removeSelected() { int i = getSelectedIndex(); if (i >= 0 && super.getModel().getSize() > 0) { DefaultListModel m = (DefaultListModel) super.getModel(); m.remove(i); if (m.getSize() > 0) { this.setSelectedIndex((i == 0)? i : i-1); } return true; } else { return false; } } /** * The tooltip reflects the value of the list item which the mouse * is pointing to. */ @Override public String getToolTipText(MouseEvent e) { int i = super.locationToIndex(e.getPoint()); if (i >= 0 && i < super.getModel().getSize()) { return (String)super.getModel().getElementAt(i); } else { return ""; } } /** * Handler for mouse events on the list */ private MouseHandler mouseHandler = new MouseHandler(); /** * Paint the Variables Display area. * Creation date: (09/07/2001 5:24:45 PM) * @param g Graphics the graphics object to use. */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); mouseHandler.paintSeparator(); } } /** * Item renderer for the enumeration name combo box. * Keeps track of a list of items which should be displayed * with special icons (ie: existing enumerations). * * @author Iulian Radu */ private class ComboBoxRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 1050231330699652507L; /** * List of existing enumeration names. Rendered items that * are contained within this list will have special icons */ List<String> existentList; /** Constructor */ public ComboBoxRenderer() { super(); initialize(null); } /** Constructor */ public ComboBoxRenderer(List<String> specialList) { super(); if (specialList == null) { throw new NullPointerException(); } initialize(specialList); } /** Set component attributes */ private void initialize(List<String> list) { setOpaque(true); setHorizontalAlignment(LEFT); setVerticalAlignment(TOP); existentList = list; } /** * This method finds the image and text corresponding * to the selected value and returns the label, set up * to display the text and image. */ public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } Icon icon; if ((existentList != null) && (existentList.contains(value))) { icon = ENUMERATION_ICON; } else { icon = CORRECTION_ICON; } String text = (String) value; setIcon(icon); setText(text); setFont(list.getFont()); return this; } } /** * Label which automatically updates its tooltip when its text changes. * * @author Iulian Radu */ public class TippedLabel extends JLabel { private static final long serialVersionUID = 7910010835431701644L; /** Currently displayed tooltip */ private JToolTip myTip; /** Create and save tooltip */ @Override public JToolTip createToolTip() { myTip = super.createToolTip(); return myTip; } /** Set text on the displayed tooltip also */ @Override public void setToolTipText(String text) { super.setToolTipText(text); if (myTip != null) { myTip.setTipText(text); myTip.setSize(myTip.getPreferredSize()); } return; } } /* Method declarations */ /** * Constructor for a new generator ui. * @param parent the parent of the dialog * @param perspective the perspective the UI should use */ public EnumeratedTypeGemGeneratorDialog(JFrame parent, Perspective perspective) { super(parent, true); if (perspective == null) { throw new NullPointerException(); } this.perspective = perspective; // Initialize dialog components and add // listener to Cancel the dialog if the user presses ESC KeyListener dismissKeyListener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { dispose(); } } }; getOkButton().addKeyListener(dismissKeyListener); getCancelButton().addKeyListener(dismissKeyListener); privateButton.addKeyListener(dismissKeyListener); publicButton.addKeyListener(dismissKeyListener); getAddButton().addKeyListener(dismissKeyListener); getRemoveButton().addKeyListener(dismissKeyListener); getUpButton().addKeyListener(dismissKeyListener); getDownButton().addKeyListener(dismissKeyListener); getValueField().addKeyListener(dismissKeyListener); getValueField().setColumns(10); ((JTextField)getNameField().getEditor().getEditorComponent()).setColumns(10); getValueList(); setTitle(GeneratorMessages.getString("ETGF_GenerateTypeTitle")); populateEnumerations(); filterEnumerationsCombo(""); gemNameField.setSelectedIndex(-1); getDerivedInstancesField().setSelectedItem(DerivedInstancesOption.EQ_ONLY_DERIVED_INSTANCES_OPTION); ((JTextField)gemNameField.getEditor().getEditorComponent()).setColumns(25); updateAllStates(false); ((DefaultListModel)enumListControl.getModel()).clear(); suggestedNameCount = 0; editingExistingEnumeration = false; modifiedEnumeration = false; validEnumName = false; // Make the type name field have default focus setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { private static final long serialVersionUID = -9147781078212867922L; @Override public Component getDefaultComponent(Container c) { return gemNameField; } @Override public Component getFirstComponent(Container c) { return gemNameField; } }); // Default button is Add getRootPane().setDefaultButton(getAddButton()); // Add items to dialog and pack getContentPane().setLayout(new BorderLayout()); JPanel mainPanel = getMainPanel(); getContentPane().add(mainPanel, BorderLayout.CENTER); getContentPane().add(getButtonPanel(), BorderLayout.SOUTH); buttonGroup.add(publicButton); buttonGroup.add(privateButton); buttonGroup.setSelected(publicButton.getModel(), true); pack(); // Ensure minimum size is maintained minimumSize = getSize(); // Timer will check if the dialog size is under or equal to // minimum size, and simulate size change in order to // refresh window to current size. This simulation is needed // because on systems where window contents are displayed while // a window resizes, the size of the dialog may become out of sync // with its actual window. (refer to java bug: 4450706) resizeTimer = new Timer(1000, new ActionListener() { public void actionPerformed(ActionEvent e) { Dimension size = EnumeratedTypeGemGeneratorDialog.this.getSize(); if ((size.width <= minimumSize.width) || (size.height <= minimumSize.height)) { // The window of the dialog is only resized if // the dialog size actually changes. So, simulate // a size change. setSize(new Dimension(size.width, size.height+1)); setSize(size); } } }); resizeTimer.restart(); resizeTimer.setRepeats(false); addComponentListener(new ComponentAdapter() { // Listener called whenever dialog size changes // (eg: due to manual window resized, or setSize call) @Override public void componentResized(ComponentEvent e) { boolean wasBadSize = enforceMinimumSize(minimumSize); validate(); if (wasBadSize) { resizeTimer.restart(); } } }); // Handle loading / saving of preferences addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Preferences prefs = Preferences.userNodeForPackage(EnumeratedTypeGemGeneratorDialog.class); PreferencesHelper.putDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, EnumeratedTypeGemGeneratorDialog.this); } }); Preferences prefs = Preferences.userNodeForPackage(EnumeratedTypeGemGeneratorDialog.class); PreferencesHelper.getDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, this, new Dimension(625, 445), new Point(50, 50)); } /** * If the window is smaller then its minimum size then the * size is set to the minimum size. * * @return true if minimum dialog size has been enforced; false if not */ private boolean enforceMinimumSize(Dimension minSize) { boolean wasBadSize = false; Dimension size = getSize(); if (size.height < minSize.height) { size.height = minSize.height; wasBadSize = true; } if (size.width < minSize.width) { size.width = minSize.width; wasBadSize = true; } setSize(size); return wasBadSize; } /** * Updates states of all components. * * @param typedName true if update called due to keyboard event on * enumeration name combo box */ private void updateAllStates(boolean typedName) { String statusText = GeneratorMessages.getString("ETGF_OkMessage"); String statusToolTipText = statusText; Icon statusIcon = OK_ICON; boolean okEnabled = true; boolean addEnabled = true; boolean upEnabled = true; boolean downEnabled = true; boolean removeEnabled = true; // Update the Name field and OK button switch (updateEnumNameState(typedName)) { case ERROR_NONE : { validEnumName = true; break; } case ERROR_EMPTY : { String message = GeneratorMessages.getString("ETGF_TypeSomething"); statusText = message; statusToolTipText = message; statusIcon = ERROR_ICON; okEnabled = false; validEnumName = false; break; } case ERROR_CORRECTED : { statusText = statusLabel.getText(); statusToolTipText = statusLabel.getToolTipText(); statusIcon = ERROR_ICON; okEnabled = false; validEnumName = false; break; } case ERROR_NO_SUGGESTIONS : { statusText = GeneratorMessages.getString("ETGF_InvalidType"); statusToolTipText = statusLabel.getToolTipText(); statusIcon = ERROR_ICON; okEnabled = false; validEnumName = false; break; } case ERROR_EXISTING_ENTITY : { String message = GeneratorMessages.getString("ETGF_DataTypeExists"); statusText = message; statusToolTipText = message; statusIcon = WARNING_ICON; okEnabled = true; validEnumName = true; break; } } // Update Value field and Add button switch (updateValueNameState()) { case ERROR_NONE : break; case ERROR_CORRECTED : { if (statusIcon != ERROR_ICON) { statusText = statusLabel.getText(); statusToolTipText = statusLabel.getToolTipText(); statusIcon = ERROR_ICON; } addEnabled = true; break; } case ERROR_EMPTY : { addEnabled = false; break; } case ERROR_NO_SUGGESTIONS : { if (statusIcon != ERROR_ICON) { statusText = GeneratorMessages.getString("ETGF_InvalidValue"); statusToolTipText = statusLabel.getToolTipText(); statusIcon = ERROR_ICON; } addEnabled = false; break; } case ERROR_ALREADY_STORED : { if (statusIcon != ERROR_ICON) { String errors = GeneratorMessages.getString("ETGF_RepeatedEnumeration"); statusText = errors; statusToolTipText = "<html><body>" + errors + "</body></html>"; statusIcon = ERROR_ICON; } addEnabled = false; break; } case ERROR_EXISTING_ENTITY : { if (statusIcon != ERROR_ICON) { statusText = statusLabel.getText(); statusToolTipText = statusLabel.getToolTipText(); statusIcon = ERROR_ICON; } addEnabled = false; break; } } // Update enumeration value list switch (updateValueListState(typedName)) { case ERROR_NONE : break; case ERROR_EMPTY : { if (statusIcon != ERROR_ICON) { String message = GeneratorMessages.getString("ETGF_EnumerationNeeded"); statusText = message; statusToolTipText = message; statusIcon = ERROR_ICON; } okEnabled = false; break; } } statusLabel.setText(statusText); statusLabel.setToolTipText(statusToolTipText); statusLabel.setIcon(statusIcon); getOkButton().setEnabled(okEnabled); getAddButton().setEnabled(addEnabled); getUpButton().setEnabled(upEnabled); getDownButton().setEnabled(downEnabled); getRemoveButton().setEnabled(removeEnabled); // Update rest of buttons updateOtherButtonStates(); } /** * Adds the suggested enumeration value string to the * enumeration value list, and updates affected components * */ private void addSuggestedValueToList() { modifiedEnumeration = true; ((DefaultListModel)enumListControl.getModel()).addElement(suggestedValue); valueField.setText(""); updateAllStates(false); valueField.requestFocus(); } /** * Returns status of enumeration value list, and updates the list elements * if an existing enumeration was selected. * * @param justTyped true if the state was changed while typing; false if not * @return error code indicating status of update operation */ private int updateValueListState(boolean justTyped) { if (justTyped) { // If we are not modifying an enumeration if (!modifiedEnumeration) { if (editingExistingEnumeration) { // Was looking at existing enumeration, so clear the fields ((DefaultListModel)enumListControl.getModel()).clear(); } // See if we are looking at existing enumeration, and repopulate if so editingExistingEnumeration = retrieveEnumerationValues(((JTextField)gemNameField.getEditor().getEditorComponent()).getText()); if (editingExistingEnumeration) { validEnumName = true; } } } // Check that at least one enumeration value has been added to the list if (((DefaultListModel)enumListControl.getModel()).getSize() < 1) { return ERROR_EMPTY; } else { return ERROR_NONE; } } /** * Updates the state of the Ok button to only be enabled if the user has entered * all required information. Also updates the information message displayed. * * @param justTyped true if the state was changed while typing; false if not * @return error code indicating status of update operation */ private int updateEnumNameState(boolean justTyped) { String gemName = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); // Check for empty string if (gemName == null || gemName.length()==0) { return ERROR_EMPTY; } // Check that typed value is not already in list if (!justTyped) { for (int i = 0; i < gemNameField.getItemCount(); i++) { if (gemName.equals(gemNameField.getItemAt(i))) { if (i < suggestedNameCount) { return ERROR_NONE; } else { return ERROR_EXISTING_ENTITY; } } } } else { justTyped = false; } // Validate and correct value name // The errors text will hold description of all errors // encountered while correcting. String errors = ""; IdentifierUtils.ValidatedIdentifier validationResult = IdentifierUtils.makeValidatedIdentifier(gemName,true); if (!validationResult.isValid()) { // Convert error codes to strings and append to tooltip String firstError = validationErrorForTypeToString(validationResult.getNthError(0)); errors = firstError+"<p>"; for (int i = 1; i < validationResult.getNErrors(); i++) { errors += validationErrorForTypeToString(validationResult.getNthError(i))+"<p>"; } // Add suggestions to value combo box, filtering the already existing // enumerations (if by chance a suggested enumeration exists in the module, // it will already be populated in the combo box) String filterString = gemName; String addItem = null; if (validationResult.hasSuggestion()) { filterString = validationResult.getSuggestion(); if (perspective.getWorkingModuleTypeInfo().getTypeConstructor(validationResult.getSuggestion())!=null) { // Suggestion is already in list } else { addItem = validationResult.getSuggestion(); } } else { addToNameCombo(null, ""); statusLabel.setToolTipText("<html><body>" + errors + "</body></html>"); return ERROR_NO_SUGGESTIONS; } addToNameCombo(addItem, filterString); // Report error statusLabel.setText(firstError); errors = errors + GeneratorMessages.getString("ETGF_Suggestion") + filterString + "<p>"; statusLabel.setToolTipText("<html><body>" + errors + "</body></html>"); return ERROR_CORRECTED; } // Check if data type with the given name already exists if (perspective.getWorkingModuleTypeInfo().getTypeConstructor(gemName) != null) { addToNameCombo(null, gemName); return ERROR_EXISTING_ENTITY; } // This component is ok addToNameCombo(null,gemName); return ERROR_NONE; } /** * Adds a value to the enumeration name combo box, and refreshes the * combo box after the list items have been filtered. * * @param newItem value to be added to list (can be null) * @param filterString string to filter items by */ private void addToNameCombo(String newItem, String filterString) { String editorValue = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); int ss = ((JTextField)gemNameField.getEditor().getEditorComponent()).getSelectionStart(); int se = ((JTextField)gemNameField.getEditor().getEditorComponent()).getSelectionEnd(); int cp = ((JTextField)gemNameField.getEditor().getEditorComponent()).getCaretPosition(); boolean vis = gemNameField.getEditor().getEditorComponent().hasFocus(); gemNameField.setPopupVisible(false); suggestedNameCount = 0; gemNameField.removeAllItems(); if (newItem != null) { suggestedNameCount = 1; gemNameField.addItem(newItem); } filterEnumerationsCombo(filterString); gemNameField.setSelectedIndex(-1); if (gemNameField.getItemCount() <= 0) { vis = false; } gemNameField.setPopupVisible(vis); gemNameField.getEditor().setItem(editorValue); ((JTextField)gemNameField.getEditor().getEditorComponent()).select(ss, se); ((JTextField)gemNameField.getEditor().getEditorComponent()).moveCaretPosition(cp); } /** * Updates the state of the Add button to only be enabled if the user has entered * all required information. Also updates the information message displayed. * * @return error code indicating status of update operation */ private int updateValueNameState() { String valueName = valueField.getText(); // Check for Empty string if (valueName == null || valueName.length() == 0) { return ERROR_EMPTY; } IdentifierUtils.ValidatedIdentifier validationResult = IdentifierUtils.makeValidatedIdentifier(valueName,true); if (validationResult.isValid()) { // The constructor string has valid syntax. Now check that suggestion does // not belong to another type, and has not been added to our value list. suggestedValue = valueName; return checkSuggestedValue(suggestedValue); } // Validate and correct value name // The errors text will hold description of all errors // encountered while correcting. // Convert error codes to strings and append to tooltip String firstError = validationErrorForValueToString(validationResult.getNthError(0)); String errors = firstError+"<p>"; int errorCount = validationResult.getNErrors(); for (int i = 1; i < errorCount; i++) { errors += validationErrorForValueToString(validationResult.getNthError(i)) + "<p>"; } // Check that suggestion does not belong to another // type, and has not been added to our value list. if (!validationResult.hasSuggestion() || (checkSuggestedValue(validationResult.getSuggestion()) != ERROR_NONE)) { statusLabel.setToolTipText("<html><body>" + errors + "</body></html>"); return ERROR_NO_SUGGESTIONS; } suggestedValue = validationResult.getSuggestion(); statusLabel.setText(firstError); errors = errors + GeneratorMessages.getString("ETGF_Suggestion") + suggestedValue + "<p>"; statusLabel.setToolTipText("<html><body>" + errors + "</body></html>"); return ERROR_CORRECTED; } /** * Checks that the suggested enumeration value does not exist as a constructor * and has not been already added to the value list. * * @param valueName suggested value * @return ERROR_NONE if no error; * ERROR_ALREADY_STORED if the value is contained in the list * ERROR_EXISTING_ENTITY if the value is already a constructor */ private int checkSuggestedValue(String valueName) { if (valueName == null) { throw new NullPointerException(); } // Check if specified value has already been added to value list if (((DefaultListModel)enumListControl.getModel()).contains(valueName)) { return ERROR_ALREADY_STORED; } // Check if specified value is not already a constructor DataConstructor constr = perspective.getWorkingModuleTypeInfo().getDataConstructor(valueName); if (constr == null) { return ERROR_NONE; } QualifiedName typeName = constr.getTypeConstructor().getName(); if (!typeName.getUnqualifiedName().equals( ((JTextField)gemNameField.getEditor().getEditorComponent()).getText() )) { String statusMessage = "<html><body>"+ GeneratorMessages.getString("ETGF_ExistingConstructorFor")+ typeName.getUnqualifiedName()+ "</body></html>"; statusLabel.setToolTipText(statusMessage); statusLabel.setText(statusMessage); return ERROR_EXISTING_ENTITY; } return ERROR_NONE; } /** * Updates the state of Up,Down, and Remove buttons based on * the enumListControl selection. */ private void updateOtherButtonStates() { int index = enumListControl.getSelectedIndex(); int maxIndex = ((DefaultListModel)enumListControl.getModel()).getSize() - 1; getRemoveButton().setEnabled(index >= 0 && index <= maxIndex); getUpButton().setEnabled (index > 0 && index <= maxIndex); getDownButton().setEnabled (index >= 0 && index < maxIndex); } /** * Translates ValidationError enumerations, for type validation, into error * message strings. * * @param error to decode * @return error message string */ private String validationErrorForTypeToString(IdentifierUtils.ValidationStatus error) { if (error == null) { throw new NullPointerException(); } if (error == IdentifierUtils.ValidationStatus.INVALID_CONTENT) { return GeneratorMessages.getString("ETGF_IllegalTypeContentCharacters"); } else if (error == IdentifierUtils.ValidationStatus.INVALID_START) { return GeneratorMessages.getString("ETGF_IllegalTypeStartCharacters"); } else if (error == IdentifierUtils.ValidationStatus.NEED_UPPER) { return GeneratorMessages.getString("ETGF_CorrectionTypeUpperStart"); } else if (error == IdentifierUtils.ValidationStatus.EXISTING_KEYWORD) { return GeneratorMessages.getString("ETGF_ExistingKeyword"); } else { return GeneratorMessages.getString("ETGF_UnknownError"); } } /** * Translates ValidationError enumerations, for value validation, into error * message strings. * * @param error to decode * @return error message string */ private String validationErrorForValueToString(IdentifierUtils.ValidationStatus error) { if (error == null) { throw new NullPointerException(); } if (error == IdentifierUtils.ValidationStatus.INVALID_CONTENT) { return GeneratorMessages.getString("ETGF_IllegalValueContentCharacters"); } else if (error == IdentifierUtils.ValidationStatus.INVALID_START) { return GeneratorMessages.getString("ETGF_IllegalValueStartCharacters"); } else if (error == IdentifierUtils.ValidationStatus.NEED_UPPER) { return GeneratorMessages.getString("ETGF_CorrectionValueUpperStart"); } else if (error == IdentifierUtils.ValidationStatus.EXISTING_KEYWORD) { return GeneratorMessages.getString("ETGF_ExistingKeyword"); } else { return GeneratorMessages.getString("ETGF_UnknownError"); } } /** * @return the main panel that shows the contents of the dialog */ private JPanel getMainPanel() { JPanel javaPanel = new JPanel(); javaPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); javaPanel.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.NORTHWEST; constraints.gridx = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.fill = GridBagConstraints.NONE; constraints.insets = new Insets(5, 5, 10, 5); javaPanel.add(statusLabel, constraints); statusLabel.setFont(getFont().deriveFont(Font.BOLD)); statusLabel.setMaximumSize(statusLabel.getSize()); constraints.gridx = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5, 5, 5, 5); javaPanel.add(new JLabel(GeneratorMessages.getString("ETGF_GemNameHeader")), constraints); constraints.gridx = 2; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.RELATIVE; javaPanel.add(getNameField(), constraints); constraints.gridx = GridBagConstraints.RELATIVE; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(new JLabel(""), constraints); constraints.gridx = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(new JLabel(GeneratorMessages.getString("ETGF_VisibilityHeader")), constraints); constraints.gridx = 2; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(publicButton, constraints); constraints.gridx = 3; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(privateButton, constraints); constraints.gridx = 4; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; javaPanel.add(new JLabel(""), constraints); constraints.gridx = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(new JLabel(GeneratorMessages.getString("ETGF_InstancesHeader")), constraints); constraints.gridx = 2; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.RELATIVE; javaPanel.add(getDerivedInstancesField(), constraints); constraints.gridx = GridBagConstraints.RELATIVE; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(new JLabel(""), constraints); constraints.gridx = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(new JLabel(GeneratorMessages.getString("ETGF_EnumerationHeader")), constraints); constraints.gridx = 2; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.RELATIVE; javaPanel.add(getValueField(), constraints); constraints.gridx = GridBagConstraints.RELATIVE; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(new JLabel(""), constraints); constraints.gridx = 2; constraints.weightx = 1; constraints.weighty = 1; constraints.gridheight = 6; constraints.gridwidth = GridBagConstraints.RELATIVE; constraints.fill = GridBagConstraints.BOTH; if (enumListScrollPane == null) { getValueList(); } javaPanel.add(enumListScrollPane, constraints); // Arrows go into separate right panel { JPanel p2 = new JPanel(); p2.setLayout(new GridBagLayout()); constraints.insets = new Insets(5, 5, 0, 5); constraints.gridx = 1; constraints.gridy = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.gridheight = 1; constraints.gridwidth = 1; p2.add(getUpButton(),constraints); constraints.gridx = GridBagConstraints.RELATIVE; constraints.gridy = 1; constraints.weightx = 1; constraints.weighty = 0; constraints.gridwidth = GridBagConstraints.REMAINDER; constraints.anchor = GridBagConstraints.LINE_END; p2.add(new JLabel(""),constraints); constraints.gridx = 1; constraints.gridy++; constraints.weightx = 0; constraints.weighty = 0; constraints.gridheight = 1; constraints.gridwidth = 1; p2.add(getDownButton(), constraints); constraints.insets = new Insets(0, 0, 5, 0); constraints.gridx = GridBagConstraints.RELATIVE; constraints.gridy = 5; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; javaPanel.add(p2,constraints); } constraints.insets = new Insets(5, 5, 5, 5); constraints.gridx = GridBagConstraints.RELATIVE; constraints.gridy++; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(getAddButton(), constraints); addButton.setMinimumSize(new Dimension(200,200)); constraints.gridx = GridBagConstraints.RELATIVE; constraints.gridy++; constraints.weightx = 0; constraints.weighty = 0; constraints.gridheight = 1; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(getRemoveButton(), constraints); constraints.gridx = GridBagConstraints.RELATIVE; constraints.gridy++; constraints.weightx = 0; constraints.weighty = 0; constraints.gridwidth = 1; constraints.anchor = GridBagConstraints.LINE_END; javaPanel.add(new JLabel(""), constraints); return javaPanel; } /** * @return the panel that contains the buttons at the bottom of the dialog */ private JPanel getButtonPanel() { JPanel buttonPanel = new JPanel(); buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); buttonPanel.add(Box.createHorizontalGlue()); buttonPanel.add(getOkButton()); buttonPanel.add(Box.createHorizontalStrut(5)); buttonPanel.add(getCancelButton()); return buttonPanel; } /** * @return initialized enumeration value text field */ private JTextField getValueField() { if (valueField == null) { valueField = new JTextField(); valueField.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { updateAllStates(false); if ((e.getKeyCode() == KeyEvent.VK_ENTER) && getAddButton().isEnabled()) { // Pushed ENTER, and possible to push Add; add value to list addSuggestedValueToList(); return; } } }); valueField.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // User selected an item from dropdown list; update suggestedValue to use // (the dropdown is populated with only valid values) suggestedValue = valueField.getText(); updateAllStates(false); } }); } return valueField; } /** * @return initialized enumeration name combo box */ private JComboBox getNameField() { if (gemNameField == null) { gemNameField = new JComboBox(); gemNameField.setEditable(true); gemNameField.getEditor().setItem(null); gemNameField.setSelectedItem(null); gemNameField.addMouseWheelListener(new ComboBoxWheelListener()); gemNameField.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { if (gemNameField.isPopupVisible()) { // Ignore it, processed by combobox to hide popup } else { dispose(); } return; } } @Override public void keyReleased(KeyEvent e) { int selectedIndex = gemNameField.getSelectedIndex(); // If ENTER pushed and name is valid, jump to value field if (e.getKeyCode() == KeyEvent.VK_ENTER) { if (validEnumName) { if ((selectedIndex > -1) && (selectedIndex < suggestedNameCount)) { ((JTextField)gemNameField.getEditor().getEditorComponent()).setText( (String)gemNameField.getItemAt(selectedIndex)); validEnumName = true; } lastNameTyped = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); valueField.requestFocus(); } else { // Select first item if possible if (gemNameField.getItemCount() > 0) { ((JTextField)gemNameField.getEditor().getEditorComponent()).setText( (String)gemNameField.getItemAt(0)); validEnumName = true; lastNameTyped = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); valueField.requestFocus(); } } return; } // If ESC pushed, put back last typed text if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { gemNameField.setSelectedIndex(-1); ((JTextField)gemNameField.getEditor().getEditorComponent()).setText(lastNameTyped); updateAllStates(true); gemNameField.hidePopup(); return; } // Update states, and display whole list if nothing typed String name = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); updateAllStates((e.getKeyChar() != KeyEvent.CHAR_UNDEFINED)); if ((name == null) || (name.length() == 0)) { gemNameField.setPopupVisible(false); suggestedNameCount = 0; gemNameField.removeAllItems(); filterEnumerationsCombo(""); gemNameField.setSelectedIndex(-1); gemNameField.setPopupVisible(true); } if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { lastNameTyped = name; } } }); gemNameField.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { // User selected an item from dropdown list; update list of enumeration values // Note: the dropdown is populated with only valid values boolean canSwitch = editingExistingEnumeration && !modifiedEnumeration; if (canSwitch) { // Was editing existing enumeration, so clear the fields ((DefaultListModel)enumListControl.getModel()).clear(); } if (!modifiedEnumeration) { editingExistingEnumeration = retrieveEnumerationValues(((JTextField)gemNameField.getEditor().getEditorComponent()).getText()); } updateAllStates(false); // Do not highlight text ((JTextField)gemNameField.getEditor().getEditorComponent()).select(((JTextField)gemNameField.getEditor().getEditorComponent()).getText().length(),0); } }); gemNameField.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { if (validEnumName) { // Valid name selected, so remove any suggestions for (int i = 0; i < suggestedNameCount; i++) { gemNameField.removeItemAt(0); } suggestedNameCount = 0; } } }); } gemNameField.setRenderer(new ComboBoxRenderer(allEnumerationNamesList)); return gemNameField; } /** * A type-safe enumeration of the combo box options for the derived instances field in the dialog box. * * @author Joseph Wong */ private static class DerivedInstancesOption { /** * The DerivedInstancesOption instance representing the option for generating only a derived Eq instance. */ private static final DerivedInstancesOption EQ_ONLY_DERIVED_INSTANCES_OPTION = new EnumeratedTypeGemGeneratorDialog.DerivedInstancesOption(new QualifiedName[] { CAL_Prelude.TypeClasses.Eq}); /** * The DerivedInstancesOption instance representing the option for generating the full complement of derived instances (i.e. Eq, Ord, Bounded, Enum). */ private static final DerivedInstancesOption EQ_ORD_BOUNDED_ENUM_DERIVED_INSTANCES_OPTION = new EnumeratedTypeGemGeneratorDialog.DerivedInstancesOption(new QualifiedName[] { CAL_Prelude.TypeClasses.Eq, CAL_Prelude.TypeClasses.Ord, CAL_Prelude.TypeClasses.Bounded, CAL_Prelude.TypeClasses.Enum }); /** * The string representation of the list of type class names to be displayed in the combo box. */ private final String stringRep; /** * Constructs a DerivedInstancesOption instance representing the specified list of type class names. * @param classNames the type class names represented by this option. */ private DerivedInstancesOption(QualifiedName[] classNames) { StringBuilder buf = new StringBuilder(classNames[0].getQualifiedName()); for (int i = 1; i < classNames.length; i++) { buf.append(", ").append(classNames[i].getQualifiedName()); } this.stringRep = buf.toString(); } /** * @return the string representation of this option to be display in the combo box. */ @Override public String toString() { return stringRep; } } /** * @return initialized derived instances combo box */ private JComboBox getDerivedInstancesField() { if (derivedInstancesField == null) { derivedInstancesField = new JComboBox(DERIVED_INSTANCES_OPTIONS); derivedInstancesField.setEditable(false); } return derivedInstancesField; } /** * Adapter object in charge of managing focus for the * enumeration value list. It deselects the list if focus * is lost to a component other than a control button. */ private FocusAdapter valueListDeselector = new FocusAdapter() { @Override public void focusLost(FocusEvent e) { if ((e.getOppositeComponent() != getOkButton()) && (e.getOppositeComponent() != getCancelButton()) && (e.getOppositeComponent() != getAddButton()) && (e.getOppositeComponent() != getRemoveButton()) && (e.getOppositeComponent() != getUpButton()) && (e.getOppositeComponent() != getDownButton()) && (e.getOppositeComponent() != getValueList())) { getValueList().clearSelection(); } } }; /** * @return initialized value list control */ private SwapList getValueList() { if (enumListControl == null) { enumListControl = new SwapList(new DefaultListModel()); enumListControl.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { // Shift items with ALT if (e.isAltDown()) { if (e.getKeyCode() == KeyEvent.VK_UP) { enumListControl.shiftUp(); } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { enumListControl.shiftDown(); } } updateAllStates(false); } }); enumListControl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); enumListControl.setVisibleRowCount(5); enumListControl.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { updateOtherButtonStates(); } }); enumListScrollPane = new JScrollPane(enumListControl); enumListControl.addFocusListener(valueListDeselector); // Create mouse adapter which, on drag, sets the modified flag MouseClickDragAdapter newMouseHandler = new MouseClickDragAdapter() { @Override public void mouseReallyDragged(MouseEvent e, Point where, boolean wasDragging) { if (enumListControl.getModel().getSize() > 0) { modifiedEnumeration = true; } }}; enumListControl.addMouseListener(newMouseHandler); enumListControl.addMouseMotionListener(newMouseHandler); } return enumListControl; } /** * @return the OK button for the dialog */ private JButton getOkButton() { if (okButton == null) { Action okAction = new AbstractAction(GeneratorMessages.getString("ETGF_OK")) { private static final long serialVersionUID = -5422279201196503598L; public void actionPerformed(ActionEvent e) { generateSource(); Preferences prefs = Preferences.userNodeForPackage(EnumeratedTypeGemGeneratorDialog.class); PreferencesHelper.putDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, EnumeratedTypeGemGeneratorDialog.this); dispose(); } }; okButton = new JButton(okAction); okButton.addFocusListener(valueListDeselector); okButton.setPreferredSize(getCancelButton().getPreferredSize()); okButton.setMnemonic(KeyEvent.VK_O); } return okButton; } /** * @return the cancel button for the dialog */ private JButton getCancelButton() { if (cancelButton == null) { Action cancelAction = new AbstractAction(GeneratorMessages.getString("ETGF_Cancel")) { private static final long serialVersionUID = 3129182448714213946L; public void actionPerformed(ActionEvent e) { Preferences prefs = Preferences.userNodeForPackage(EnumeratedTypeGemGeneratorDialog.class); PreferencesHelper.putDialogProperties(prefs, DIALOG_PROPERTIES_PREF_KEY, EnumeratedTypeGemGeneratorDialog.this); dispose(); } }; cancelButton = new JButton(cancelAction); cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.addFocusListener(valueListDeselector); } return cancelButton; } /** * @return the Add button for the dialog */ private JButton getAddButton() { if (addButton == null) { Action addAction = new AbstractAction(GeneratorMessages.getString("ETGF_Add")) { private static final long serialVersionUID = -4808960566839453171L; public void actionPerformed(ActionEvent e) { addSuggestedValueToList(); } }; addButton = new JButton(addAction); addButton.setMnemonic(KeyEvent.VK_A); addButton.addFocusListener(valueListDeselector); } return addButton; } /** * @return the REMOVE button for the dialog */ private JButton getRemoveButton() { if (removeButton == null) { Action removeAction = new AbstractAction(GeneratorMessages.getString("ETGF_Remove")) { private static final long serialVersionUID = -7713372366025001184L; public void actionPerformed(ActionEvent e) { enumListControl.removeSelected(); updateAllStates(false); modifiedEnumeration = true; } }; removeButton = new JButton(removeAction); removeButton.addFocusListener(valueListDeselector); removeButton.setMnemonic(KeyEvent.VK_R); } return removeButton; } /** * @return the DOWN button for the dialog */ private JButton getDownButton() { if (downButton == null) { Action downAction = new AbstractAction() { private static final long serialVersionUID = -8750386517784977237L; public void actionPerformed(ActionEvent e) { enumListControl.shiftDown(); modifiedEnumeration = true; } }; downButton = new JButton(downAction); downButton.setIcon(DOWN_ICON); downButton.setMargin(new Insets(0, 0, 0, 0)); downButton.addFocusListener(valueListDeselector); } return downButton; } /** * @return the UP button for the dialog */ private JButton getUpButton() { if (upButton == null) { Action upAction = new AbstractAction() { private static final long serialVersionUID = -6879585081593228032L; public void actionPerformed(ActionEvent e) { enumListControl.shiftUp(); modifiedEnumeration = true; } }; upButton = new JButton(upAction); upButton.setIcon(UP_ICON); upButton.setMargin(new Insets(0, 0, 0, 0)); upButton.addFocusListener(valueListDeselector); } return upButton; } /** * Filters the enumeration names in the current module to begin with * the typed prefix, and introduces them in the enumeration name combo box. * * The combo box is cleared of old items. */ private void filterEnumerationsCombo(String filterString) { if (filterString == null) { filterString = ""; } int ss = ((JTextField)gemNameField.getEditor().getEditorComponent()).getSelectionStart(); int se = ((JTextField)gemNameField.getEditor().getEditorComponent()).getSelectionEnd(); int cp = ((JTextField)gemNameField.getEditor().getEditorComponent()).getCaretPosition(); // Read non-suggested items into a list and filter as we go for (int i = 0; i < allEnumerationNamesList.size(); i++) { String item = allEnumerationNamesList.get(i); if (item.toUpperCase().startsWith( filterString.toUpperCase() )) { gemNameField.addItem(item); } } gemNameField.getEditor().setItem(filterString); ((JTextField)gemNameField.getEditor().getEditorComponent()).select(ss, se); ((JTextField)gemNameField.getEditor().getEditorComponent()).moveCaretPosition(cp); } /** * Generates the source definitions for the data type */ private void generateSource() { String gemName = ((JTextField)gemNameField.getEditor().getEditorComponent()).getText(); String genComment = GeneratorMessages.getString("ETGF_CALDeclComment") + gemName + "\n"; StringBuilder source = new StringBuilder(genComment); Scope visibility = (publicButton.getModel().isSelected()) ? Scope.PUBLIC : Scope.PRIVATE; boolean eqInstanceOnly = (derivedInstancesField.getSelectedItem() == DerivedInstancesOption.EQ_ONLY_DERIVED_INSTANCES_OPTION); DefaultListModel enumListModel = ((DefaultListModel)enumListControl.getModel()); String[] enumValues = new String[enumListModel.getSize()]; for (int i = 0, n = enumListModel.getSize(); i < n; i++) { enumValues[i] = (String)enumListModel.get(i); } SourceModelUtilities.Enumeration enumObject = new SourceModelUtilities.Enumeration(QualifiedName.make(perspective.getWorkingModuleName(), gemName), visibility, enumValues, eqInstanceOnly); TopLevelSourceElement[] sourceElements = enumObject.toSourceElements(); for (int i = 0, n = sourceElements.length; i < n; i++) { source.append(sourceElements[i].toSourceText()); source.append("\n"); if( !(sourceElements[i] instanceof FunctionTypeDeclaration) && i < n-1 ) { source.append("\n"); } } sourceDefinitions.put(gemName, source.toString()); } /** * @return the new source definitions that should be created */ public Map<String, String> getSourceDefinitions() { return sourceDefinitions; } /** * Populates the allEnumerationNamesList with * existing enumerations (types with arity 0) from the * current module. * */ private void populateEnumerations() { int numTypes = perspective.getWorkingModuleTypeInfo().getNTypeConstructors(); for (int i = 0; i < numTypes; i++) { TypeConstructor typeCons = perspective.getWorkingModuleTypeInfo().getNthTypeConstructor(i); try { if (perspective.isEnumDataType(typeCons.getName()) && perspective.getWorkspace().checkDefinitionContent(perspective.getWorkingModuleName(), typeCons.getName().getUnqualifiedName(), GeneratorMessages.getString("ETGF_CALDeclComment"))) { allEnumerationNamesList.add(typeCons.getName().getUnqualifiedName()); } } catch (IOException ex) { JOptionPane.showMessageDialog(this, GeneratorMessages.getString("ETGF_ErrorReadingEnums") + ex.getLocalizedMessage(), GeneratorMessages.getString("ETGF_PopulateFailed"), JOptionPane.ERROR_MESSAGE); } } Collections.sort(allEnumerationNamesList); } /** * If the specified enumeration exists, the dialog is populated * with the enumeration values and the derived instances settings. * * @param enumName enumeration name * @return true if specified enumeration exists; false if not */ private boolean retrieveEnumerationValues(String enumName) { ModuleTypeInfo workingModuleTypeInfo = perspective.getWorkingModuleTypeInfo(); TypeConstructor typeCons = workingModuleTypeInfo.getTypeConstructor(enumName); if (typeCons != null) { ((DefaultListModel)enumListControl.getModel()).clear(); if(typeCons.getScope() == Scope.PUBLIC) { publicButton.doClick(); } else { privateButton.doClick(); } int constructors = typeCons.getNDataConstructors(); for (int i = 0; i < constructors; i++) { QualifiedName constructorName = typeCons.getNthDataConstructor(i).getName(); ((DefaultListModel)enumListControl.getModel()).addElement(constructorName.getUnqualifiedName()); } // We need to determine whether the existing enumeration has only a derived Eq instance, // or the full complement of derived instances (i.e. Eq, Ord, Bounded, Enum), so that // the correct selection can be shown in the derived instances combo box. TypeClass ordTypeClass = workingModuleTypeInfo.getVisibleTypeClass(CAL_Prelude.TypeClasses.Ord); ClassInstance ordInstance = workingModuleTypeInfo.getVisibleClassInstance(ordTypeClass, typeCons); TypeClass boundedTypeClass = workingModuleTypeInfo.getVisibleTypeClass(CAL_Prelude.TypeClasses.Bounded); ClassInstance boundedInstance = workingModuleTypeInfo.getVisibleClassInstance(boundedTypeClass, typeCons); TypeClass enumTypeClass = workingModuleTypeInfo.getVisibleTypeClass(CAL_Prelude.TypeClasses.Enum); ClassInstance enumInstance = workingModuleTypeInfo.getVisibleClassInstance(enumTypeClass, typeCons); if (ordInstance == null && boundedInstance == null && enumInstance == null) { getDerivedInstancesField().setSelectedItem(DerivedInstancesOption.EQ_ONLY_DERIVED_INSTANCES_OPTION); } else { getDerivedInstancesField().setSelectedItem(DerivedInstancesOption.EQ_ORD_BOUNDED_ENUM_DERIVED_INSTANCES_OPTION); } return true; } else { return false; } } }