/*
* 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.
*/
/*
* NavEditorComponent.java
* Creation date: Jul 22, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client.navigator;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TooManyListenersException;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.openquark.cal.compiler.AdjunctSource;
import org.openquark.cal.compiler.CodeAnalyser;
import org.openquark.cal.compiler.CodeQualificationMap;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.SourceIdentifier;
import org.openquark.cal.compiler.SourceModel;
import org.openquark.cal.compiler.SourceModelUtilities;
import org.openquark.cal.compiler.TypeExpr;
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.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.MetaModule;
import org.openquark.cal.services.Perspective;
import org.openquark.cal.valuenode.Target;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.cal.valuenode.TargetRunner.ProgramCompileException;
import org.openquark.gems.client.AutoCompleteManager;
import org.openquark.gems.client.AutoCompletePopupMenu;
import org.openquark.gems.client.CodeGemEditor;
import org.openquark.gems.client.GemCodeSyntaxListener;
import org.openquark.gems.client.QualificationPanel;
import org.openquark.gems.client.QualificationsDisplay;
import org.openquark.gems.client.ValueRunner;
import org.openquark.gems.client.ModuleNameDisplayUtilities.TreeViewDisplayMode;
import org.openquark.gems.client.caleditor.AdvancedCALEditor;
import org.openquark.gems.client.navigator.NavAddress.NavAddressMethod;
import org.openquark.util.Pair;
/**
* A base class for editing components that appear in editing sections.
* The base class provides the actual component that does the editing,
* the title for the component, and a setter/getter for the component's value.
*
* @param <V> the type of the value stored by the editor
* @author Frank Worsley
*/
abstract class NavEditorComponent<V> {
/** The default outside border of editor components with borders. */
static final Border DEFAULT_OUTSIDE_BORDER = BorderFactory.createLineBorder(Color.GRAY, 1);
/** The default inside border of editor components with borders. */
static final Border DEFAULT_INSIDE_BORDER = BorderFactory.createEmptyBorder(5, 5, 5, 5);
/** The default compound border of editor components with borders. */
static final Border DEFAULT_BORDER = BorderFactory.createCompoundBorder(DEFAULT_OUTSIDE_BORDER, DEFAULT_INSIDE_BORDER);
/** The default background color for editors with a background. */
static final Color DEFAULT_BACKGROUND_COLOR = Color.LIGHT_GRAY;
/** The title of the editor component. */
private final String title;
/** The description of the editor component. */
private final String description;
/** The unique key that identifies this editor inside its editor section. */
private final String key;
/** The editor section this component is used in. */
private final NavEditorSection editorSection;
/**
* Constructs a new editor without a unique key, title, or description.
* @param editorSection the editor section the editor belongs to
*/
public NavEditorComponent(NavEditorSection editorSection) {
this (editorSection, null);
}
/**
* Constructs a new editor with a unique key.
* @param editorSection the editor section the editor belongs to
* @param key the unique key of the editor
*/
public NavEditorComponent(NavEditorSection editorSection, String key) {
this (editorSection, key, null);
}
/**
* Constructs a new editor with a unique key and title.
* @param editorSection the editor section this editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
*/
public NavEditorComponent(NavEditorSection editorSection, String key, String title) {
this (editorSection, key, title, null);
}
/**
* Constructs a new editor with the given title, description, and unique key.
* @param editorSection the editor section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavEditorComponent(NavEditorSection editorSection, String key, String title, String description) {
if (editorSection == null) {
throw new NullPointerException();
}
this.key = key;
this.title = title;
this.description = description;
this.editorSection = editorSection;
}
/**
* @return the title of the editor. May be null if the editor doesn't have a title.
*/
public String getTitle() {
return title;
}
/**
* @return the description of the editor. May be null if the editor doesn't have a description.
*/
public String getDescription() {
return description;
}
/**
* @return the unique key that identifies this editor component.
* Maybe be null if this editor does not have a unique key.
*/
public String getKey() {
return key;
}
/**
* @return the editor section this editor is used by
*/
NavEditorSection getEditorSection() {
return editorSection;
}
/**
* Notifies the editor section that this editor belongs to that the
* value stored by the editor has changed.
*/
void editorChanged() {
editorSection.editorChanged(this);
}
/**
* @return the actual editor component
*/
public abstract JComponent getEditorComponent();
/**
* @return the value stored by the editor
*/
public abstract V getValue();
/**
* Sets the value stored by the editor.
* @param value the new value of the editor
*/
public abstract void setValue(V value);
}
/**
* An editor component to edit a single line of text.
* @author Frank Worsley
*/
class NavTextFieldEditor extends NavEditorComponent<String> {
/** The text field for editing the text. */
private final JTextField textField = new JTextField();
/**
* Constructs a new text field editor.
* @param editorSection the section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavTextFieldEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
textField.setColumns(50);
textField.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
});
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return textField;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public String getValue() {
String text = textField.getText();
return text != null && text.trim().length() > 0 ? text : null;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(String value) {
textField.setText(value);
}
}
/**
* An editor component to edit multiple lines of text.
* @author Frank Worsley
*/
class NavTextAreaEditor extends NavEditorComponent<String> {
/** The text area for editing the text. */
private final JTextArea textArea = new JTextArea();
/** The scrollpane that houses the text area. */
private final JScrollPane scrollPane = new JScrollPane(textArea);
/**
* Constructs a new text area editor
* @param editorSection the editor section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavTextAreaEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
Dimension size = new Dimension(250, 100);
scrollPane.setMinimumSize(size);
scrollPane.setPreferredSize(size);
scrollPane.setMaximumSize(size);
textArea.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
});
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return scrollPane;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public String getValue() {
String text = textArea.getText();
return text != null && text.trim().length() > 0 ? text : null;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(String value) {
textArea.setText(value);
}
}
/**
* An editor component to edit a boolean value.
* @author Frank Worsley
*/
class NavBooleanEditor extends NavEditorComponent<Boolean> {
/** The panel that contains the editor components. */
private final JPanel editorPanel;
/** The button group for the yes/no radio buttons. */
private final ButtonGroup buttonGroup = new ButtonGroup();
/** The radio button for the true value. */
private final JRadioButton yesButton = new JRadioButton(NavigatorMessages.getString("NAV_YesButtonLabel"));
/** The radio button for the false value. */
private final JRadioButton noButton = new JRadioButton(NavigatorMessages.getString("NAV_NoButtonLabel"));
/**
* Constructs a new boolean editor.
* @param editorSection the section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavBooleanEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
editorPanel = new JPanel();
editorPanel.setOpaque(false);
editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS));
editorPanel.add(yesButton);
editorPanel.add(Box.createHorizontalStrut(15));
editorPanel.add(noButton);
editorPanel.add(Box.createHorizontalGlue());
yesButton.setOpaque(false);
noButton.setOpaque(false);
buttonGroup.add(yesButton);
buttonGroup.add(noButton);
yesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
editorChanged();
}
});
noButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
editorChanged();
}
});
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return editorPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public Boolean getValue() {
ButtonModel selected = buttonGroup.getSelection();
if (selected == yesButton.getModel()) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(Boolean value) {
if (value.booleanValue()) {
yesButton.setSelected(true);
} else {
noButton.setSelected(true);
}
}
}
/**
* An editor component to edit a list of Strings.
* @author Frank Worsley
*/
class NavListEditor extends NavEditorComponent<List<String>> {
/** The icon for the up button. */
private static final ImageIcon upIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/up.gif"));
/** The icon for the down button. */
private static final ImageIcon downIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/down.gif"));
/** The list of data. */
private final JList dataList = new JList();
/** The new item text field. */
private final JTextField itemField = new JTextField();
/** The scrollpane that contains the JList. */
private final JScrollPane scrollPane = new JScrollPane(dataList);
/** The panel that contains the list and related buttons. */
private final JPanel contentPanel = new JPanel();
/** Whether or not the text in the text field can be added as a new item. */
private boolean canAddNewItem = false;
/**
* Constructs a new list editor.
* @param editorSection the section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavListEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
contentPanel.setOpaque(false);
contentPanel.setLayout(new BorderLayout(5, 5));
contentPanel.add(scrollPane, BorderLayout.CENTER);
dataList.setModel(new DefaultListModel());
dataList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
Dimension size = new Dimension(250, 100);
scrollPane.setMinimumSize(size);
scrollPane.setPreferredSize(size);
scrollPane.setMaximumSize(size);
// Create a text field and buttons for changing the list
final JButton addButton = new JButton(NavigatorMessages.getString("NAV_AddItemButtonLabel"));
final JButton removeButton = new JButton(NavigatorMessages.getString("NAV_RemoveItemButtonLabel"));
itemField.setToolTipText(NavigatorMessages.getString("NAV_ItemFieldToolTip"));
addButton.setEnabled(false);
addButton.setToolTipText(NavigatorMessages.getString("NAV_AddItemButtonToolTip"));
removeButton.setEnabled(false);
removeButton.setToolTipText(NavigatorMessages.getString("NAV_RemoveItemButtonLabel"));
// Add the components
Box hbox = Box.createHorizontalBox();
hbox.add(itemField);
hbox.add(Box.createHorizontalStrut(5));
hbox.add(addButton);
hbox.add(Box.createHorizontalStrut(5));
hbox.add(removeButton);
contentPanel.add(hbox, BorderLayout.NORTH);
// Create buttons for re-ordering the list
final JButton upButton = new JButton(upIcon);
final JButton downButton = new JButton(downIcon);
upButton.setMargin(new Insets(0, 0, 0, 0));
upButton.setEnabled(false);
upButton.setToolTipText(NavigatorMessages.getString("NAV_MoveUpItemButtonToolTip"));
downButton.setMargin(new Insets(0, 0, 0, 0));
downButton.setEnabled(false);
downButton.setToolTipText(NavigatorMessages.getString("NAV_MoveDownItemButtonToolTip"));
// Add the components
Box vbox = Box.createVerticalBox();
vbox.add(Box.createVerticalGlue());
vbox.add(upButton);
vbox.add(Box.createVerticalStrut(5));
vbox.add(downButton);
vbox.add(Box.createVerticalGlue());
contentPanel.add(vbox, BorderLayout.EAST);
// Add a key listener to add the item if the enter key is pressed
itemField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
addNewItem();
}
}
});
// Add a ley listener to remove the item if the delete key is pressed
dataList.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_DELETE) {
removeSelectedItem();
}
}
});
// Add an action listener to the add button
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addNewItem();
}
});
// Add an action listener to the remove button
removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeSelectedItem();
itemField.requestFocusInWindow();
}
});
// Add an action listener to the up button
upButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
int index = dataList.getSelectedIndex();
Object elem = listModel.getElementAt(index);
Object old = listModel.getElementAt(index - 1);
listModel.set(index, old);
listModel.set(index - 1, elem);
dataList.setSelectedIndex(index - 1);
editorChanged();
}
});
// Add an action listener to the down button
downButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
int index = dataList.getSelectedIndex();
Object elem = listModel.getElementAt(index);
Object old = listModel.getElementAt(index + 1);
listModel.set(index, old);
listModel.set(index + 1, elem);
dataList.setSelectedIndex(index + 1);
editorChanged();
}
});
// Add a list selection listener to disabled/enable the buttons
dataList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
int index = dataList.getSelectedIndex();
removeButton.setEnabled(index != -1);
upButton.setEnabled(index > 0);
downButton.setEnabled(index != -1 && index < dataList.getModel().getSize() - 1);
}
});
// Add a change listener to the text field. Only enable the add button
// if the text field contains a valid value and the value is not already
// in the list.
itemField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent e) {
resetAddButton();
}
public void removeUpdate(DocumentEvent e) {
resetAddButton();
}
private void resetAddButton() {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
canAddNewItem = itemField.getText().trim().length() > 0
&& !listModel.contains(itemField.getText());
addButton.setEnabled(canAddNewItem);
}
});
}
/**
* Adds a new item with the value entered in the item text field to
* the list, if the new item is valid and not already in the list.
*/
private void addNewItem() {
if (canAddNewItem) {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
listModel.addElement(itemField.getText());
dataList.setSelectedValue(itemField.getText(), true);
itemField.setText("");
editorChanged();
}
}
/**
* Removes the selected item from the list.
*/
private void removeSelectedItem() {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
int index = dataList.getSelectedIndex();
if (index != -1) {
listModel.remove(index);
// select the next item in the list
if (listModel.getSize() > 0) {
dataList.setSelectedIndex(index < listModel.getSize() ? index : index - 1);
}
editorChanged();
}
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public List<String> getValue() {
DefaultListModel listModel = (DefaultListModel) dataList.getModel();
List<String> values = new ArrayList<String>();
for (int i = 0; i < listModel.size(); i++) {
values.add((String)listModel.get(i));
}
return values;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(List<String> values) {
DefaultListModel listModel = new DefaultListModel();
for (final String value : values) {
listModel.addElement(value);
}
dataList.setModel(listModel);
}
}
/**
* An editor component to edit related features.
* @author Frank Worsley
*/
class NavFeaturesEditor extends NavEditorComponent<List<CALFeatureName>> {
/** The icon for the up button. */
private static final ImageIcon upIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/up.gif"));
/** The icon for the down button. */
private static final ImageIcon downIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/down.gif"));
/** The icon for the left button. */
private static final ImageIcon leftIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/leftArrow.gif"));
/** The icon for the right button. */
private static final ImageIcon rightIcon = new ImageIcon(NavListEditor.class.getResource("/Resources/rightArrow.gif"));
/** The button for adding a related feature. */
private final JButton addButton = new JButton(rightIcon);
/** The button for removing a related feature. */
private final JButton removeButton = new JButton(leftIcon);
/** The button for moving up a related feature. */
private final JButton upButton = new JButton(upIcon);
/** The button for moving down a related feature. */
private final JButton downButton = new JButton(downIcon);
/** The tree of current related features. */
private final JList relatedFeatures = new JList();
/** The tree of non-related features. */
private final NavTree otherFeatures = new NavTree();
/** The panel that contains the trees and related buttons. */
private final JPanel contentPanel = new JPanel();
/**
* Constructs a new list editor.
* @param editorSection the section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavFeaturesEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
contentPanel.setOpaque(false);
contentPanel.setLayout(new GridLayout(1, 2));
Perspective perspective = getEditorSection().getEditorPanel().getNavigatorOwner().getPerspective();
NavTreeModel model = new NavTreeModel();
model.load(perspective, TreeViewDisplayMode.FLAT_ABBREVIATED);
otherFeatures.setModel(model);
otherFeatures.collapseToModules();
otherFeatures.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
relatedFeatures.setModel(new DefaultListModel());
relatedFeatures.setCellRenderer(createFeatureListRenderer());
relatedFeatures.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
Box leftBox = Box.createVerticalBox();
leftBox.add(Box.createVerticalGlue());
leftBox.add(addButton);
leftBox.add(Box.createVerticalStrut(10));
leftBox.add(removeButton);
leftBox.add(Box.createVerticalGlue());
leftBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
Box rightBox = Box.createVerticalBox();
rightBox.add(Box.createVerticalGlue());
rightBox.add(upButton);
rightBox.add(Box.createVerticalStrut(10));
rightBox.add(downButton);
rightBox.add(Box.createVerticalGlue());
rightBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 0));
JPanel leftPanel = new JPanel();
leftPanel.setLayout(new BorderLayout());
leftPanel.add(new JScrollPane(otherFeatures), BorderLayout.CENTER);
leftPanel.add(leftBox, BorderLayout.EAST);
leftPanel.setOpaque(false);
JPanel rightPanel = new JPanel();
rightPanel.setLayout(new BorderLayout());
rightPanel.add(new JScrollPane(relatedFeatures), BorderLayout.CENTER);
rightPanel.add(rightBox, BorderLayout.EAST);
rightPanel.setOpaque(false);
contentPanel.add(leftPanel);
contentPanel.add(rightPanel);
// Set a preferred height of 150, the width will resize dynamically anyway.
contentPanel.setPreferredSize(new Dimension(150, 150));
// Setup the buttons.
addButton.setEnabled(false);
addButton.setToolTipText(NavigatorMessages.getString("NAV_AddFeatureButtonToolTip"));
addButton.setMargin(new Insets(0, 0, 0, 0));
addButton.setAlignmentY(Component.CENTER_ALIGNMENT);
removeButton.setEnabled(false);
removeButton.setToolTipText(NavigatorMessages.getString("NAV_RemoveFeatureButtonToolTip"));
removeButton.setMargin(new Insets(0, 0, 0, 0));
removeButton.setAlignmentY(Component.CENTER_ALIGNMENT);
upButton.setEnabled(false);
upButton.setToolTipText(NavigatorMessages.getString("NAV_MoveUpFeatureButtonToolTip"));
upButton.setMargin(new Insets(0, 0, 0, 0));
downButton.setEnabled(false);
downButton.setToolTipText(NavigatorMessages.getString("NAV_MoveDownFeatureButtonToolTip"));
downButton.setMargin(new Insets(0, 0, 0, 0));
// Add an action listener to the add button
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addFeature();
editorChanged();
}
});
// Add an action listener to the remove button
removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeFeature();
editorChanged();
}
});
// Add an action listener to the up button
upButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
moveFeatureUp();
editorChanged();
}
});
// Add an action listener to the down button
downButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
moveFeatureDown();
editorChanged();
}
});
// Add selection listeners to enable/disable the buttons
otherFeatures.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
TreePath path = e.getNewLeadSelectionPath();
boolean enabled = false;
if (path != null) {
relatedFeatures.clearSelection();
NavTreeNode node = (NavTreeNode) path.getLastPathComponent();
DefaultListModel listModel = (DefaultListModel) relatedFeatures.getModel();
NavAddress address = node.getAddress();
// We can add all nodes other than valut nodes as related features.
enabled = address.getParameter(NavAddress.VAULT_PARAMETER) == null &&
address.getMethod() != NavAddress.MODULE_NAMESPACE_METHOD &&
!address.equals(getEditorSection().getAddress()) &&
!listModel.contains(address.toFeatureName());
}
addButton.setEnabled(enabled);
}
});
relatedFeatures.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
int index = relatedFeatures.getSelectedIndex();
removeButton.setEnabled(index != -1);
upButton.setEnabled(index > 0);
downButton.setEnabled(index != -1 && index < relatedFeatures.getModel().getSize() - 1);
if (index != -1) {
otherFeatures.clearSelection();
}
}
});
}
/**
* Adds the currently selected features to the list of related features.
*/
private void addFeature() {
NavTreeNode selected = (NavTreeNode) otherFeatures.getSelectionPath().getLastPathComponent();
NavAddress address = selected.getAddress();
DefaultListModel listModel = (DefaultListModel) relatedFeatures.getModel();
listModel.addElement(address.toFeatureName());
NavTreeNode next = (NavTreeNode) selected.getNextSibling();
if (next != null) {
TreePath path = new TreePath(next.getPath());
otherFeatures.setSelectionPath(path);
otherFeatures.makeVisible(path);
} else {
addButton.setEnabled(false);
}
}
/**
* Removes the selected features from the related features.
*/
private void removeFeature() {
DefaultListModel listModel = (DefaultListModel) relatedFeatures.getModel();
int index = relatedFeatures.getSelectedIndex();
listModel.remove(index);
// select the next item in the list
if (listModel.getSize() > 0) {
relatedFeatures.setSelectedIndex(index < listModel.getSize() ? index : index - 1);
} else {
removeButton.setEnabled(false);
}
}
/**
* Moves up the selected related feature.
*/
private void moveFeatureUp() {
DefaultListModel listModel = (DefaultListModel) relatedFeatures.getModel();
int index = relatedFeatures.getSelectedIndex();
Object elem = listModel.getElementAt(index);
Object old = listModel.getElementAt(index - 1);
listModel.set(index, old);
listModel.set(index - 1, elem);
relatedFeatures.setSelectedIndex(index - 1);
}
/**
* Moves down the selected related feature.
*/
private void moveFeatureDown() {
DefaultListModel listModel = (DefaultListModel) relatedFeatures.getModel();
int index = relatedFeatures.getSelectedIndex();
Object elem = listModel.getElementAt(index);
Object old = listModel.getElementAt(index + 1);
listModel.set(index, old);
listModel.set(index + 1, elem);
relatedFeatures.setSelectedIndex(index + 1);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public List<CALFeatureName> getValue() {
List<CALFeatureName> featureNames = new ArrayList<CALFeatureName>();
DefaultListModel listModel = (DefaultListModel)relatedFeatures.getModel();
for (int i = 0, size = listModel.getSize(); i < size; i++) {
featureNames.add((CALFeatureName)listModel.getElementAt(i));
}
return featureNames;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(List<CALFeatureName> calFeatureNames) {
DefaultListModel listModel = new DefaultListModel();
for (final CALFeatureName calFeatureName : calFeatureNames) {
listModel.addElement(calFeatureName);
}
relatedFeatures.setModel(listModel);
}
/**
* @return the list cell renderer to use for the related features list
*/
private DefaultListCellRenderer createFeatureListRenderer() {
return new DefaultListCellRenderer() {
private static final long serialVersionUID = -1761318607899607831L;
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
NavAddress address = NavAddress.getAddress((CALFeatureName) value);
NavAddressMethod method = address.getMethod();
NavFrameOwner owner = getEditorSection().getEditorPanel().getNavigatorOwner();
ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(owner.getPerspective().getWorkingModuleTypeInfo());
String text = NavAddressHelper.getDisplayText(owner, address, namingPolicy);
Icon icon = null;
if (method == NavAddress.MODULE_METHOD) {
icon = NavTreeCellRenderer.moduleNodeIcon;
} else if (method == NavAddress.FUNCTION_METHOD) {
icon = NavTreeCellRenderer.functionNodeIcon;
} else if (method == NavAddress.TYPE_CLASS_METHOD) {
icon = NavTreeCellRenderer.typeClassNodeIcon;
} else if (method == NavAddress.TYPE_CONSTRUCTOR_METHOD) {
icon = NavTreeCellRenderer.typeConstructorNodeIcon;
} else if (method == NavAddress.CLASS_METHOD_METHOD) {
icon = NavTreeCellRenderer.functionNodeIcon;
} else if (method == NavAddress.DATA_CONSTRUCTOR_METHOD) {
icon = NavTreeCellRenderer.functionNodeIcon;
} else if (method == NavAddress.CLASS_INSTANCE_METHOD) {
icon = NavTreeCellRenderer.classInstanceNodeIcon;
} else if (method == NavAddress.INSTANCE_METHOD_METHOD) {
icon = NavTreeCellRenderer.functionNodeIcon;
}
JLabel label = (JLabel) super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus);
label.setIcon(icon);
return label;
}
};
}
}
/**
* An editor component to edit a CALExample.
* @author Frank Worsley
*/
class NavExampleEditor extends NavEditorComponent<CALExample> {
/** The main panel that contains the editor components. */
private final JPanel contentPanel = new JPanel();
/** The field for editing the example description. */
private final JTextField descriptionField = new JTextField();
/** The checkbox for editing the run automatically flag. */
private final JCheckBox runAutomaticallyBox = new JCheckBox(NavigatorMessages.getString("NAV_AutoRunExampleCheckBox"));
/** The expression editor panel for editing the example expression. */
private final ExpressionPanel expressionPanel;
/** The combox box for selecting the module context. */
private final JComboBox moduleContextBox;
/** The CAL example this editor is currently editing. */
private CALExample currentValue = null;
/**
* Constructs a new example editor.
* @param editorSection the section the editor belongs to
* @param example the example with which to initialize the editor
*/
public NavExampleEditor(NavEditorSection editorSection, CALExample example) {
super(editorSection);
// Constraints for components in the left column
GridBagConstraints left = new GridBagConstraints();
left.anchor = GridBagConstraints.WEST;
left.fill = GridBagConstraints.BOTH;
left.gridx = 0;
left.weightx = 0;
left.insets = new Insets(2, 0, 3, 5);
// Right column constraints
GridBagConstraints right = new GridBagConstraints();
right.anchor = GridBagConstraints.WEST;
right.fill = GridBagConstraints.BOTH;
right.gridx = 1;
right.weightx = 1;
right.insets = new Insets(2, 5, 3, 0);
// Setup the controls
moduleContextBox = EditorHelper.getModuleContextComboBox(getEditorSection().getEditorPanel().getNavigatorOwner());
expressionPanel = new ExpressionPanel(editorSection.getEditorPanel().getNavigatorOwner());
runAutomaticallyBox.setOpaque(false);
// Create a JPanel to hold the top editing controls
JPanel topPanel = new JPanel();
topPanel.setOpaque(false);
topPanel.setLayout(new GridBagLayout());
topPanel.add(new JLabel(NavigatorMessages.getString("NAV_Description")), left);
topPanel.add(descriptionField, right);
topPanel.add(new JLabel(NavigatorMessages.getString("NAV_ExpressionRunInModule")), left);
topPanel.add(moduleContextBox, right);
left.gridwidth = GridBagConstraints.REMAINDER;
topPanel.add(runAutomaticallyBox, left);
// Setup the main content panel
contentPanel.setOpaque(true);
contentPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
contentPanel.setBorder(DEFAULT_BORDER);
contentPanel.setLayout(new BorderLayout(5, 5));
contentPanel.add(topPanel, BorderLayout.NORTH);
contentPanel.add(expressionPanel, BorderLayout.CENTER);
// Add run button
expressionPanel.addRunButton(NavigatorMessages.getString("NAV_RunExampleButton"), new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionPanel.runExpression();
}
});
// Add delete button
expressionPanel.addDeleteButton(NavigatorMessages.getString("NAV_DeleteExampleButton"), new ActionListener() {
public void actionPerformed(ActionEvent e) {
getEditorSection().removeEditor(NavExampleEditor.this);
}
});
// Add change listeners to know when the editor changed
moduleContextBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionPanel.setModuleName((ModuleName) moduleContextBox.getSelectedItem());
}
});
runAutomaticallyBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
editorChanged();
}
});
// Add change listener to know when editor contents change
expressionPanel.setPanelChangeListener(new ExpressionPanel.PanelChangeListener() {
public void panelContentsChanged() {
editorChanged();
}
});
descriptionField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
// Finally set the supplied value
setValue(example);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public CALExample getValue() {
ModuleName moduleContext = (ModuleName) moduleContextBox.getSelectedItem();
String description = (descriptionField.getText().trim().length() > 0) ? descriptionField.getText() : null;
CALExpression expression = new CALExpression(
moduleContext, expressionPanel.getExpressionText(), expressionPanel.getQualificationMap(), expressionPanel.getQualifiedExpressionText());
CALExample example = new CALExample(expression, description, runAutomaticallyBox.isSelected());
return example;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(CALExample value) {
currentValue = value;
moduleContextBox.setSelectedItem(currentValue.getExpression().getModuleContext());
expressionPanel.setModuleName(currentValue.getExpression().getModuleContext());
expressionPanel.setQualificationMap(currentValue.getExpression().getQualificationMap().makeCopy());
expressionPanel.setExpressionText(currentValue.getExpression().getExpressionText());
descriptionField.setText(currentValue.getDescription());
runAutomaticallyBox.setSelected(currentValue.evaluateExample());
if (currentValue.evaluateExample()) {
if (currentValue.getExpression().getExpressionText().length() == 0) {
expressionPanel.setStatusMessage(NavigatorMessages.getString("NAV_EnterUsageExample_Message"));
} else {
expressionPanel.runExpression();
}
} else {
expressionPanel.setStatusMessage(NavigatorMessages.getString("NAV_ClickToRunExample_Message"));
}
}
}
/**
* An editor component to edit a custom metadata attribute.
* @author Joseph Wong
*/
class NavCustomAttributeEditor extends NavEditorComponent<Pair<String, String>> {
/** The main panel that contains the editor components. */
private final JPanel contentPanel = new JPanel();
/** The text field for the custom attribute's name. */
private final JTextField attributeName = new JTextField();
/** The text area for the custom attribute's value. */
private final JTextArea attributeValue = new JTextArea();
/** The scrollpane that contains the text area. */
private final JScrollPane scrollPane = new JScrollPane(attributeValue);
/** The icon for the delete button. */
private static final ImageIcon deleteIcon = new ImageIcon(ExpressionPanel.class.getResource("/Resources/delete.gif"));
/** The button for deleting the custom attribute. */
private final JButton deleteButton = new JButton(deleteIcon);
/**
* Constructs a new custom attribute editor.
* @param editorSection the section the editor belongs to
*/
public NavCustomAttributeEditor(NavEditorSection editorSection, String attrName, String attrValue) {
super(editorSection);
// Initial values for attribute name and value
attributeName.setText(attrName);
attributeValue.setText(attrValue);
attributeValue.setLineWrap(true);
attributeValue.setWrapStyleWord(true);
Dimension size = new Dimension(250, 100);
scrollPane.setMinimumSize(size);
scrollPane.setPreferredSize(size);
scrollPane.setMaximumSize(size);
// Constraints for components in the left column
GridBagConstraints left = new GridBagConstraints();
left.anchor = GridBagConstraints.WEST;
left.fill = GridBagConstraints.BOTH;
left.gridx = 0;
left.weightx = 0;
left.insets = new Insets(2, 0, 3, 5);
// Center column constraints
GridBagConstraints center = new GridBagConstraints();
center.anchor = GridBagConstraints.WEST;
center.fill = GridBagConstraints.BOTH;
center.gridx = 1;
center.weightx = 1;
center.insets = new Insets(2, 0, 3, 5);
// Right column constraints
GridBagConstraints right = new GridBagConstraints();
right.anchor = GridBagConstraints.WEST;
right.fill = GridBagConstraints.BOTH;
right.gridx = 2;
right.weightx = 0;
right.insets = new Insets(2, 0, 3, 0);
// Setup the left panel
JPanel leftPanel = new JPanel();
leftPanel.setOpaque(false);
leftPanel.setLayout(new GridBagLayout());
leftPanel.add(new JLabel(NavigatorMessages.getString("NAV_CustomAttributeName")), left);
leftPanel.add(attributeName, center);
leftPanel.add(new JLabel(NavigatorMessages.getString("NAV_CustomAttributeValue")), left);
leftPanel.add(scrollPane, center);
// Setup the right panel
JPanel rightPanel = new JPanel();
rightPanel.setOpaque(false);
rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
rightPanel.add(deleteButton);
rightPanel.add(Box.createVerticalGlue());
// Setup the content panel
contentPanel.setOpaque(true);
contentPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
contentPanel.setBorder(DEFAULT_BORDER);
contentPanel.setLayout(new BorderLayout());
contentPanel.add(leftPanel, BorderLayout.CENTER);
contentPanel.add(rightPanel, BorderLayout.EAST);
// Setup the delete button
deleteButton.setToolTipText(NavigatorMessages.getString("NAV_DeleteCustomAttributeButton"));
deleteButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getEditorSection().removeEditor(NavCustomAttributeEditor.this);
}
});
// Add change listener to know when editor contents change
attributeName.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
attributeValue.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
}
/**
* {@inheritDoc}
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @return a Pair of Strings - the (name, value) pair.
*/
@Override
public Pair<String, String> getValue() {
String name = attributeName.getText();
String value = attributeValue.getText();
if (name == null || name.trim().length() == 0) {
name = null;
}
if (value == null || value.trim().length() == 0) {
value = null;
}
return new Pair<String, String>(name, value);
}
/**
* @param value a Pair of Strings - the (name, value) pair to be set into the editor.
*/
@Override
public void setValue(Pair<String, String> value) {
attributeName.setText(value.fst());
attributeValue.setText(value.snd());
}
}
/**
* An editor component to edit display the argument information for an entity with
* a button to edit the argument metadata.
* @author Frank Worsley
*/
class NavEntityArgumentEditor extends NavEditorComponent<ArgumentMetadata> {
/** The main content panel for the editor. */
private final JPanel contentPanel = new JPanel();
/** The label for displaying the type of the argument. */
private final JLabel typeLabel = new JLabel();
/** The label for displaying the name of the argument. */
private final JLabel nameLabel = new JLabel();
/** The field for editing the argument display name. */
private final JTextField displayNameField = new JTextField();
/** The field for editing the argument description. */
private final JTextField descriptionField = new JTextField();
/** The button for editing the argument. */
private final JButton editButton = new JButton(NavigatorMessages.getString("NAV_EditArgProperties"));
/** The number of the argument. */
private final int argumentNumber;
/** The adjusted name of the argument. */
private String adjustedName = null;
/** The type of the argument. */
private String typeString = null;
/** The argument metadata this editor was initialized with. */
private ArgumentMetadata initialValue = null;
/**
* Constructs a new argument editor.
* @param editorSection the section the editor belongs to
* @param metadata the argument metadata
* @param typeString a String that represents the type of the argument (ie: Num a => [a])
* @param adjustedName the adjusted name of the argument
*/
NavEntityArgumentEditor(NavEditorSection editorSection, int argNum, ArgumentMetadata metadata, String typeString, String adjustedName) {
super(editorSection);
this.argumentNumber = argNum;
this.adjustedName = adjustedName;
this.typeString = typeString;
contentPanel.setOpaque(true);
contentPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
contentPanel.setBorder(DEFAULT_BORDER);
contentPanel.setLayout(new BorderLayout(5, 5));
JLabel descriptionTitle = new JLabel(NavigatorMessages.getString("NAV_ShortDescription"));
JLabel nameTitle = new JLabel(NavigatorMessages.getString("NAV_DisplayName"));
nameTitle.setPreferredSize(descriptionTitle.getPreferredSize());
nameTitle.setToolTipText(NavigatorMessages.getString("NAV_DisplayNameDescription"));
descriptionTitle.setToolTipText(NavigatorMessages.getString("NAV_ShortDescriptionDescription"));
typeLabel.setFont(typeLabel.getFont().deriveFont(Font.ITALIC));
nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
editButton.setToolTipText(NavigatorMessages.getString("NAV_EditArgPropertiesToolTip"));
Dimension prefSize = displayNameField.getPreferredSize();
prefSize = new Dimension(150, prefSize.height);
displayNameField.setMaximumSize(prefSize);
displayNameField.setPreferredSize(prefSize);
prefSize = descriptionField.getPreferredSize();
prefSize = new Dimension(500, prefSize.height);
descriptionField.setMaximumSize(prefSize);
descriptionField.setPreferredSize(prefSize);
Box topBox = Box.createHorizontalBox();
topBox.add(nameLabel);
topBox.add(typeLabel);
topBox.add(Box.createHorizontalGlue());
topBox.add(editButton);
Box centerBox = Box.createHorizontalBox();
centerBox.add(nameTitle);
centerBox.add(Box.createHorizontalStrut(5));
centerBox.add(displayNameField);
centerBox.add(Box.createHorizontalGlue());
Box bottomBox = Box.createHorizontalBox();
bottomBox.add(descriptionTitle);
bottomBox.add(Box.createHorizontalStrut(5));
bottomBox.add(descriptionField);
bottomBox.add(Box.createHorizontalGlue());
contentPanel.add(topBox, BorderLayout.NORTH);
contentPanel.add(centerBox, BorderLayout.CENTER);
contentPanel.add(bottomBox, BorderLayout.SOUTH);
editButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
NavFrameOwner owner = getEditorSection().getEditorPanel().getNavigatorOwner();
NavAddress address = getEditorSection().getEditorPanel().getAddress();
owner.editMetadata(address.withParameter(NavAddress.ARGUMENT_PARAMETER, Integer.toString(argumentNumber)));
}
});
// Add document listeners to let the section know when the user edits a value
displayNameField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
descriptionField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
setValue(metadata);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public ArgumentMetadata getValue() {
ArgumentMetadata currentValue = (ArgumentMetadata) initialValue.copy();
currentValue.setDisplayName(displayNameField.getText());
currentValue.setShortDescription(descriptionField.getText());
return currentValue;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(ArgumentMetadata value) {
initialValue = value;
displayNameField.setText(initialValue.getDisplayName());
descriptionField.setText(initialValue.getShortDescription());
setAdjustedName(adjustedName);
setType(typeString);
}
/**
* @param adjustedName the adjusted name of the argument to use when there is no real name
*/
public void setAdjustedName(String adjustedName) {
this.adjustedName = adjustedName;
nameLabel.setText(adjustedName);
}
/**
* @param typeString the type of the argument to display in the UI
*/
public void setType(String typeString) {
this.typeString = typeString;
typeLabel.setText(typeString != null ? " :: " + typeString : null);
}
}
/**
* An editor component to edit display the return value information for an entity.
* @author Frank Worsley
*/
class NavEntityReturnValueEditor extends NavEditorComponent<String> {
/** The main content panel for the editor. */
private final JPanel contentPanel = new JPanel();
/** The label for displaying the type of the return value. */
private final JLabel typeLabel = new JLabel();
/** The label for displaying the return value indicator (e.g. "result"). */
private final JLabel nameLabel = new JLabel(NavigatorMessages.getString("NAV_ReturnValueIndicator"));
/** The field for editing the return value description. */
private final JTextField descriptionField = new JTextField();
/** The type of the argument. */
private String typeString = null;
/** The metadata value this editor was initialized with. Could be null. */
private String initialValue = null;
/**
* Constructs a new return value editor.
* @param editorSection the section the editor belongs to
* @param key the key of the editor.
* @param returnValueDesc the metadata value. Could be null.
* @param typeString a String that represents the type of the return value (e.g. Num a => [a])
*/
NavEntityReturnValueEditor(NavEditorSection editorSection, String key, String returnValueDesc, String typeString) {
super(editorSection, key);
this.typeString = typeString;
contentPanel.setOpaque(true);
contentPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
contentPanel.setBorder(DEFAULT_BORDER);
contentPanel.setLayout(new BorderLayout(5, 5));
JLabel descriptionTitle = new JLabel(NavigatorMessages.getString("NAV_ShortDescription"));
JLabel nameTitle = new JLabel(NavigatorMessages.getString("NAV_DisplayName"));
nameTitle.setPreferredSize(descriptionTitle.getPreferredSize());
nameTitle.setToolTipText(NavigatorMessages.getString("NAV_DisplayNameDescription"));
descriptionTitle.setToolTipText(NavigatorMessages.getString("NAV_ShortDescriptionDescription"));
typeLabel.setFont(typeLabel.getFont().deriveFont(Font.ITALIC));
nameLabel.setFont(nameLabel.getFont().deriveFont(Font.ITALIC | Font.BOLD));
Dimension prefSize = descriptionField.getPreferredSize();
prefSize = new Dimension(500, prefSize.height);
descriptionField.setMaximumSize(prefSize);
descriptionField.setPreferredSize(prefSize);
Box topBox = Box.createHorizontalBox();
topBox.add(nameLabel);
topBox.add(typeLabel);
topBox.add(Box.createHorizontalGlue());
Box bottomBox = Box.createHorizontalBox();
bottomBox.add(descriptionTitle);
bottomBox.add(Box.createHorizontalStrut(5));
bottomBox.add(descriptionField);
bottomBox.add(Box.createHorizontalGlue());
contentPanel.add(topBox, BorderLayout.NORTH);
contentPanel.add(bottomBox, BorderLayout.CENTER);
// Add document listeners to let the section know when the user edits a value
descriptionField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
editorChanged();
}
public void insertUpdate(DocumentEvent e) {
editorChanged();
}
public void removeUpdate(DocumentEvent e) {
editorChanged();
}
});
setValue(returnValueDesc);
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public String getValue() {
String description = descriptionField.getText().trim();
if (description.length() == 0) {
return null;
} else {
return description;
}
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(String value) {
initialValue = value;
if (initialValue == null) {
descriptionField.setText("");
} else {
descriptionField.setText(initialValue);
}
setType(typeString);
}
/**
* @param typeString the type of the argument to display in the UI
*/
public void setType(String typeString) {
this.typeString = typeString;
typeLabel.setText(typeString != null ? " :: " + typeString : null);
}
}
/**
* An editor for editing CAL expressions.
* @author Frank Worsley
*/
class NavExpressionEditor extends NavEditorComponent<CALExpression> {
/** The main content panel for the editor. */
private final JPanel contentPanel = new JPanel();
/** The combo box for selecting the module context. */
private final JComboBox moduleContextBox;
/** The expression editor panel for editing the expression. */
private final ExpressionPanel expressionPanel;
/** The current CAL expression being edited. */
private CALExpression currentValue;
/**
* Constructs a new expression editor.
* @param editorSection the editor section the editor belongs to
* @param key the unique key of the editor
* @param title the title of the editor
* @param description the description of the editor
*/
public NavExpressionEditor(NavEditorSection editorSection, String key, String title, String description) {
super(editorSection, key, title, description);
// Create the components
moduleContextBox = EditorHelper.getModuleContextComboBox(editorSection.getEditorPanel().getNavigatorOwner());
expressionPanel = new ExpressionPanel(editorSection.getEditorPanel().getNavigatorOwner());
expressionPanel.setStatusMessage(NavigatorMessages.getString("NAV_ExpressionClickButtonToRun_Message"));
// Setup the content panel
contentPanel.setLayout(new BorderLayout(5, 5));
contentPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
contentPanel.setOpaque(true);
contentPanel.setBorder(DEFAULT_BORDER);
// Add the components
Box topBox = Box.createHorizontalBox();
topBox.add(new JLabel(NavigatorMessages.getString("NAV_ExpressionRunInModule")));
topBox.add(Box.createHorizontalStrut(5));
topBox.add(moduleContextBox);
contentPanel.add(topBox, BorderLayout.NORTH);
contentPanel.add(expressionPanel, BorderLayout.CENTER);
// Add a run button
expressionPanel.addRunButton(NavigatorMessages.getString("NAV_ExpressionClickToRun_Message"), new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionPanel.runExpression();
}
});
// Add change listeners to update editor with new module
moduleContextBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
expressionPanel.setModuleName((ModuleName) moduleContextBox.getSelectedItem());
}
});
// Add change listener to knwo when editor contents change
expressionPanel.setPanelChangeListener(new ExpressionPanel.PanelChangeListener() {
public void panelContentsChanged() {
editorChanged();
}
});
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getEditorComponent()
*/
@Override
public JComponent getEditorComponent() {
return contentPanel;
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#getValue()
*/
@Override
public CALExpression getValue() {
if (expressionPanel.getExpressionText().trim().length() == 0 || moduleContextBox.getSelectedItem() == null) {
return null;
}
return new CALExpression((ModuleName) moduleContextBox.getSelectedItem(), expressionPanel.getExpressionText(),
expressionPanel.getQualificationMap(), expressionPanel.getQualifiedExpressionText());
}
/**
* @see org.openquark.gems.client.navigator.NavEditorComponent#setValue(java.lang.Object)
*/
@Override
public void setValue(CALExpression value) {
currentValue = value;
if (value != null) {
moduleContextBox.setSelectedItem(currentValue.getModuleContext());
expressionPanel.setExpressionText(currentValue.getExpressionText());
} else {
moduleContextBox.setSelectedItem(CAL_Prelude.MODULE_NAME);
expressionPanel.setExpressionText(null);
}
expressionPanel.setStatusMessage(NavigatorMessages.getString("NAV_ExpressionClickButtonToRun_Message"));
}
}
/**
* A simple helper class that defines one static method that is used by several editors.
* @author Frank Worsley
*/
class EditorHelper {
//
// This class is not intended to be instantiated.
//
private EditorHelper() {
}
/**
* @param owner the navigator owner from which to get the perspective
* @return a combo box with the <code>ModuleName</code>s of all modules in the perspective
*/
public static JComboBox getModuleContextComboBox(NavFrameOwner owner) {
List<ModuleName> modules = new ArrayList<ModuleName>();
Perspective perspective = owner.getPerspective();
// get names of visible modules
for (final MetaModule metaModule : perspective.getVisibleMetaModules()) {
modules.add(metaModule.getName());
}
// get names of invisible modules
for (final MetaModule metaModule : perspective.getInvisibleMetaModules()) {
modules.add(metaModule.getName());
}
// sort alphabetically
Collections.sort(modules);
// load the combobox model with the modules
DefaultComboBoxModel model = new DefaultComboBoxModel();
for (final ModuleName moduleName : modules) {
model.addElement(moduleName);
}
return new JComboBox(model);
}
/**
* Evaluates the given CAL expression and puts the result text into the given StringBuilder.
* @param owner the navigator owner
* @param expression the expression to evaluate
* @param result the buffer to hold the result text
* @return true if evaluated successfully, false if error occurs
*/
public static boolean evaluateExpression(NavFrameOwner owner, CALExpression expression, StringBuilder result) {
// Check for old example format..
if (expression.getQualifiedExpressionText().equals("") && expression.getExpressionText().length()>0) {
// Expression has not been qualified
CompilerMessageLogger messageLogger = new MessageLogger();
CALExpression qualifiedExpression = qualifyExpression(owner, expression, messageLogger);
if ((qualifiedExpression == null) && (result != null)) {
result.append(messageLogger.getFirstError().getMessage());
return false;
}
}
String expressionText = expression.getQualifiedExpressionText();
CompilerMessageLogger messageLogger = new MessageLogger();
SourceModel.Expr expressionSourceModel = SourceModelUtilities.TextParsing.parseExprIntoSourceModel(expressionText, messageLogger);
if (messageLogger.getNErrors() > 0) {
// The expression could not be parsed.
result.append(messageLogger.getFirstError().getMessage());
return false;
}
Target expressionTarget = new Target.SimpleTarget(expressionSourceModel);
ModuleName expressionModule = expression.getModuleContext();
AdjunctSource expressionDef = expressionTarget.getTargetDef(null, owner.getTypeChecker().getTypeCheckInfo(expressionModule).getModuleTypeInfo());
TypeExpr resultType = owner.getTypeChecker().checkFunction(expressionDef, expressionModule, messageLogger);
if (resultType == null) {
// The type checker couldn't figure out the type.
result.append(messageLogger.getFirstError().getMessage());
return false;
}
try {
// Now create a target to be run by a value runner.
ValueRunner valueRunner = owner.getValueRunner();
ValueNode valueNode = valueRunner.getValue(expressionTarget, expressionModule);
if (valueRunner.isErrorFlagged()) {
result.append(valueRunner.getErrorMessage(expressionTarget, expressionModule));
return false;
} else if (valueNode == null) {
result.append(NavigatorMessages.getString("NAV_NullValueNode_Message"));
return false;
} else {
result.append(valueNode.getTextValue());
return true;
}
} catch (ProgramCompileException ex) {
result.append(NavigatorMessages.getString("NAV_CompilationError_Message"));
return false;
} catch (Throwable ex) {
result.append(NavigatorMessages.getString("NAV_ExecutionError_Message"));
return false;
}
}
/**
* Analyses the specified expression's text, updates the qualified code and qualification map.
*
* Note: This method is here because CAL expressions may have been created with no qualified code;
* this happens when loading old metadata which does not contain qualification map, and thus this
* method is here for backwards compatibility.
*
* @param owner
* @param expression
* @param messageLogger logger to hold errors encountered in auto-qualification
* @return expression with updated qualified code and map; null if errors occurred
*/
private static CALExpression qualifyExpression(NavFrameOwner owner, CALExpression expression, CompilerMessageLogger messageLogger) {
CodeAnalyser analyser = new CodeAnalyser(
owner.getTypeChecker(),
owner.getPerspective().getMetaModule(expression.getModuleContext()).getTypeInfo(),
true,
true);
CodeAnalyser.QualificationResults qualificationResults =
analyser.qualifyExpression(expression.getExpressionText(), null, expression.getQualificationMap(), messageLogger);
if (qualificationResults == null) {
return null;
}
return new CALExpression(
expression.getModuleContext(),
expression.getExpressionText(),
qualificationResults.getQualificationMap(),
qualificationResults.getQualifiedCode());
}
}
/**
* A special panel for editing and running a CALExpression. It provides a CAL source
* code editor, a status label and optional run/delete buttons.
* @author Frank Worsley
*/
class ExpressionPanel extends JPanel implements AutoCompleteManager.AutoCompleteEditor {
private static final long serialVersionUID = -6858240765439671805L;
/*
* Icons used for popup menu items
*/
private static final ImageIcon POPUP_CONSTRUCTOR_ICON = new ImageIcon(AdvancedCALEditor.class.getResource("/Resources/Gem_Yellow.gif"));
private static final ImageIcon POPUP_FUNCTION_ICON = new ImageIcon(AdvancedCALEditor.class.getResource("/Resources/nav_function.gif"));
private static final ImageIcon POPUP_CLASS_ICON = new ImageIcon(AdvancedCALEditor.class.getResource("/Resources/nav_typeclass.gif"));
private static final ImageIcon POPUP_TYPE_ICON = new ImageIcon(AdvancedCALEditor.class.getResource("/Resources/nav_typeconstructor.gif"));
/**
* Class providing default popup menus for identifiers
* @author Iulian Radu
*/
private class PopupProvider implements AdvancedCALEditor.IdentifierPopupMenuProvider {
/**
* Focus listener for editor popup menus.
* On focus lost, display selections are cleared
*/
private class EditorMenuFocusListener implements PopupMenuListener {
public void popupMenuCanceled(PopupMenuEvent e) {
getQualificationsDisplay().clearSelection();
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
getQualificationsDisplay().clearSelection();
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
}
private final EditorMenuFocusListener editorMenuFocusListener = new EditorMenuFocusListener();
/**
* Menu item for converting an ambiguity to a proper qualification
* @author Iulian Radu
*/
private class ToQualificationMenuItem extends JCheckBoxMenuItem implements ActionListener {
private static final long serialVersionUID = 7755582940170807447L;
private final String unqualifiedName;
private final ModuleName moduleName;
private final SourceIdentifier.Category type;
/**
* Constructor
* @param unqualifiedName unqualified name of the identifier
* @param moduleName module which the identifier will belong to if menu clicked
* @param type type of identifier
*/
ToQualificationMenuItem(String unqualifiedName, ModuleName moduleName, SourceIdentifier.Category type) {
super(QualifiedName.make(moduleName, unqualifiedName).getQualifiedName());
this.unqualifiedName = unqualifiedName;
this.moduleName = moduleName;
this.setIcon(getTypeIcon(type));
this.type = type;
this.addActionListener(this);
}
/**
* Converts the identifier to the proper qualification
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent evt) {
Object eventSource = evt.getSource();
if (eventSource == this) {
CodeQualificationMap qualificationMap = ExpressionPanel.this.userResolvedAmbiguities;
qualificationMap.putQualification(unqualifiedName, moduleName, type);
updateQualifications();
}
}
/**
* Overwrite tooltip location to always be on the right side of the menu.
* @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
*/
@Override
public Point getToolTipLocation(MouseEvent e) {
return new Point(this.getWidth(), 0);
}
}
/**
* Menu item for changing module of the identifier
* @author Iulian Radu
*/
private class ModuleChangeMenuItem extends JCheckBoxMenuItem implements ActionListener {
private static final long serialVersionUID = -2341741495125721145L;
private final String unqualifiedName;
private final ModuleName newModuleName;
private final SourceIdentifier.Category type;
/**
* Constructor
* @param unqualifiedName unqualified name of the identifier
* @param newModuleName module to which the identifier will be switched by the menu item
* @param type type of identifier
* @param active whether this menu item represents the current form of the
* identifier
*/
ModuleChangeMenuItem(String unqualifiedName, ModuleName newModuleName, SourceIdentifier.Category type, boolean active) {
super(QualifiedName.make(newModuleName, unqualifiedName).getQualifiedName());
this.unqualifiedName = unqualifiedName;
this.newModuleName = newModuleName;
this.type = type;
this.setIcon(getTypeIcon(type));
this.setState(active);
if (!active) {
// Only act if not active
this.addActionListener(this);
}
}
/**
* Informs all the listeners that a module change is requested
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent evt) {
Object eventSource = evt.getSource();
if (eventSource == this) {
CodeQualificationMap qualificationMap = ExpressionPanel.this.userResolvedAmbiguities;
qualificationMap.removeQualification(unqualifiedName, type);
qualificationMap.putQualification(unqualifiedName, newModuleName, type);
updateQualifications();
}
}
/**
* Overwrite tooltip location to always be on the right side of the menu.
* @see javax.swing.JComponent#getToolTipLocation(java.awt.event.MouseEvent)
*/
@Override
public Point getToolTipLocation(MouseEvent e) {
return new Point(this.getWidth(), 0);
}
}
/**
* Get the popup menu for a clicked identifier
* @param identifier the identifier selected
* @return JPopupMenu the popup menu to be displayed; null if no menu for this item
*/
public JPopupMenu getPopupMenu(AdvancedCALEditor.PositionlessIdentifier identifier) {
// Determine if identifier is an ambiguity or qualification
CodeAnalyser.AnalysedIdentifier.QualificationType qualificationType = identifier.getQualificationType();
// Qualified symbol ?
if (qualificationType.isResolvedTopLevelSymbol()) {
String unqualifiedName = identifier.getName();
ModuleName moduleName = identifier.getResolvedModuleName();
if (moduleName == null) {
return null;
}
SourceIdentifier.Category type = identifier.getCategory();
boolean mapQualified = (qualificationType == CodeAnalyser.AnalysedIdentifier.QualificationType.UnqualifiedResolvedTopLevelSymbol);
getQualificationsDisplay().selectPanelForIdentifier(identifier);
JPopupMenu menu = getQualificationPopupMenu(unqualifiedName, moduleName, type, mapQualified);
menu.addPopupMenuListener(editorMenuFocusListener);
return menu;
}
// Ambiguity ?
if (qualificationType == CodeAnalyser.AnalysedIdentifier.QualificationType.UnqualifiedAmbiguousTopLevelSymbol) {
getQualificationsDisplay().selectPanelForIdentifier(identifier);
JPopupMenu menu = getAmbiguityPopupMenu(identifier.getName(), identifier.getCategory());
menu.addPopupMenuListener(editorMenuFocusListener);
return menu;
}
// This menu only handles ambiguities, and qualified identifiers
return null;
}
/**
* Create menu for a qualification.
* This is the menu for a qualification panel, or for a CAL editor identifier which is not an argument.
*
* @param unqualifiedName
* @param moduleName
* @param type
* @param changeableModule true if identifier can have its module change
* @return qualification popup menu
*/
private JPopupMenu getQualificationPopupMenu(String unqualifiedName, ModuleName moduleName, SourceIdentifier.Category type, boolean changeableModule) {
JPopupMenu menu = new JPopupMenu();
// Add module change items
if (!changeableModule) {
JCheckBoxMenuItem newItem = new ModuleChangeMenuItem(unqualifiedName, moduleName, type, true);
newItem.setToolTipText(calEditor.getMetadataToolTipText(unqualifiedName, moduleName, type, moduleTypeInfo));
newItem.setEnabled(false);
menu.add(newItem);
} else {
// This is an unqualified module
List<ModuleName> candidateModules = CodeAnalyser.getModulesContainingIdentifier(unqualifiedName, type, moduleTypeInfo);
for (final ModuleName newModule : candidateModules) {
JCheckBoxMenuItem newItem = new ModuleChangeMenuItem(unqualifiedName, newModule, type, (newModule.equals(moduleName)));
newItem.setToolTipText(calEditor.getMetadataToolTipText(unqualifiedName, newModule, type, moduleTypeInfo));
menu.add(newItem);
}
}
return menu;
}
/**
* Create menu for an ambiguity.
* This is the menu for a text identifier which could not be qualified; the options
* are to qualify to a matching function.
*
* @param unqualifiedName
* @return ambiguity popup menu
*/
private JPopupMenu getAmbiguityPopupMenu(String unqualifiedName, SourceIdentifier.Category type) {
JPopupMenu menu = new JPopupMenu();
List<ModuleName> candidateModules = CodeAnalyser.getModulesContainingIdentifier(unqualifiedName, type, moduleTypeInfo);
for (final ModuleName moduleName : candidateModules) {
JMenuItem newItem = new ToQualificationMenuItem(unqualifiedName, moduleName, type);
newItem.setToolTipText(calEditor.getMetadataToolTipText(unqualifiedName, moduleName, type, moduleTypeInfo));
menu.add(newItem);
}
return menu;
}
/**
* Retrieves the icon associated to a given type
* @param type
* @return icon associated to type
*/
private ImageIcon getTypeIcon(SourceIdentifier.Category type) {
if (type == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD) {
return POPUP_FUNCTION_ICON;
} else if (type == SourceIdentifier.Category.DATA_CONSTRUCTOR) {
return POPUP_CONSTRUCTOR_ICON;
} else if (type == SourceIdentifier.Category.TYPE_CLASS) {
return POPUP_CLASS_ICON;
} else if (type == SourceIdentifier.Category.TYPE_CONSTRUCTOR) {
return POPUP_TYPE_ICON;
} else {
throw new IllegalArgumentException();
}
}
}
/**
* Interface allowing listeners to be notified that the panel contents
* have changed.
* @author Iulian Radu
*/
public interface PanelChangeListener {
/**
* Notifies listeners that the panel contents have changed
*/
public void panelContentsChanged();
}
/**
* Listener for change events on this panel
*/
private PanelChangeListener panelChangeListener;
/** The icon for the run button. */
private static final ImageIcon runIcon = new ImageIcon(ExpressionPanel.class.getResource("/Resources/play.gif"));
/** The icon for the delete button. */
private static final ImageIcon deleteIcon = new ImageIcon(ExpressionPanel.class.getResource("/Resources/delete.gif"));
/** The label that displays status messages and expression output. */
private final JLabel statusLabel = new JLabel();
/** The CAL editor for editing the expression. */
private final AdvancedCALEditor calEditor;
/** The module in which the CAL code belongs */
private ModuleTypeInfo moduleTypeInfo;
/** The qualification map for the expression code */
private CodeQualificationMap qualificationMap;
/**
* Qualification map of all identifiers which have been assigned to specific modules,
* regardless of whether they are still used within the code. The map for the expression
* code contains only necessary entries from this map, and entries representing
* unambiguous qualifications.
*/
private CodeQualificationMap userResolvedAmbiguities = new CodeQualificationMap();
/** The expression in its fully qualified form */
private String qualifiedExpressionText;
/** Transfer handler for the editor component */
private CodeGemEditor.EditorTextTransferHandler editorTransferHandler;
/** The code analyser which inspects the editor text */
private CodeAnalyser codeAnalyser;
/** The scrollpane that embeds the CAL editor. */
private final JScrollPane scrollPane;
/** The split pane that holds the editor and qualifications scroll pane */
private JSplitPane splitPane;
/** The panel showing qualifications */
private QualificationsDisplay qualificationsDisplay;
/** The button for running the example. */
private final JButton runButton = new JButton(runIcon);
/** The button for deleting the example. */
private final JButton deleteButton = new JButton(deleteIcon);
/** The box for holding the run/delete buttons. */
private final Box buttonBox = Box.createVerticalBox();
/** The navigator owner that uses this expression panel. */
private final NavFrameOwner owner;
/** The code syntax listener used for chromacoding */
private final GemCodeSyntaxListener codeSyntaxListener;
/**
* Constructs a new expression panel.
* @param owner the navigator owner of the expression panel
*/
public ExpressionPanel(NavFrameOwner owner) {
this.owner = owner;
// set default module to be as defined by perspective
Perspective perspective = owner.getPerspective();
this.moduleTypeInfo = perspective.getWorkingModuleTypeInfo();
// Create and initialize components
this.calEditor = new AdvancedCALEditor(moduleTypeInfo, perspective.getWorkspace());
this.codeSyntaxListener = new GemCodeSyntaxListener(perspective);
try {
this.calEditor.addCALSyntaxStyleListener(codeSyntaxListener);
} catch (TooManyListenersException e) {
// The only effect this has on the editor is that custom text coloring does not work;
// however, the system will be in an erroneous state.
}
this.scrollPane = new JScrollPane(calEditor);
getSplitPane().setPreferredSize(new Dimension(150, 100));
setOpaque(false);
setLayout(new BorderLayout(5, 5));
add(getSplitPane(), BorderLayout.CENTER);
add(buttonBox, BorderLayout.EAST);
add(statusLabel, BorderLayout.SOUTH);
codeAnalyser = new CodeAnalyser(owner.getTypeChecker(), moduleTypeInfo, false, false);
editorTransferHandler = new CodeGemEditor.EditorTextTransferHandler(getCALEditor().getTransferHandler(), codeAnalyser);
editorTransferHandler.setUserQualifiedIdentifiers(userResolvedAmbiguities);
getCALEditor().setTransferHandler(editorTransferHandler);
// Add popup menu providers
AdvancedCALEditor.IdentifierPopupMenuProvider popupProvider = new PopupProvider();
getCALEditor().setPopupMenuProvider(popupProvider);
qualificationsDisplay.setPopupMenuProvider(popupProvider);
// Add listeners to respond to changes in typed code text by updating qualifications
getCALEditor().getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
updateQualifications();
}
public void insertUpdate(DocumentEvent e) {
updateQualifications();
}
public void removeUpdate(DocumentEvent e) {
updateQualifications();
}
});
final AutoCompleteManager autoCompleteManager = new AutoCompleteManager(this, perspective);
getCALEditor().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// The gesture is control - space - a la Eclipse et al.
if ((e.isControlDown()) && (e.getKeyCode() == KeyEvent.VK_SPACE)) {
// Show the autocomplete popup just underneath the cursor
try {
autoCompleteManager.showCodeEditorPopupMenu();
} catch (AutoCompletePopupMenu.AutoCompleteException ex) {
setStatusMessage(NavigatorMessages.getString("NAV_NoAutocompleteAvailable_Message"));
}
}
}
});
// Add listener to respond to qualification panel events
qualificationsDisplay.addPanelEventListener(new QualificationsDisplay.PanelEventListener() {
/** Do nothing on icon double click since we do not allow arguments */
public void panelTypeIconDoubleClicked(QualificationPanel panel) {
}
/** Show menu at mouse position if module label double clicked */
public void panelModuleLabelDoubleClicked(QualificationPanel panel, Point mousePoint) {
JPopupMenu menu = qualificationsDisplay.getPopupMenuProvider().getPopupMenu(panel.getIdentifier());
if (menu != null) {
menu.show(qualificationsDisplay, mousePoint.x, mousePoint.y);
}
}
});
}
public void setPanelChangeListener(PanelChangeListener listener) {
this.panelChangeListener = listener;
}
/**
* @return split pane displaying cal editor and qualifications
*/
private JSplitPane getSplitPane() {
if (splitPane == null) {
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setName("ExampleEditorSplitPane1");
splitPane.setOneTouchExpandable(true);
splitPane.setRightComponent(scrollPane);
splitPane.setLeftComponent(getQualificationsDisplay());
splitPane.setDividerLocation(175);
}
return splitPane;
}
/**
* @return the cal editor component used to edit the expression text.
*/
public AdvancedCALEditor getCALEditor() {
return calEditor;
}
/**
* @return the qualifications display to edit code qualifications
*/
public QualificationsDisplay getQualificationsDisplay() {
if (qualificationsDisplay == null) {
qualificationsDisplay = new QualificationsDisplay(owner.getPerspective().getWorkspace());
}
return qualificationsDisplay;
}
/**
* Sets the expression text to display.
* @param text the expression text
*/
public void setExpressionText(String text) {
if (userResolvedAmbiguities == null) {
userResolvedAmbiguities = new CodeQualificationMap();
editorTransferHandler.setUserQualifiedIdentifiers(userResolvedAmbiguities);
}
calEditor.setText(text);
calEditor.setCaretPosition(0); // Ensure that the beginning of the text is visible
updateQualifications();
}
/**
* @return the expression text currently displayed
*/
public String getExpressionText() {
return calEditor.getText();
}
/**
* Adds a run button with the given tooltip and action listener
* @param toolTip the tooltip
* @param listener the action listener
*/
public void addRunButton(String toolTip, ActionListener listener) {
runButton.addActionListener(listener);
runButton.setToolTipText(toolTip);
buttonBox.add(runButton);
buttonBox.add(Box.createVerticalStrut(5));
}
/**
* Adds a delete button with the given tooltip and action listener
* @param toolTip the tooltip
* @param listener the action listener
*/
public void addDeleteButton(String toolTip, ActionListener listener) {
deleteButton.addActionListener(listener);
deleteButton.setToolTipText(toolTip);
buttonBox.add(deleteButton);
buttonBox.add(Box.createVerticalGlue());
}
/**
* Sets the status message to display in the status label.
* @param message the message to display
*/
public void setStatusMessage(String message) {
statusLabel.setForeground(Color.BLACK);
statusLabel.setText(message);
}
/**
* Runs the example stored in the editor and displays the result in the status label.
*/
public boolean runExpression() {
if (qualifiedExpressionText.equals("") && (calEditor.getText().length() > 0)) {
// Expression hasn't been qualified. Do this before running
updateQualifications();
}
CALExpression expression = new CALExpression(moduleTypeInfo.getModuleName(), calEditor.getText(), qualificationMap, qualifiedExpressionText);
StringBuilder result = new StringBuilder();
boolean isValid = EditorHelper.evaluateExpression(owner, expression, result);
statusLabel.setText(NavigatorMessages.getString("NAV_Result") + " " + result.toString());
statusLabel.setForeground(isValid ? Color.BLACK : Color.RED);
return isValid;
}
/**
* Analyses the editor code, updates qualification map, qualifies expression code,
* and updates the UI with the analysis results.
*/
public void updateQualifications() {
// Qualify expression
CompilerMessageLogger messageLogger = new MessageLogger();
CodeAnalyser.QualificationResults qualificationResults;
qualificationResults = codeAnalyser.qualifyExpression(calEditor.getText(), null, userResolvedAmbiguities, messageLogger);
if (qualificationResults == null) {
// Expression parsing failed.
qualifiedExpressionText = calEditor.getText();
qualificationMap = new CodeQualificationMap();
calEditor.setSourceIdentifiers(null);
calEditor.updateAmbiguityIndicators();
qualificationsDisplay.generateQualificationPanels(qualificationMap, null, moduleTypeInfo);
} else {
qualifiedExpressionText = qualificationResults.getQualifiedCode();
qualificationMap = qualificationResults.getQualificationMap();
calEditor.setSourceIdentifiers(qualificationResults.getAnalysedIdentifiers());
calEditor.updateAmbiguityIndicators();
qualificationsDisplay.generateQualificationPanels(qualificationMap, qualificationResults.getAnalysedIdentifiers(), moduleTypeInfo);
// Build up list of variable names for the chromacoder
List<String> variableNames = new ArrayList<String>();
for (final CodeAnalyser.AnalysedIdentifier identifier : qualificationResults.getAnalysedIdentifiers()) {
if (identifier.getCategory() == SourceIdentifier.Category.LOCAL_VARIABLE_DEFINITION &&
!variableNames.contains(identifier.getName())) {
variableNames.add(identifier.getName());
}
}
Collections.sort(variableNames);
String[] variableNamesArray = new String[variableNames.size()];
variableNames.toArray(variableNamesArray);
codeSyntaxListener.setLocalVariableNames(variableNamesArray);
}
notifyEditorChanged();
}
/**
* Carry out actions needed to insert the specified string into the editor
* from the auto-complete manager.
*
* @param backtrackLength
* @param insertion
*/
public void insertAutoCompleteString(int backtrackLength, String insertion) {
try {
// Remove text from editor
AdvancedCALEditor editor = getCALEditor();
int caretPosition = editor.getCaretPosition();
CodeAnalyser.AnalysedIdentifier identifier = editor.getIdentifierAtPosition(caretPosition);
editor.getDocument().remove (caretPosition - backtrackLength, backtrackLength);
if ((identifier == null) || (insertion.indexOf('.') < 0)) {
// Not a qualified (ie: ambiguous) insetion, or cannot locate identifier
// Do regular insert
editor.getDocument().insertString(caretPosition - backtrackLength, insertion, null);
return;
} else {
// Qualified completion, and type of the identifier is known
// So do a smart insert and update the qualification map
SourceIdentifier.Category identifierCategory = identifier.getCategory();
QualifiedName rawCompletedName = QualifiedName.makeFromCompoundName(insertion);
ModuleName moduleName = moduleTypeInfo.getModuleNameResolver().resolve(rawCompletedName.getModuleName()).getResolvedModuleName();
QualifiedName completedName = QualifiedName.make(moduleName, rawCompletedName.getUnqualifiedName());
CodeGemEditor.EditorTextTransferHandler.insertEditorQualification(getCALEditor(), completedName, identifierCategory, userResolvedAmbiguities, false);
}
} catch (BadLocationException e) {
throw new IllegalStateException("bad location on auto-complete insert");
}
}
/**
* @see org.openquark.gems.client.AutoCompleteManager.AutoCompleteEditor#getEditorComponent()
*/
public JTextComponent getEditorComponent() {
return getCALEditor();
}
/**
* Notifies panel listeners that the contents have changed
*/
private void notifyEditorChanged() {
// Notify the panel listeners of update
if (panelChangeListener != null) {
panelChangeListener.panelContentsChanged();
}
}
/**
* Set the module to use, and update code qualifications
* @param moduleName name of module to use for this expression
*/
public void setModuleName(ModuleName moduleName) {
this.moduleTypeInfo = owner.getPerspective().getMetaModule(moduleName).getTypeInfo();
// Create new code analyser and editor transfer handle, since they depend on the current module
codeAnalyser = new CodeAnalyser(owner.getTypeChecker(), moduleTypeInfo, false, false);
editorTransferHandler = new CodeGemEditor.EditorTextTransferHandler(getCALEditor().getTransferHandler(), codeAnalyser);
// Module change may affect visibility of qualifications in code, so update them
if (!calEditor.getText().equals("")) {
if (userResolvedAmbiguities == null) {
userResolvedAmbiguities = new CodeQualificationMap();
editorTransferHandler.setUserQualifiedIdentifiers(userResolvedAmbiguities);
}
updateQualifications();
} else {
notifyEditorChanged();
}
}
/**
* Set the qualification map to use for qualifying the expression
* @param qualificationMap
*/
public void setQualificationMap(CodeQualificationMap qualificationMap) {
this.qualificationMap = qualificationMap;
this.userResolvedAmbiguities = qualificationMap;
this.editorTransferHandler.setUserQualifiedIdentifiers(userResolvedAmbiguities);
}
/**
* @return the qualification map used to qualify expression
*/
public CodeQualificationMap getQualificationMap() {
return qualificationMap;
}
/**
* @return fully qualified expression text
*/
public String getQualifiedExpressionText() {
return qualifiedExpressionText;
}
}