Package org.openquark.gems.client.navigator

Source Code of org.openquark.gems.client.navigator.ExpressionPanel

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

Related Classes of org.openquark.gems.client.navigator.ExpressionPanel

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