/*
* 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.
*/
/*
* NavEditorSection.java
* Creation date: Jul 9, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client.navigator;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import org.openquark.cal.compiler.CodeQualificationMap;
import org.openquark.cal.compiler.LanguageInfo;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous;
import org.openquark.cal.metadata.ArgumentMetadata;
import org.openquark.cal.metadata.CALExample;
import org.openquark.cal.metadata.CALExpression;
import org.openquark.cal.metadata.CALFeatureMetadata;
import org.openquark.cal.metadata.ClassMethodMetadata;
import org.openquark.cal.metadata.FunctionMetadata;
import org.openquark.cal.metadata.FunctionalAgentMetadata;
import org.openquark.cal.metadata.InstanceMethodMetadata;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.util.Pair;
import org.openquark.util.UnsafeCast;
/**
* This class implements the basis for a metadata editor section. An editor section
* is a component that contains one or more editor components that allow metadata to
* be edited. Editor sections can be expanded or collapsed using an expander widget
* at the top of the section. Editor sections for specific metadata attributes derive
* from this base class.
*
* @author Frank Worsley
*/
abstract class NavEditorSection extends JPanel {
/** The color to use as the background when a section has focus. */
static final Color FOCUS_COLOR = Color.BLUE.darker().darker();
/** The color to use as the background when a section has an error. */
static final Color ERROR_COLOR = Color.RED.darker();
/** The color to use for the background for the default state. */
static final Color NORMAL_COLOR = Color.LIGHT_GRAY;
/** The background color for the error labels. */
private static final Color ERROR_LABEL_COLOR = new Color(255, 255, 200);
/** The outside border for the error labels. */
private static final Border ERROR_LABEL_OUTSIDE_BORDER = BorderFactory.createLineBorder(Color.GRAY, 1);
/** The inside border for the error labels. */
private static final Border ERROR_LABEL_INSIDE_BORDER = BorderFactory.createEmptyBorder(1, 2, 2, 2);
/** The border for the error labels. */
private static final Border ERROR_LABEL_BORDER = BorderFactory.createCompoundBorder(ERROR_LABEL_OUTSIDE_BORDER, ERROR_LABEL_INSIDE_BORDER);
/** The icon for the error labels. */
private static final ImageIcon ERROR_LABEL_ICON = new ImageIcon(NavEditorSection.class.getResource("/Resources/error.gif"));
/**
* The map that stores the label used to display the title for an editor.
*/
private final Map<NavEditorComponent<?>, JLabel> editorToTitleMap = new HashMap<NavEditorComponent<?>, JLabel>();
/**
* The map that stores the label used to display error message for an editor.
*/
private final Map<NavEditorComponent<?>, JLabel> editorToErrorMap = new HashMap<NavEditorComponent<?>, JLabel>();
/**
* The map that stores the editors with unique keys used in this section.
*/
private final Map<String, NavEditorComponent<?>> keyToEditorMap = new HashMap<String, NavEditorComponent<?>>();
/**
* The list of editors in this section in the same order they were added.
*/
private final List<NavEditorComponent<?>> allEditors = new ArrayList<NavEditorComponent<?>>();
/** The panel that holds the various editing components. */
private final JPanel contentPanel = new JPanel();
/** The expander used by this section. */
private final NavExpander expander;
/** The title of the editor section. */
private final String title;
/** The editor panel that is using this editor section. */
private final NavEditorPanel editorPanel;
/** A label to display placeholder messages in empty sections. */
private final JLabel placeHolderLabel = new JLabel();
/** Whether or not this section has the focused look. */
private boolean hasFocus = false;
/** Whether or not this section has the error look. */
private boolean hasErrors = false;
/**
* Constructor for a new editor section.
* @param editorPanel the editor panel the section belongs to
* @param title the title of the section
*/
public NavEditorSection(NavEditorPanel editorPanel, String title) {
if (editorPanel == null) {
throw new NullPointerException();
}
this.editorPanel = editorPanel;
this.title = title;
setLayout(new BorderLayout());
expander = new NavExpander(this);
expander.setExpanded(false);
add(expander, BorderLayout.NORTH);
contentPanel.setBackground(Color.WHITE);
contentPanel.setLayout(new GridBagLayout());
contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(contentPanel, BorderLayout.CENTER);
setMaximumSize(new Dimension(5000, 1));
setFocusable(true);
setFocusedLook(false);
// Add a mouse listener that requests focus when the section is clicked.
contentPanel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
requestFocusInWindow();
}
});
// Add a key listener to expand/collapse the section
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
boolean expanded = expander.isExpanded();
setExpanded(!expanded);
}
}
});
}
/**
* @return the title of the editor section
*/
String getTitle() {
return title;
}
/**
* @return the editor panel that is using this editor section
*/
NavEditorPanel getEditorPanel() {
return editorPanel;
}
/**
* @return the content panel of this editor section
*/
JPanel getContentPanel() {
return contentPanel;
}
/**
* @return the expander used by this editor section
*/
NavExpander getExpander() {
return expander;
}
/**
* Convenience method to get the metadata object being edited.
* @return the metadata object being edited
*/
CALFeatureMetadata getMetadata() {
return getEditorPanel().getMetadata();
}
/**
* Convenience method to return the address of the entity whose metadata is being edited.
* @return the address of the entity whose metadata is being edited
*/
NavAddress getAddress() {
return getEditorPanel().getAddress();
}
/**
* @return the set of editors used in this section
*/
List<NavEditorComponent<?>> getEditors() {
return new ArrayList<NavEditorComponent<?>>(allEditors);
}
/**
* @param key the key of the editor
* @return the editor with the given key
*/
NavEditorComponent<?> getEditor(String key) {
if (!keyToEditorMap.containsKey(key)) {
throw new IllegalArgumentException("no editor with the given key: " + key);
}
return keyToEditorMap.get(key);
}
/**
* @param key the key of an editor.
* @return true if this section contains an editor with the given key.
*/
boolean containsEditor(String key) {
return keyToEditorMap.containsKey(key);
}
/**
* Adds a new editor component to the section.
* @param editor the editor component to add
*/
void addEditor(NavEditorComponent<?> editor) {
// Remove the placeholder label since there now is an actual
// editor to fill the section.
if (placeHolderLabel.getParent() != null) {
contentPanel.remove(placeHolderLabel);
}
allEditors.add(editor);
if (editor.getKey() != null) {
keyToEditorMap.put(editor.getKey(), editor);
}
// Setup the basic constraints
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.WEST;
constraints.gridx = 0;
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.insets = new Insets(5, 5, 2, 5);
// Add the error label for the editor
JLabel errorLabel = new JLabel();
errorLabel.setBackground(ERROR_LABEL_COLOR);
errorLabel.setBorder(ERROR_LABEL_BORDER);
errorLabel.setIcon(ERROR_LABEL_ICON);
errorLabel.setOpaque(true);
errorLabel.setVisible(false);
editorToErrorMap.put(editor, errorLabel);
contentPanel.add(errorLabel, constraints);
constraints.gridx = 0;
constraints.gridwidth = 1;
constraints.insets = new Insets(5, 5, 5, 5);
// Only add a title label if the editor has a title
if (editor.getTitle() != null) {
JLabel titleLabel = new JLabel(editor.getTitle());
titleLabel.setToolTipText(editor.getDescription());
contentPanel.add(titleLabel, constraints);
editorToTitleMap.put(editor, titleLabel);
// Update the constraints to take into account the title label
constraints.gridx = 1;
}
// Add the actual editor
constraints.weightx = 1;
contentPanel.add(editor.getEditorComponent(), constraints);
// For some reason we need to validate the editor panel's parent if
// we add or remove components after the section is visible. Simply
// validating the section or editor panel is not enough.
if (getEditorPanel().getParent() != null) {
getEditorPanel().getParent().validate();
}
}
/**
* Removes the given editor from the section.
* @param editor the editor to remove
*/
void removeEditor(NavEditorComponent<?> editor) {
// remove editor from the editor set
allEditors.remove(editor);
if (editor.getKey() != null) {
keyToEditorMap.remove(editor.getKey());
}
// remove editor and error + title components
contentPanel.remove(editor.getEditorComponent());
contentPanel.remove(editorToErrorMap.remove(editor));
if (editor.getTitle() != null) {
contentPanel.remove(editorToTitleMap.remove(editor));
}
maybeAddPlaceHolderLabel();
if (getEditorPanel().getParent() != null) {
getEditorPanel().getParent().validate();
}
}
/**
* Removes all editors from the section.
*/
void removeAllEditors() {
contentPanel.removeAll();
allEditors.clear();
keyToEditorMap.clear();
maybeAddPlaceHolderLabel();
if (getEditorPanel().getParent() != null) {
getEditorPanel().getParent().validate();
}
}
/**
* Called when the value stored by the given editor has changed.
* @param editor the editor whose value changed
*/
void editorChanged(NavEditorComponent<?> editor) {
editorPanel.sectionChanged(this);
doValidate();
}
/**
* @param key the key of the editor whose value to fetch
* @return the value stored by the editor component. This may be null if the
* editor is storing a null value.
*/
Object getEditorValue(String key) {
NavEditorComponent<?> editor = keyToEditorMap.get(key);
if (editor == null) {
throw new IllegalArgumentException("no editor with the given key");
}
return editor.getValue();
}
/**
* Sets the value of the editor with the given key.
* @param key the key of the editor
* @param value the new value for the editor
*/
<T> void setEditorValue(String key, Object value) {
NavEditorComponent<T> editor = UnsafeCast.unsafeCast(keyToEditorMap.get(key));
if (editor == null) {
throw new IllegalArgumentException("no editor with the given key");
}
editor.setValue(UnsafeCast.<T>unsafeCast(value)); // ~ unsafe
}
/**
* Sets the focused look of a section. If the section is focused its border
* and expander appear in blue, otherwise in light gray.
* @param focused true if the section should look as if it is focused
*/
void setFocusedLook(boolean focused) {
if (focused && !hasErrors) {
// The error look overrides the focus look
expander.setFocusedLook();
setBorder(BorderFactory.createLineBorder(FOCUS_COLOR, 2));
} else if (!focused && !hasErrors) {
// Only go back to normal look if we don't have errors
expander.setNormalLook();
setBorder(BorderFactory.createLineBorder(NORMAL_COLOR, 2));
}
this.hasFocus = focused;
}
/**
* Sets an error message to be displayed in the section header. Setting an error message
* causes the outline of the section to be displayed in red. To clear a previous message
* and restore the normal state pass in null for the message.
* @param message the error message to display
*/
void setErrorMessage(String message) {
if (message != null) {
// Error look overrides all other looks
expander.setErrorLook(message);
setBorder(BorderFactory.createLineBorder(ERROR_COLOR, 2));
} else if (message == null && hasFocus) {
// Set to focus look if the section has focus
expander.setFocusedLook();
setBorder(BorderFactory.createLineBorder(FOCUS_COLOR, 2));
} else {
// If no focus and no error reset to normal look
expander.setNormalLook();
setBorder(BorderFactory.createLineBorder(NORMAL_COLOR, 2));
}
this.hasErrors = (message != null);
}
/**
* Sets an error message for the given editor. Use null to clear the error message.
* @param editor the editor to set the error message for
* @param message the error message to use
*/
void setEditorErrorMessage(NavEditorComponent<?> editor, String message) {
JLabel errorLabel = editorToErrorMap.get(editor);
errorLabel.setText(message);
errorLabel.setVisible(message != null);
JLabel titleLabel = editorToTitleMap.get(editor);
if (titleLabel != null) {
titleLabel.setForeground(message != null ? Color.RED : Color.BLACK);
}
if (getEditorPanel().getParent() != null) {
getEditorPanel().getParent().validate();
}
}
/**
* If the section is empty and does not contain any editor this method can be used
* to set a placeholder message to be displayed in the empty section. The message is
* automatically displayed whenever there are no editors in the section.
* @param message the message to be displayed
*/
void setPlaceHolderMessage(String message) {
placeHolderLabel.setText(message);
maybeAddPlaceHolderLabel();
}
/**
* Adds the placeholder label to the section if no editors are in the section.
*/
private void maybeAddPlaceHolderLabel() {
if (contentPanel.getComponentCount() == 0) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(15, 5, 15, 5);
constraints.gridx = 0;
contentPanel.add(placeHolderLabel, constraints);
if (getEditorPanel().getParent() != null) {
getEditorPanel().getParent().validate();
}
}
}
/**
* @param expanded whether or not the section should be expanded
*/
void setExpanded(boolean expanded) {
expander.setExpanded(expanded);
}
/**
* Validates the metadata entered by the user. If any editor in the section contains
* invalid data this should return false and set the appropriate error message for the
* editors that contains invalid data.
* @return true if data is valid, false if there is invalid data
*/
abstract boolean doValidate();
/**
* Saves the values stored by the editor components back into the metadata.
*/
abstract void doSave();
/**
* Reverts the values stored in the editor components to the values currently
* stored in the metadata object being edited.
*/
abstract void doRevert();
}
/**
* An editor section for editing the basic CAL feature metadata.
* @author Frank Worsley
*/
class NavFeatureEditorSection extends NavEditorSection {
private static final long serialVersionUID = 3173779274560768632L;
/* Keys for the editors. */
private static final String DISPLAY_NAME_KEY = "displayName";
private static final String SHORT_DESCRIPTION_KEY = "shortDescription";
private static final String LONG_DESCRIPTION_KEY = "longDescription";
private static final String AUTHOR_KEY = "author";
private static final String VERSION_KEY = "version";
private static final String PREFERRED_KEY = "preferred";
private static final String EXPERT_KEY = "expert";
private static final String RELATED_FEATURES_KEY = "relatedFeatures";
/**
* Constructor for a new feature metadata section.
* @param editorPanel the editor panel the section belongs to
*/
public NavFeatureEditorSection(NavEditorPanel editorPanel) {
super(editorPanel, NavigatorMessages.getString("NAV_BasicProperties_Header"));
// Add editor components for the basic feature metadata items
addEditor(new NavTextFieldEditor(this, DISPLAY_NAME_KEY, NavigatorMessages.getString("NAV_DisplayName"), NavigatorMessages.getString("NAV_DisplayNameDescription")));
addEditor(new NavTextFieldEditor(this, SHORT_DESCRIPTION_KEY, NavigatorMessages.getString("NAV_ShortDescription"), NavigatorMessages.getString("NAV_ShortDescriptionDescription")));
addEditor(new NavTextAreaEditor (this, LONG_DESCRIPTION_KEY, NavigatorMessages.getString("NAV_LongDescription"), NavigatorMessages.getString("NAV_LongDescriptionDescription")));
addEditor(new NavTextFieldEditor(this, AUTHOR_KEY, NavigatorMessages.getString("NAV_Author"), NavigatorMessages.getString("NAV_AuthorDescription")));
addEditor(new NavTextFieldEditor(this, VERSION_KEY, NavigatorMessages.getString("NAV_Version"), NavigatorMessages.getString("NAV_VersionDescription")));
addEditor(new NavBooleanEditor (this, PREFERRED_KEY, NavigatorMessages.getString("NAV_Preferred"), NavigatorMessages.getString("NAV_PreferredDescription")));
addEditor(new NavBooleanEditor (this, EXPERT_KEY, NavigatorMessages.getString("NAV_Expert"), NavigatorMessages.getString("NAV_ExpertDescription")));
addEditor(new NavFeaturesEditor (this, RELATED_FEATURES_KEY, NavigatorMessages.getString("NAV_RelatedFeatures"), NavigatorMessages.getString("NAV_RelatedFeaturesDescription")));
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doValidate()
*/
@Override
boolean doValidate() {
// For arguments the display name has to be a valid CAL identifier.
if (getMetadata() instanceof ArgumentMetadata) {
NavEditorComponent<String> editor = UnsafeCast.unsafeCast(getEditor(DISPLAY_NAME_KEY));
String displayName = editor.getValue();
if (displayName != null && !LanguageInfo.isValidFunctionName(displayName)) {
//setErrorMessage("You have entered invalid values. Your changes have not been saved.");
setErrorMessage("");
setEditorErrorMessage(editor, NavigatorMessages.getString("NAV_InvalidArgName_Message"));
return false;
} else {
setErrorMessage(null);
setEditorErrorMessage(editor, null);
}
}
return true;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doSave()
*/
@Override
void doSave() {
CALFeatureMetadata metadata = getMetadata();
metadata.setDisplayName((String) getEditorValue(DISPLAY_NAME_KEY));
metadata.setShortDescription((String) getEditorValue(SHORT_DESCRIPTION_KEY));
metadata.setLongDescription((String) getEditorValue(LONG_DESCRIPTION_KEY));
metadata.setAuthor((String) getEditorValue(AUTHOR_KEY));
metadata.setVersion((String) getEditorValue(VERSION_KEY));
Boolean preferred = (Boolean) getEditorValue(PREFERRED_KEY);
metadata.setPreferred(preferred.booleanValue());
Boolean expert = (Boolean) getEditorValue(EXPERT_KEY);
metadata.setExpert(expert.booleanValue());
metadata.clearRelatedFeatures();
List<CALFeatureName> relatedFeatures = UnsafeCast.unsafeCast(getEditorValue(RELATED_FEATURES_KEY)); // ~ unsafe
for (final CALFeatureName calFeatureName : relatedFeatures) {
metadata.addRelatedFeature(calFeatureName);
}
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doRevert()
*/
@Override
void doRevert() {
CALFeatureMetadata metadata = getMetadata();
setEditorValue(DISPLAY_NAME_KEY, metadata.getDisplayName());
setEditorValue(SHORT_DESCRIPTION_KEY, metadata.getShortDescription());
setEditorValue(LONG_DESCRIPTION_KEY, metadata.getLongDescription());
setEditorValue(AUTHOR_KEY, metadata.getAuthor());
setEditorValue(VERSION_KEY, metadata.getVersion());
setEditorValue(PREFERRED_KEY, Boolean.valueOf(metadata.isPreferred()));
setEditorValue(EXPERT_KEY, Boolean.valueOf(metadata.isExpert()));
List<CALFeatureName> relatedFeatures = new ArrayList<CALFeatureName>();
int count = metadata.getNRelatedFeatures();
for (int i = 0; i < count; i++) {
relatedFeatures.add(metadata.getNthRelatedFeature(i));
}
setEditorValue(RELATED_FEATURES_KEY, relatedFeatures);
}
}
/**
* An editor section for editing gem entity metadata.
* @author Frank Worsley
*/
class NavGemEntityEditorSection extends NavEditorSection {
private static final long serialVersionUID = 1812523907521402162L;
/* Keys for the editors. */
private static final String CATEGORIES_KEY = "categories";
/**
* Constructor for a new feature metadata section.
* @param editorPanel the editor panel the section belongs to
*/
public NavGemEntityEditorSection(NavEditorPanel editorPanel) {
super(editorPanel, NavigatorMessages.getString("NAV_GemProperties_Header"));
addEditor(new NavListEditor(this, CATEGORIES_KEY, NavigatorMessages.getString("NAV_Categories"), NavigatorMessages.getString("NAV_CategoriesDescription")));
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doValidate()
*/
@Override
boolean doValidate() {
return true;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doSave()
*/
@Override
void doSave() {
List<String> categories = UnsafeCast.unsafeCast(getEditorValue(CATEGORIES_KEY)); // ~ unsafe
String[] catArray = new String[categories.size()];
for (int i = 0; i < catArray.length; i++) {
catArray[i] = categories.get(i);
}
setCategoriesIntoMetadata(catArray);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doRevert()
*/
@Override
void doRevert() {
List<String> categories = Arrays.asList(getCategoriesFromMetadata());
setEditorValue(CATEGORIES_KEY, categories);
}
/** Set the specified categories into the metadata. */
private void setCategoriesIntoMetadata(String[] catArray) {
if (getMetadata() instanceof InstanceMethodMetadata) {
((InstanceMethodMetadata)getMetadata()).setCategories(catArray);
} else {
((FunctionalAgentMetadata)getMetadata()).setCategories(catArray);
}
}
/** Get the categories from the metadata. */
private String[] getCategoriesFromMetadata() {
if (getMetadata() instanceof InstanceMethodMetadata) {
return ((InstanceMethodMetadata)getMetadata()).getCategories();
} else {
return ((FunctionalAgentMetadata)getMetadata()).getCategories();
}
}
}
/**
* A base class for a section that may contain zero or more editors. This base
* class provides handling for the adding and removing of editors.
*
* @author Joseph Wong
*/
abstract class NavMultiEditorSection extends NavEditorSection {
/** The icon for the new button. */
private static final ImageIcon newIcon = new ImageIcon(NavMultiEditorSection.class.getResource("/Resources/new.gif"));
/** The button for adding new editors. */
private final JButton addButton;
/**
* Constructor for a new multi-editor section.
* @param editorPanel the editor panel this section belongs to
* @param title the editor's title
* @param addButtonToolTipText
*/
public NavMultiEditorSection (NavEditorPanel editorPanel, String title, String addButtonToolTipText) {
super(editorPanel, title);
// Add the 'add editor' button to the expander
NavExpander expander = getExpander();
addButton = new JButton(newIcon);
addButton.setMargin(new Insets(0, 0, 0, 0));
addButton.setToolTipText(addButtonToolTipText);
expander.addButton(addButton);
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
NavEditorComponent<?> editor = makeNewEditor();
addEditor(editor);
setExpanded(true);
editor.getEditorComponent().requestFocusInWindow();
}
});
}
/** Creates a new editor component to be added to the section. */
abstract NavEditorComponent<?> makeNewEditor();
/**
* Override this to indicate the section has changed if an
* example editor is removed.
*/
@Override
void removeEditor(NavEditorComponent<?> editor) {
// figure out the index of this editor, so we can focus the adjacent one
// after it is removed from the section
int index = getEditors().indexOf(editor);
editorChanged(editor);
super.removeEditor(editor);
if (getEditors().size() == 0) {
addButton.requestFocusInWindow();
} else if (index == 0) {
getEditors().get(0).getEditorComponent().requestFocusInWindow();
} else {
getEditors().get(index - 1).getEditorComponent().requestFocusInWindow();
}
}
/**
* Override this to indicate the section has changed if an
* example editor is added.
*/
@Override
void addEditor(NavEditorComponent<?> editor) {
editorChanged(editor);
super.addEditor(editor);
}
}
/**
* An editor section for editing metadata examples.
* @author Frank Worsley
*/
class NavExampleEditorSection extends NavMultiEditorSection {
private static final long serialVersionUID = -6958553476841209895L;
/**
* Constructor for a new example editor section.
* @param editorPanel the editor panel this section belongs to
*/
public NavExampleEditorSection (NavEditorPanel editorPanel) {
super(editorPanel, NavigatorMessages.getString("NAV_UsageExamples_Header"), NavigatorMessages.getString("NAV_AddExampleButtonToolTip"));
// Set the placeholder message we would like to appear
setPlaceHolderMessage(NavigatorMessages.getString("NAV_AddExample_Message"));
}
/**
* {@inheritDoc}
*/
@Override
NavExampleEditor makeNewEditor() {
// Create a default expression and example
ModuleName moduleName = getEditorPanel().getNavigatorOwner().getPerspective().getWorkingModuleName();
CALExpression expression = new CALExpression(moduleName, "", new CodeQualificationMap(), "");
CALExample example = new CALExample(expression, "", true);
return new NavExampleEditor(this, example);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doValidate()
*/
@Override
boolean doValidate() {
return true;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doSave()
*/
@Override
void doSave() {
CALExample[] examples = new CALExample[getEditors().size()];
int i = 0;
for (final NavEditorComponent<?> editor : getEditors()) {
examples[i] = (CALExample) editor.getValue();
i++;
}
setExamplesIntoMetadata(examples);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doRevert()
*/
@Override
void doRevert() {
removeAllEditors();
CALExample[] examples = getExamplesFromMetadata();
for (final CALExample element : examples) {
addEditor(new NavExampleEditor(this, element));
}
}
/** Set the specified examples into the metadata. */
private void setExamplesIntoMetadata(CALExample[] examples) {
if (getMetadata() instanceof InstanceMethodMetadata) {
((InstanceMethodMetadata)getMetadata()).setExamples(examples);
} else {
((FunctionalAgentMetadata)getMetadata()).setExamples(examples);
}
}
/** Get the examples from the metadata. */
private CALExample[] getExamplesFromMetadata() {
if (getMetadata() instanceof InstanceMethodMetadata) {
return ((InstanceMethodMetadata)getMetadata()).getExamples();
} else {
return ((FunctionalAgentMetadata)getMetadata()).getExamples();
}
}
}
/**
* An editor section for editing custom metadata attributes.
* @author Joseph Wong
*/
class NavCustomAttributeEditorSection extends NavMultiEditorSection {
private static final long serialVersionUID = -1424974125556259742L;
/**
* Constructor for a new custom attribute editor section.
* @param editorPanel the editor panel this section belongs to
*/
public NavCustomAttributeEditorSection(NavEditorPanel editorPanel) {
super(editorPanel, NavigatorMessages.getString("NAV_CustomAttributes_Header"), NavigatorMessages.getString("NAV_AddCustomAttributeButtonToolTip"));
// Set the placeholder message we would like to appear
setPlaceHolderMessage(NavigatorMessages.getString("NAV_AddCustomAttribute_Message"));
}
/**
* {@inheritDoc}
*/
@Override
NavEditorComponent<Pair<String, String>> makeNewEditor() {
return new NavCustomAttributeEditor(this, "", "");
}
/**
* {@inheritDoc}
*/
@Override
boolean doValidate() {
Set<String> attributeNames = new HashSet<String>();
for (final NavEditorComponent<?> editor : getEditors()) {
Pair<String, String> entry = UnsafeCast.unsafeCast(editor.getValue());
if (attributeNames.contains(entry.fst())) {
return false;
} else {
attributeNames.add(entry.fst());
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
void doSave() {
// clear the existing custom attributes in the metadata object
getMetadata().clearAttributes();
// add (back) the attributes that are encapsulated by editors
for (final NavEditorComponent<?> editor : getEditors()) {
Pair<String, String> entry = UnsafeCast.unsafeCast(editor.getValue());
String attributeName = entry.fst();
String attributeValue = entry.snd();
if (attributeName != null) {
getMetadata().setAttribute(attributeName, attributeValue);
}
}
}
/**
* {@inheritDoc}
*/
@Override
void doRevert() {
// clear the existing editors
removeAllEditors();
// add the editors representing custom attributes in the metadata object
for (Iterator<String> it = getMetadata().getAttributeNames(); it.hasNext(); ) {
String attributeName = it.next();
String attributeValue = getMetadata().getAttribute(attributeName);
addEditor(new NavCustomAttributeEditor(this, attributeName, attributeValue));
}
}
}
/**
* An editor section for editing entity arguments. This section lists
* each argument along with its name, type and an edit button. Clicking the
* edit button will actually edit the metadata for the argument.
* @author Frank Worsley
*/
class NavEntityArgumentEditorSection extends NavEditorSection {
private static final long serialVersionUID = 7241341746779529968L;
/** The key for the return value description editor. */
private static final String RETURN_VALUE_KEY = "returnValue";
/** Whether the entity has a return value description. */
private final boolean hasReturnValue;
/**
* Constructs a new argument editor section.
* @param editorPanel the editor panel this section belongs to
* @param hasReturnValue whether the entity has a return value description.
*/
public NavEntityArgumentEditorSection(NavEditorPanel editorPanel, boolean hasReturnValue) {
super(editorPanel, NavigatorMessages.getString(hasReturnValue ? "NAV_GemArgumentsAndReturnValue_Header" : "NAV_GemArguments_Header"));
setPlaceHolderMessage(NavigatorMessages.getString("NAV_NoArgumentsPlaceholder_Message"));
this.hasReturnValue = hasReturnValue;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doValidate()
*/
@Override
boolean doValidate() {
// For arguments the display name has to be a valid CAL identifier.
for (final NavEditorComponent<?> listItem : getEditors()) {
if (listItem instanceof NavEntityArgumentEditor) {
NavEntityArgumentEditor editor = (NavEntityArgumentEditor) listItem;
ArgumentMetadata metadata = editor.getValue();
if (metadata.getDisplayName() != null &&
!LanguageInfo.isValidFunctionName(metadata.getDisplayName())) {
setEditorErrorMessage(editor, NavigatorMessages.getString("NAV_InvalidArgName_Message"));
return false;
} else {
setEditorErrorMessage(editor, null);
}
} else if (listItem instanceof NavEntityReturnValueEditor) {
// no need to validate the return value description
}
}
return true;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doSave()
*/
@Override
void doSave() {
List<ArgumentMetadata> argMetadata = new ArrayList<ArgumentMetadata>();
String returnValueDesc = null;
for (final Object editor : getEditors()) { // NOTE: If listItem type is NavEditorComponent<?>, javac doesn't like the instanceof tests.
if (editor instanceof NavEntityArgumentEditor) {
argMetadata.add(((NavEntityArgumentEditor)editor).getValue());
} else if (editor instanceof NavEntityReturnValueEditor) {
returnValueDesc = ((NavEntityReturnValueEditor)editor).getValue();
}
}
setArgumentsIntoMetadata(argMetadata.toArray(new ArgumentMetadata[argMetadata.size()]));
setReturnValueDescriptionIntoMetadata(returnValueDesc);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doRevert()
*/
@Override
void doRevert() {
// We want to use the unqualified names if possible
NavFrameOwner owner = getEditorPanel().getNavigatorOwner();
ModuleTypeInfo moduleInfo = owner.getPerspective().getWorkingModuleTypeInfo();
ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(moduleInfo);
// Determine the information needed to display argument information
ArgumentMetadata[] arguments = getArgumentsFromMetadata();
ArgumentMetadata[] adjusted = getArgumentsFromMetadata();
String[] typeStrings = NavAddressHelper.getTypeStrings(owner, getAddress(), namingPolicy);
String returnValueDesc = getReturnValueDescriptionFromMetadata();
NavAddressHelper.adjustArgumentNames(owner, getAddress(), adjusted);
// first, remove the return value editor so that the remainder editors should all be argument editors
if (containsEditor(RETURN_VALUE_KEY)) {
removeEditor(getEditor(RETURN_VALUE_KEY));
}
List<NavEditorComponent<?>> allEditors = getEditors();
boolean haveEditors = allEditors.size() > 0;
int nNeededArgumentEditors = arguments.length;
for (int i = 0; i < nNeededArgumentEditors; i++) {
if (haveEditors && i < allEditors.size()) {
// update the existing editor
Object listItem = allEditors.get(i);
if (listItem instanceof NavEntityArgumentEditor) {
NavEntityArgumentEditor editor = (NavEntityArgumentEditor) listItem;
editor.setValue(arguments[i]);
editor.setAdjustedName(adjusted[i].getDisplayName());
editor.setType(typeStrings[i]);
} else if (listItem instanceof NavEntityReturnValueEditor) {
throw new IllegalStateException("There should not be any return value editors remaining in this section now.");
}
} else {
// add a new editor
addEditor(new NavEntityArgumentEditor(this, i, arguments[i], typeStrings[i], adjusted[i].getDisplayName()));
}
}
// remove old editors that we don't need anymore
while (nNeededArgumentEditors < getEditors().size()) {
removeEditor(getEditors().get(getEditors().size() - 1));
}
// finally, add back the return value editor if necessary
if (hasReturnValue) {
addEditor(new NavEntityReturnValueEditor(this, RETURN_VALUE_KEY, returnValueDesc, typeStrings[typeStrings.length - 1]));
}
}
/** Set the specified arguments into the metadata. */
private void setArgumentsIntoMetadata(ArgumentMetadata[] argMetadata) {
if (getMetadata() instanceof InstanceMethodMetadata) {
((InstanceMethodMetadata)getMetadata()).setArguments(argMetadata);
} else {
((FunctionalAgentMetadata)getMetadata()).setArguments(argMetadata);
}
}
/** Get the arguments from the metadata. */
private ArgumentMetadata[] getArgumentsFromMetadata() {
if (getMetadata() instanceof InstanceMethodMetadata) {
return ((InstanceMethodMetadata)getMetadata()).getArguments();
} else {
return ((FunctionalAgentMetadata)getMetadata()).getArguments();
}
}
/** Set the specified return value description into the metadata. */
private void setReturnValueDescriptionIntoMetadata(String returnValueDesc) {
CALFeatureMetadata metadata = getMetadata();
if (metadata instanceof FunctionMetadata) {
((FunctionMetadata)metadata).setReturnValueDescription(returnValueDesc);
} else if (metadata instanceof ClassMethodMetadata) {
((ClassMethodMetadata)metadata).setReturnValueDescription(returnValueDesc);
} else if (metadata instanceof InstanceMethodMetadata) {
((InstanceMethodMetadata)metadata).setReturnValueDescription(returnValueDesc);
}
}
/** Get the return value description from the metadata. */
private String getReturnValueDescriptionFromMetadata() {
CALFeatureMetadata metadata = getMetadata();
if (metadata instanceof FunctionMetadata) {
return ((FunctionMetadata)metadata).getReturnValueDescription();
} else if (metadata instanceof ClassMethodMetadata) {
return ((ClassMethodMetadata)metadata).getReturnValueDescription();
} else if (metadata instanceof InstanceMethodMetadata) {
return ((InstanceMethodMetadata)metadata).getReturnValueDescription();
} else {
return null;
}
}
}
/**
* An editor section for editing argument specific metadata.
* This section actually edits the metadata associated with an argument.
* @author Frank Worsley
*/
class NavArgumentEditorSection extends NavEditorSection {
private static final long serialVersionUID = 4289470583217943611L;
/* Keys for the editors. */
private static final String DEFAULTS_ONLY_KEY = "defaultsOnly";
private static final String DEFAULTS_EXPRESSION_KEY = "defaultsExpression";
private static final String PROMPTING_EXPRESSION_KEY = "promptingExpression";
/**
* Constructs a new argument editor section.
* @param editorPanel the editor panel this section belongs to.
*/
public NavArgumentEditorSection(NavEditorPanel editorPanel) {
super(editorPanel, NavigatorMessages.getString("NAV_ArgumentProperties_Header"));
addEditor(new NavBooleanEditor (this, DEFAULTS_ONLY_KEY, NavigatorMessages.getString("NAV_DefaultsOnly"), NavigatorMessages.getString("NAV_DefaultsOnlyDescription")));
addEditor(new NavExpressionEditor(this, DEFAULTS_EXPRESSION_KEY, NavigatorMessages.getString("NAV_DefaultsExpression"), NavigatorMessages.getString("NAV_DefaultsExpressionDescription")));
addEditor(new NavExpressionEditor(this, PROMPTING_EXPRESSION_KEY, NavigatorMessages.getString("NAV_PromptingExpression"), NavigatorMessages.getString("NAV_PromptingExpressionDescription")));
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doValidate()
*/
@Override
boolean doValidate() {
return true;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doSave()
*/
@Override
void doSave() {
ArgumentMetadata metadata = (ArgumentMetadata) getMetadata();
metadata.setDefaultValuesOnly(((Boolean) getEditorValue(DEFAULTS_ONLY_KEY)).booleanValue());
metadata.setDefaultValuesExpression((CALExpression) getEditorValue(DEFAULTS_EXPRESSION_KEY));
metadata.setPromptingTextExpression((CALExpression) getEditorValue(PROMPTING_EXPRESSION_KEY));
}
/**
* @see org.openquark.gems.client.navigator.NavEditorSection#doRevert()
*/
@Override
void doRevert() {
ArgumentMetadata metadata = (ArgumentMetadata) getMetadata();
setEditorValue(DEFAULTS_ONLY_KEY, Boolean.valueOf(metadata.useDefaultValuesOnly()));
setEditorValue(DEFAULTS_EXPRESSION_KEY, metadata.getDefaultValuesExpression());
setEditorValue(PROMPTING_EXPRESSION_KEY, metadata.getPromptingTextExpression());
}
}
/**
* This class implements the expander component displayed at the top of editor sections.
* @author Frank Worsley
*/
class NavExpander extends JPanel {
private static final long serialVersionUID = 5726979778057766101L;
/** The collapsed disclosure icon. */
private static final ImageIcon collapsedIcon = new ImageIcon(NavExpander.class.getResource("/Resources/disclosure_shut.gif"));
/** The expanded disclosure icon. */
private static final ImageIcon expandedIcon = new ImageIcon(NavExpander.class.getResource("/Resources/disclosure_open.gif"));
/** The label that expands/collapses the expander. */
private final JLabel expanderLabel;
/** A label that displays status messages for the section. */
private final JLabel messageLabel;
/** The editor section this expander is for. */
private final NavEditorSection editorSection;
/** Whether or not this expanded is expanded. */
private boolean expanded;
/**
* Constructor for a new expander.
* @param editorSection the editor section this expander is for
*/
public NavExpander(final NavEditorSection editorSection) {
if (editorSection == null) {
throw new NullPointerException();
}
this.editorSection = editorSection;
setFocusable(false);
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
expanderLabel = new JLabel(editorSection.getTitle(), collapsedIcon, SwingConstants.LEFT);
expanderLabel.setFont(getFont().deriveFont(Font.BOLD));
add(expanderLabel);
messageLabel = new JLabel();
add(Box.createHorizontalStrut(20));
add(messageLabel);
add(Box.createHorizontalGlue());
// Add a click listener to the expander and its message labels to
// expand or collapse the section if a mouse click occurs
MouseAdapter expanderListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
setExpanded(!isExpanded());
editorSection.requestFocusInWindow();
}
};
addMouseListener(expanderListener);
expanderLabel.addMouseListener(expanderListener);
messageLabel.addMouseListener(expanderListener);
}
/**
* Adds a new button to the expander bar.
* @param button the button to add
*/
void addButton(JButton button) {
add(Box.createHorizontalStrut(5));
add(button);
}
/**
* Sets whether or not this expander is expanded.
* @param expanded true to be expanded, false otherwise
*/
public void setExpanded(boolean expanded) {
this.expanded = expanded;
expanderLabel.setIcon(expanded ? expandedIcon : collapsedIcon);
editorSection.getContentPanel().setVisible(expanded);
if (expanded) {
// show as much of the section as possible while still showing the title
editorSection.scrollRectToVisible(editorSection.getBounds());
scrollRectToVisible(getBounds());
expanderLabel.setToolTipText(NavigatorMessages.getString("NAV_ExpanderExpandedToolTip"));
} else {
scrollRectToVisible(getBounds());
expanderLabel.setToolTipText(NavigatorMessages.getString("NAV_ExpanderCollapsedToolTip"));
}
}
/**
* @return whether or not this expander is expanded
*/
public boolean isExpanded() {
return expanded;
}
/**
* Sets a message to be displayed in the expander.
* @param message
*/
void setMessage(String message) {
messageLabel.setText(message);
}
/**
* Sets the expander to its focused look.
*/
void setFocusedLook() {
setBorder(BorderFactory.createLineBorder(NavEditorSection.FOCUS_COLOR, 2));
setBackground(NavEditorSection.FOCUS_COLOR);
setForeground(Color.WHITE);
expanderLabel.setForeground(Color.WHITE);
messageLabel.setForeground(Color.WHITE);
messageLabel.setText("");
repaint();
}
/**
* Sets the expander to its normal look.
*/
void setNormalLook() {
setBorder(BorderFactory.createLineBorder(NavEditorSection.NORMAL_COLOR, 2));
setBackground(NavEditorSection.NORMAL_COLOR);
setForeground(Color.BLACK);
expanderLabel.setForeground(Color.BLACK);
messageLabel.setForeground(Color.BLACK);
messageLabel.setText("");
repaint();
}
/**
* Sets the expander to its error look with the given error message displayed.
* To clear the error look and message pass in null for the message.
* @param message the error message to display
*/
void setErrorLook(String message) {
setBorder(BorderFactory.createLineBorder(NavEditorSection.ERROR_COLOR, 2));
setBackground(NavEditorSection.ERROR_COLOR);
setForeground(Color.WHITE);
expanderLabel.setForeground(Color.WHITE);
messageLabel.setForeground(Color.WHITE);
messageLabel.setText(message);
repaint();
}
}