Package org.openquark.gems.client.valueentry

Source Code of org.openquark.gems.client.valueentry.ValueEntryPanel$ValueEntryPanelKeyListener

/*
* 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.
*/


/*
* ValueEntryPanel.java
* Creation date: (1/11/01 7:29:38 AM)
* By: Luke Evans
*/
package org.openquark.gems.client.valueentry;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;

import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.PreludeTypeConstants;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.module.Cal.Utilities.CAL_RelativeTime;
import org.openquark.cal.module.Cal.Utilities.CAL_Time;
import org.openquark.cal.valuenode.ColourValueNode;
import org.openquark.cal.valuenode.JTimeValueNode;
import org.openquark.cal.valuenode.ListValueNode;
import org.openquark.cal.valuenode.RelativeDateTimeValueNode;
import org.openquark.cal.valuenode.RelativeDateValueNode;
import org.openquark.cal.valuenode.RelativeTimeValueNode;
import org.openquark.cal.valuenode.ValueNode;


/**
* ValueEntryPanel can collect a value of a specific type from the user.
* @author Luke Evans
*/
public class ValueEntryPanel extends ValueEditor {

    private static final long serialVersionUID = 7435038299787126535L;

    /** The height of the ValueEntryPanel. */
    public static final int PANEL_HEIGHT = 26;

    /**
     * Old ValueNode.
     * This is used to check for changes in the value represented by this editor.. */
    private ValueNode oldValueNode;

    /**
     * Flag to denote that the Value is in a change transition, and NOT to
     * fire changed value events.  This can be done at the end of the value transition.
     */
    private boolean isValueTransition;
   
    /** Flag to denote that the value field is gaining focus by way of a mouse press. */
    private boolean isMouseUsed = false;
   
    /** The name of the argument that this panel is collecting a value for. */
    private String argumentName = null;

    /** Indicates whether the type icon should be displayed. */
    private boolean displayTypeIcon = true;

    /** Indicates whether the launch editor button remains when the editor loses focus. */
    private boolean alwaysShowLaunchButton = true;
   
    /**
     * The custom key listener added to the value field for certain value node types.
     * We keep track of it here so it can be removed when the value node type changes.
     */
    private KeyListener valueFieldKeyListener = null;

    /**
     * A flag to denote whether or not the invokeLater method to display a value editor has run.
     * If this is true it has run, otherwise this is false and it is still pending.
     * While the flag is false, no editor can be launched by clicking the button or type icon.
     */
    private boolean hasDisplayed = true;

    /** The '...' button to launch a value editor. */
    private JButton ivjLaunchEditorButton = null;
   
    /** The type icon that launches the switch type value editor. */
    private JLabel ivjTypeIcon = null;
   
    /** The value field for displaying the value node text value. */
    private JTextComponent ivjValueField = null;

    /**
     * A ValueEntryPanel that mirrors another panel.
     * This panel can be used to edit the same value as the panel that it mirrors.
     * @author Edward Lam
     */
    public static class MirrorValueEntryPanel extends ValueEntryPanel {
       
        private static final long serialVersionUID = -3905172071121287511L;
       
        /** The panel mirrored by this panel */
        private final ValueEntryPanel mirroredPanel;
       
        /**
         * Constructor for a MirrorValueEntryPanel
         * @param valueEditorHierarchyManager
         * @param panelToMirror the panel to be mirrored.
         */
        public MirrorValueEntryPanel(ValueEditorHierarchyManager valueEditorHierarchyManager, ValueEntryPanel panelToMirror) {
            super(valueEditorHierarchyManager, panelToMirror.getOwnerValueNode());
            this.mirroredPanel = panelToMirror;

            // Add a listener to commit the mirrored panel when this panel is committed.
            addValueEditorListener(new ValueEditorAdapter() {
                @Override
                public void valueCommitted(ValueEditorEvent evt) {
                    MirrorValueEntryPanel.this.mirroredPanel.replaceValueNode(getValueNode(), true);
                    MirrorValueEntryPanel.this.mirroredPanel.commitValue();
                }
            });
           
            // Copy over other info.
            setContext(panelToMirror.getContext());
            setArgumentName(panelToMirror.argumentName);
        }
    }

    /**
     * A specialized mouse listener used for the purpose of activating the switching data type mechanism.
     */
    private class SwitchTypeMouseListener extends MouseAdapter {

        @Override
        public void mouseClicked(MouseEvent evt) {

            // If we can't switch data type then do nothing.
            if (!isEditable()) {
                return;
            }

            SwitchTypeValueEditor newEditor = getSwitchTypeValueEditor();
            newEditor.setOwnerValueNode(getValueNode());
           
            handleLaunchEditor(newEditor, getTypeIcon());
        }
    }
   
    /**
     * A mouse adapter to handle positioning the cursor when a value field is clicked.
     * Creation date: (08/14/2002 3:00:00 PM).
     * @author Steve Norton
     */
    private class ValueFieldMouseAdapter extends MouseAdapter {
       
        /** A flag to indicate that the mouse press has switched focus to the value field from another component. */
        private boolean valueFieldGainingFocus = false;
       
        @Override
        public void mousePressed(MouseEvent evt) {

            // See if the current focus owner is the value field. 
            // If it is NOT then we want to handle the caret positioning in on mouse click.
            Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
            valueFieldGainingFocus = (focusOwner != getValueField());
           
            if (valueFieldGainingFocus) {
                isMouseUsed = true;
            }
        }
       
        @Override
        public void mouseClicked(MouseEvent evt) {

            // If the value field is gaining focus for the first time because of this mouse click
            // then we want to position the caret.
            if (valueFieldGainingFocus) {
                // If the mouse is aimed at one end or the other then use the positioning function
                // otherwise just let the default behaviour do its thing.
                Point mouseLocation = evt.getPoint();
                int textIndex = getValueField().viewToModel(mouseLocation);

                if (textIndex <= 0 || textIndex >= getValueField().getDocument().getLength()) {
                    positionValueFieldCursor();
                    evt.consume();
                }
            }
        }
    }
   
    /**
     * A key listener that gets added to the value field if the value node is for an enumerated type.
     * @author Frank Worsley
     */
    private class EnumTypeKeyListener extends KeyAdapter {
       
        /** The list of value nodes for the data constructors of the type constructor. */
        private final List<ValueNode> valueNodeList = new ArrayList<ValueNode>();
       
        public EnumTypeKeyListener() {
           
            TypeExpr typeExpr = getValueNode().getTypeExpr();
            QualifiedName typeConsName = typeExpr.rootTypeConsApp().getName();
           
            if (!valueEditorManager.getPerspective().isEnumDataType(typeConsName)) {
                throw new IllegalStateException("type is not an enumerated type: " + typeConsName);
            }
           
            DataConstructor[] dataConsArray = valueEditorManager.getPerspective().getDataConstructorsForType(typeConsName);
           
            for (final DataConstructor dataConstructor : dataConsArray) {
               
                // We have to use the value node builder helper instead of building a data constructor value node.
                // That's because although Boolean is an enumerated type, it doesn't use DataConstructorValueNodes.
                valueNodeList.add(valueEditorManager.getValueNodeBuilderHelper().buildValueNode(null, dataConstructor, typeExpr));
            }
        }
       
        @Override
        public void keyPressed(KeyEvent e) {
           
            int current = -1;
           
            for (int i = 0, size = valueNodeList.size(); i < size; i++) {
                ValueNode valueNode = valueNodeList.get(i);
                if (valueNode.sameValue(getValueNode())) {
                    current = i;
                    break;
                }
            }
           
            if (current == -1) {
                throw new IllegalStateException("invalid current value: " + getValueNode());
            }
           
            EnumeratedValueEditor editor = (EnumeratedValueEditor) getValueEditor();
            editor.setHighlightedValue(getValueNode());
           
            int keyCode = e.getKeyCode();
           
            if (keyCode == KeyEvent.VK_UP && current > 0) {
               
                ValueNode newValue = valueNodeList.get(current - 1);
                editor.setOwnerValueNode(newValue);
                handleLaunchEditor(editor, getLaunchEditorButton());
                e.consume();
               
            } else if (keyCode == KeyEvent.VK_DOWN && current < valueNodeList.size() - 1) {
               
                ValueNode newValue = valueNodeList.get(current + 1);
                editor.setOwnerValueNode(newValue);
                handleLaunchEditor(editor, getLaunchEditorButton());
                e.consume();
           
            } else if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) {
                Toolkit.getDefaultToolkit().beep();
                e.consume();
            }
        }
    }
   
    /**
     * A key listener that gets added to the value field if the value type is Char.
     * @author Frank Worsley
     */
    private class CharTypeKeyListener extends KeyAdapter {
       
        @Override
        public void keyPressed(KeyEvent e) {
           
            if ((e.getKeyCode() != KeyEvent.VK_ENTER) && (e.getKeyCode() != KeyEvent.VK_ESCAPE) && !e.isAltDown()) {
               
                if (e.getKeyCode() != KeyEvent.VK_BACK_SPACE) {
                    setValueTransition(true);
                }
               
                // Clear away previous char (as long as there is a valid char to replace it).
                if (e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
                    getValueField().setText("");
                }
            }
        }
       
        @Override
        public void keyReleased(KeyEvent e) {
            setValueTransition(false);
        }
    }
   
    /**
     * A focus listener for the components of the Value Entry Panel.
     * @author Steve Norton
     */
    private class VEPFocusListener extends FocusAdapter {
       
        @Override
        public void focusGained(FocusEvent e) {
            if (!alwaysShowLaunchButton) {
                getLaunchEditorButton().setVisible(true);
            }

            // If the text field is gaining focus from some other component then the text field's
            // caret must be positioned. 
            JTextComponent textField = getValueField();
            if (e.getSource() == textField && e.getOppositeComponent() != textField) {

                // If the mouse was used to grant focus don't update the position as the mouse listener
                // would have already done it by now.
                if (!isMouseUsed) {
                    positionValueFieldCursor();
                }
            }
           
            // Reset the mouse used flag
            isMouseUsed = false;
        }

        @Override
        public void focusLost(FocusEvent evt) {
            if (!alwaysShowLaunchButton) {
                getLaunchEditorButton().setVisible(false);
            }
        }
    }
       
    /**
     * KeyListener used to listen for user's commit(Enter), cancel (Esc) and
     * launch editor (Alt+l) input.
     * Register the key listener on the textfield of this ValueEntryPanel.
     * Note: If the ValueEntryPanel which is registerd with this key listener
     * is on the first level, then commit and cancel does nothing.
     */
    private class ValueEntryPanelKeyListener extends KeyAdapter {

        @Override
        public void keyPressed(KeyEvent evt) {

            if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
                handleCommitGesture();
                evt.consume(); // Don't want the control with the focus to perform its action.

            } else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
                handleCancelGesture();
                evt.consume();

            } else if ((evt.getKeyCode() == KeyEvent.VK_L) && evt.isAltDown()) {
                getLaunchEditorButton().doClick();
                evt.consume();

            } else if ((evt.getKeyCode() == KeyEvent.VK_X) && evt.isControlDown()) {

                // Only allow if editable.
                if (isEditable()) {
                    cutToClipboard();
                }
                evt.consume();

            } else if ((evt.getKeyCode() == KeyEvent.VK_C) && evt.isControlDown()) {
                copyToClipboard();
                evt.consume();

            } else if ((evt.getKeyCode() == KeyEvent.VK_V) && evt.isControlDown()) {

                // Only allow if editable.
                if (isEditable()) {
                    pasteFromClipboard();
                }
                evt.consume();
            }
        }
    }

    /**
     * Creates a new ValueEntryPanel initialized with the data in ValueNode.
     * @param valueEditorHierarchyManager
     * @param dataVN
     */
    public ValueEntryPanel(ValueEditorHierarchyManager valueEditorHierarchyManager, ValueNode dataVN) {

        super(valueEditorHierarchyManager);
        initialize();

        // Configure this ValueEntryPanel with the params.
        setOwnerValueNode(dataVN);
        setInitialValue();
    }
   
    /**
     * Creates a new ValueEntryPanel.
     *   This editor will only be in a semi-initialized state. 
     *   setOwnerValueNode() and setInitialValue() must be called to complete initialization.
     * @param valueEditorHierarchyManager
     */
    ValueEntryPanel(ValueEditorHierarchyManager valueEditorHierarchyManager) {
        super(valueEditorHierarchyManager);
        initialize();
    }

    /**
     * Initialize the class.
     * Note: Extra Set-up code has been added.
     */
    private void initialize() {
        try {
            enableEvents(AWTEvent.FOCUS_EVENT_MASK | AWTEvent.HIERARCHY_EVENT_MASK);
            setName("ValueEntryPanel");
            setMaximumSize(new Dimension(65536, PANEL_HEIGHT));
            setMinimumSize(new Dimension(50, PANEL_HEIGHT));
            setPreferredSize(new Dimension(400, PANEL_HEIGHT));
            setSize(50, PANEL_HEIGHT);

            setBorder(valueEditorManager.getValueEditorBorder(this));

            setLayout(new BorderLayout());
            add(getTypeIcon(), "West");
            add(getLaunchEditorButton(), "East");
            add(getValueField(), "Center");
           
            getLaunchEditorButton().addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    ValueEditor editor;
                    if (isEditable()) {
                        editor = getValueEditor();
                       
                    } else {
                       
                        // Try to get outputable only editor
                        editor = valueEditorManager.getValueEditorForValueNode(valueEditorHierarchyManager, getValueNode(), true);
                        if (editor == null) {
                            // No outputable editor. Create string value node and edit this
                            ValueNode node = valueEditorManager.getValueNodeBuilderHelper().getValueNodeForTypeExpr(valueEditorManager.getTypeCheckInfo().getTypeChecker().getTypeFromString(CAL_Prelude.TypeConstructors.String.getQualifiedName(), CAL_Prelude.MODULE_NAME, null));
                            node.setOutputJavaValue(getValueNode().getTextValue());
                            editor = valueEditorManager.getValueEditorForValueNode(valueEditorHierarchyManager, node, true);
                        }
                    }
                    handleLaunchEditor(editor, getLaunchEditorButton());
                }
            });

        } catch (Throwable ivjExc) {
            handleException(ivjExc);
        }
       
        setDisplayTypeIcon(valueEditorManager.showValueEntryPanelTypeIcon());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Component getDefaultFocusComponent() {
        return getValueField();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void commitValue() {
        valueChangedCheck();
        notifyValueCommitted();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void cancelValue() {
        changeOwnerValue(getOwnerValueNode());
    }
   
    /**
     * Return the LaunchEditorButton property value.
     * @return JButton
     */
    protected JButton getLaunchEditorButton() {
        if (ivjLaunchEditorButton == null) {
            try {
                ivjLaunchEditorButton = new JButton();
                ivjLaunchEditorButton.setName("LaunchEditorButton");
                ivjLaunchEditorButton.setToolTipText("Launch Editor (Alt+L)");
                ivjLaunchEditorButton.setText("");
                ivjLaunchEditorButton.setIcon(new ImageIcon(getClass().getResource("/Resources/ellipsis.gif")));
                ivjLaunchEditorButton.setMargin(new Insets(0, 0, 0, 0));
                ivjLaunchEditorButton.setRequestFocusEnabled(false);
            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjLaunchEditorButton;
    }
   
    /**
     * Return the TypeIcon property value.
     * Note: Extra set-up code has been added.
     * @return JLabel
     */
    protected JLabel getTypeIcon() {
        if (ivjTypeIcon == null) {
            try {
                ivjTypeIcon = new JLabel();
                ivjTypeIcon.setName("TypeIcon");
                ivjTypeIcon.setIcon(new ImageIcon(getClass().getResource("/Resources/notype.gif")));

                // The value editor manager may indicate that we should not be showing borders
                if (valueEditorManager.useValueEntryPanelBorders()) {
                    ivjTypeIcon.setBorder(new EtchedBorder());
                } else {
                    ivjTypeIcon.setBorder(null);
                }

                ivjTypeIcon.setText("");

                ivjTypeIcon.addMouseListener(new SwitchTypeMouseListener());

            } catch (Throwable ivjExc) {
                handleException(ivjExc);
            }
        }
        return ivjTypeIcon;
    }
   
    /**
     * @return the value entry field for this value entry panel
     */
    protected JTextComponent getValueField() {
       
        if (ivjValueField == null) {

            TypeExpr typeExpr = getValueNode() != null ? getValueNode().getTypeExpr() : TypeExpr.makeParametricType();
           
            // Date and Time types get a special value field.
            if(typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDate) ||
               typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeTime) ||
               typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDateTime) ||
               typeExpr.isNonParametricType(CAL_Time.TypeConstructors.Time)) {
               
                ivjValueField = new DateTimeValueEntryField(this);
   
            } else {
               
                ivjValueField = new ValueEntryField(this);
               
                // Add a mouse listener that will help with positioning the cursor.
                ivjValueField.addMouseListener(new ValueFieldMouseAdapter());
            }
        
            // Make sure that the ValueFormat has this ValueEntryPanel set.
            // Otherwise, we may miss potential data change events.
            ValueFormat valueFormat = (ValueFormat) ivjValueField.getDocument();
            valueFormat.setChecking(true);
           
            ivjValueField.addKeyListener(new ValueEntryPanelKeyListener());
        }
       
        return ivjValueField;
    }
   
    /**
     * Updates the current value field for a new value node. This removes listeners added for
     * the type of the previous value node and performs other setup for the value field appearance.
     */
    private void updateValueField() {
       
        // Check if the value field needs to be replaced.
        JTextComponent oldValueField = ivjValueField;
        ivjValueField = null;
        JTextComponent newValueField = getValueField();
       
        if (oldValueField.getClass().getName().equals(newValueField.getClass().getName())) {
            ivjValueField = oldValueField;
        } else {
            remove(oldValueField);
            add(newValueField, BorderLayout.CENTER);
            revalidate();
        }
       
        TypeExpr typeExpr = getValueNode() != null ? getValueNode().getTypeExpr() : TypeExpr.makeParametricType();
       
        // If there's an argument name, pass it on to the value field.
        if (argumentName != null) {
            ((ValueEntryField) ivjValueField).setToolTipPrefix(argumentName + ":  ");
        }
       
        // Check if we should use a border.
        if (!valueEditorManager.useValueEntryPanelBorders()) {
            ivjValueField.setBorder(null);
        }
       
        // Remove the old key listener.
        ivjValueField.removeKeyListener(valueFieldKeyListener);
        valueFieldKeyListener = null;
       
        PreludeTypeConstants typeConstants = valueEditorManager.getValueNodeBuilderHelper().getPreludeTypeConstants();
       
        // Add a special key listener for certain types.
        if (typeExpr.sameType(typeConstants.getCharType())) {
           
            valueFieldKeyListener = new CharTypeKeyListener();
            ivjValueField.addKeyListener(valueFieldKeyListener);
       
        } else if (typeExpr.rootTypeConsApp() != null) {
           
            QualifiedName typeConsName = typeExpr.rootTypeConsApp().getName();
           
            if (valueEditorManager.getPerspective().isEnumDataType(typeConsName)) {
               
                valueFieldKeyListener = new EnumTypeKeyListener();
                ivjValueField.addKeyListener(valueFieldKeyListener);
            }
        }
    }

    /**
     * Returns a FontMetrics object set with the Font of the ValueField.
     * Creation date: (24/05/01 5:02:41 PM)
     * @return FontMetrics
     */
    public FontMetrics getValueFieldFontMetrics() {
        return getFontMetrics(getValueField().getFont());
    }
   
    /**
     * Launch the given value editor using the given component to determine the launch location.
     * When this is called, the hierarchy must be in an appropriate state to support the launch
     * of a child editor from this editor.
     * @param valueEditor the editor to launch
     * @param launcher the component launching the editor
     */
    protected void handleLaunchEditor(final ValueEditor valueEditor, final JComponent launcher) {

        if (!hasDisplayed) {
            return;
        }

        if (valueEditor != null) {
           
            if (ValueEntryPanel.this.getContext().getLeastConstrainedTypeExpr() == null) {
                JOptionPane.showMessageDialog(getTopLevelAncestor(),
                    ValueEditorMessages.getString("VE_ContextBroken"),
                    ValueEditorMessages.getString("VE_UnableToLaunchValueEditor"),
                    JOptionPane.INFORMATION_MESSAGE);
              return
            }
               
           
            // If editor is non-null, then launch it.
            hasDisplayed = false;

            SwingUtilities.invokeLater(new Runnable() {

                public void run() {

                    valueEditor.setContext(new ValueEditorContext() {
                        public TypeExpr getLeastConstrainedTypeExpr() {
                            return ValueEntryPanel.this.getContext().getLeastConstrainedTypeExpr();
                        }
                    });
                   
                    addChildCommitListener(valueEditor);

                    Insets editorInsets = valueEditor.getInsets();
                    Point location = launcher.getLocation();
                    location.x -= editorInsets.left;
                    location.y -= editorInsets.top;
                                       
                    valueEditorHierarchyManager.launchEditor(valueEditor, location, ValueEntryPanel.this);

                    hasDisplayed = true;
                }
            });

        } else {

            // If editor is null, display an error message informing the user that no editor was found.
            
            JOptionPane.showMessageDialog(getTopLevelAncestor(),
                                          ValueEditorMessages.getString("VE_NoValueEditor", getValueNode().getTypeExpr().toString()),
                                          ValueEditorMessages.getString("VE_UnableToLaunchValueEditor"),
                                          JOptionPane.INFORMATION_MESSAGE);
        }
    }
   
    /**
     * Add a listener to a new child editor so that commits to the child will also be committed to this editor.
     * @param childEditor the child editor to which to add the new listener
     */
    private void addChildCommitListener(final ValueEditor childEditor) {
        childEditor.addValueEditorListener(new ValueEditorAdapter(){
            @Override
            public void valueCommitted(ValueEditorEvent evt) {
               
                replaceValueNode(childEditor.getValueNode(), true);
               
                updateValueField();
               
                commitValue();
            }
        });
    }

    /**
     * Returns a value editor appropriate for this value node, or null if none is appropriate.
     */
    protected ValueEditor getValueEditor() {
        return valueEditorManager.getValueEditorForValueNode(valueEditorHierarchyManager, getValueNode(), false);
    }

    /**
     * Called whenever the part throws an exception.
     * @param exception Throwable
     */
    private void handleException(Throwable exception) {

        /* Uncomment the following lines to print uncaught exceptions to stdout */
        System.out.println("--------- UNCAUGHT EXCEPTION ---------");
        exception.printStackTrace(System.out);
    }

    /**
     * @see org.openquark.gems.client.valueentry.ValueEditor#refreshDisplay()
     */
    @Override
    public void refreshDisplay() {

        updateDisplayedValue();

        // Update colour.
        if (valueEditorManager.useTypeColour()) {
            Color typeColour = valueEditorManager.getTypeColour(getValueNode().getTypeExpr());
            setBackground(typeColour);
        }

        repaint();
    }
   
    /**
     * Notifies this ValueEntryPanel to update the value displayed in the text field with
     * the value in the ValueNode.
     */
    private void updateDisplayedValue() {

        // Handle this depending on type.
        ValueNode valueNode = getValueNode();
        TypeExpr typeExpr = valueNode.getTypeExpr();

        // Note: if this is set as output, then the value field will not be a
        // DateTimeValueEntryField, but a text field!
        if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDate)) {

            DateTimeValueEntryField dateTimeVef = (DateTimeValueEntryField) getValueField();
            dateTimeVef.setFormat(DateTimeValueEntryField.RELATIVEDATE);
            dateTimeVef.setDate(((RelativeDateValueNode)valueNode).getDateValue());

        } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeTime)) {

            DateTimeValueEntryField dateTimeVef = (DateTimeValueEntryField) getValueField();
            dateTimeVef.setFormat(DateTimeValueEntryField.RELATIVETIME);
            dateTimeVef.setDate(((RelativeTimeValueNode)valueNode).getTimeValue());

        } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDateTime)) {

            DateTimeValueEntryField dateTimeVef = (DateTimeValueEntryField) getValueField();
            dateTimeVef.setFormat(DateTimeValueEntryField.RELATIVEDATETIME);
            dateTimeVef.setDate(((RelativeDateTimeValueNode)valueNode).getDateTimeValue());

        } else if (typeExpr.isNonParametricType(CAL_Time.TypeConstructors.Time)) {

            DateTimeValueEntryField dateTimeVef = (DateTimeValueEntryField) getValueField();
            dateTimeVef.setFormat(DateTimeValueEntryField.JTIME);
            dateTimeVef.setDate(((JTimeValueNode)valueNode).getJavaDate());

        } else {

            // We do some special tool tip if the ValueNode is a ListValueNode.
            if (valueNode instanceof ListValueNode) {

                ListValueNode listValueNode = (ListValueNode)valueNode;
                String iconToolTip = "<i>" + valueEditorManager.getTypeName(typeExpr) + "</i>";
                int elementCount = listValueNode.getNElements();
                iconToolTip = iconToolTip + " (length " + elementCount + ")";
                getTypeIcon().setToolTipText("<html><body>" + iconToolTip + "</body></html>");

                // No break here.  Want to go with default stuff.
            }

            setTextValue(valueNode.getTextValue());
        }

       if (isEditable()) {
           getValueField().setForeground(valueEditorManager.isFieldEditable(typeExpr) ? Color.black : Color.gray);
       } else {
           getValueField().setForeground(Color.gray);
       }
       
        // Special handling for colour value nodes.
        if (valueNode instanceof ColourValueNode) {      

            ColourValueNode colourValueNode = (ColourValueNode) valueNode;

            Color colour = colourValueNode.getColourValue();
            Color opaqueColour = new Color(colour.getRed(), colour.getGreen(), colour.getBlue());           
            getValueField().setBackground(opaqueColour);
            getValueField().setForeground(opaqueColour);
            getValueField().setSelectedTextColor(opaqueColour);
            getValueField().setSelectionColor(opaqueColour);
            getValueField().setCaretColor(opaqueColour);
           
        } else {
            // Reset to default colours.
            JTextField textField = new JTextField();       // prototypical text field.
            getValueField().setBackground(textField.getBackground());
            getValueField().setForeground(textField.getForeground());
            getValueField().setSelectedTextColor(textField.getSelectedTextColor());
            getValueField().setSelectionColor(textField.getSelectionColor());
            getValueField().setCaretColor(textField.getCaretColor());
        }
       
        // Possible that the data value has changed.  Make a check.
        valueChangedCheck();
    }
   
    /**
     * Enables or disables the launch button based on value entry panel state.
     */
    private void updateLaunchButton() {
      
        // TODO: ValueEditorTableCell also has a version of this method; see if can
        // use only one.
       
        if (getValueNode() == null) {
            getLaunchEditorButton().setEnabled(false);

        } else if (isEditable()) {
            PreludeTypeConstants typeConstants = valueEditorManager.getValueNodeBuilderHelper().getPreludeTypeConstants();
            // TODO: Can this be removed or also used for numbers ?
            getLaunchEditorButton().setEnabled(!getValueNode().getTypeExpr().sameType(typeConstants.getCharType()));

        } else {
            getLaunchEditorButton().setEnabled(true);
        }
    }
   
    /**
     * @see org.openquark.gems.client.valueentry.ValueEditor#setEditable(boolean)
     */
    @Override
    public void setEditable(boolean isEditable) {

        super.setEditable(isEditable);

        // If we don't have a value node yet, then this will be done in
        // setOwnerValueNode and setInitialValue anyway.
        if (getValueNode() != null) {
            updatePreferredSize();
            updateDisplayedValue();
        }
       
        updateTypeIconColour();
        updateLaunchButton();
    }
   
    /**
     * @see org.openquark.gems.client.valueentry.ValueEditor#setInitialValue()
     */
    @Override
    public void setInitialValue() {
        updateDisplayedValue();
        updateLaunchButton();
    }
   
    /**
     * Set the name of the argument that this ValueEntryPanel is collecting a value for.
     * This is really only used as a prefix to the text field's tool tip.
     * @param name the name of the argument represented by this value entry panel.
     */
    public void setArgumentName(String name) {
       
        this.argumentName = name;
       
        JTextComponent textField = getValueField();
        if (textField instanceof ValueEntryField) {
            ((ValueEntryField)textField).setToolTipPrefix(name + ":  ");
        }
    }
   
    /**
     * Sets the value of the text field in this ValueEntryPanel.
     * Note: This method will automatically show the text at the left.
     * Note: Setting the text in the text field by this method will NOT affect the data in the
     * ValueNode of this ValueEntryPanel (so no data value changed events will fire).
     * Creation date: (23/01/01 4:31:58 PM)
     * @param newVal String
     */
    private void setTextValue(String newVal) {

        // First, allow the edit.
        ValueFormat valueFormat = (ValueFormat) getValueField().getDocument();
        valueFormat.setChecking(false);

        getValueField().setText(newVal);

        // Now, show the left part of the text.
        getValueField().setCaretPosition(0);

        // Now, restore checking.
        valueFormat.setChecking(true);
    }
   
    /**
     * Set the type icon of this ValueEntryPanel to the named icon.
     * Creation date: (1/12/01 9:42:51 AM)
     * @param iconName String
     */
    void setTypeIcon(String iconName) {

        // Set the type icon
        getTypeIcon().setIcon(new ImageIcon(getClass().getResource(iconName)));

        updateTypeIconColour();
    }
   
    /**
     * Sets the ValueNode for this ValueEntryPanelEditor and initializes some of the UI set-up.
     * @param newValueNode
     */
    @Override
    public void setOwnerValueNode(ValueNode newValueNode) {
       
        ValueNode oldOwnerValueNode = getOwnerValueNode();

        super.setOwnerValueNode(newValueNode);
        TypeExpr typeExpr = getValueNode().getTypeExpr();

        // if the value changed, give it a new value entry field.
        if (oldOwnerValueNode == null || !oldOwnerValueNode.sameValue(newValueNode)) {

            // Set-up this ValueEntryPanel with the data in the ValueNode.
            String iconName = valueEditorManager.getTypeIconName(typeExpr);
            this.setTypeIcon(iconName);
   
            // Show or hide the type icon, if necessary.
            setDisplayTypeIcon(valueEditorManager.showValueEntryPanelTypeIcon());

            // Recreate the value field to match the new type.
            updateValueField();
           
            // Register the focus listeners.
            Component[] componentList = this.getComponents();
            for (final Component component : componentList) {
                component.addFocusListener(new VEPFocusListener());
            }

            // Give the ValueEntryPanel a preferred starting size.
            updatePreferredSize();
        }

        getTypeIcon().setToolTipText("<html><body><i>" + valueEditorManager.getTypeName(typeExpr) + "</i></body></html>");

        if (valueEditorManager.useTypeColour()) {
            // Set the background to the type color, so that our etched border has the type color.
            setBackground(valueEditorManager.getTypeColour(newValueNode.getTypeExpr()));
        }
    }
   
    /**
     * Sets whether or not a value change check should be done.
     * If valueTransition is true, then we're in the middle of changing values,
     * and should not do the check.  The check will (should) be done at the end
     * of the valueTransition.  Don't forget to reset to false.
     * @param valueTransition
     */
    public void setValueTransition(boolean valueTransition) {
        isValueTransition = valueTransition;
    }
   
    /**
     * Sets the preferred size of the panel according to what is displayed in the value field.
     */
    private void updatePreferredSize() {
       
        int preferredWidth = valueEditorManager.getValuePreferredWidth(getValueNode(), getValueFieldFontMetrics(), isEditable());
        preferredWidth += getValueField().getInsets().left + getValueField().getInsets().right;
        preferredWidth += getLaunchEditorButton().getPreferredSize().width;
        preferredWidth += getTypeIcon().getPreferredSize().width;
        preferredWidth += getInsets().left + getInsets().right;
       
        Dimension newPreferredSize = new Dimension(preferredWidth, PANEL_HEIGHT);
        setPreferredSize(newPreferredSize);
    }
   
    /**
     * If editable sets the type icon colour to yellow, otherwise to light gray.
     */
    private void updateTypeIconColour() {

        if (isEditable()) {
            getTypeIcon().setBackground(Color.yellow);
        } else {
            getTypeIcon().setBackground(Color.lightGray);
        }
    }
   
    /**
     * Checks if a data change has occurred, and if it did, then fire ValueEditorEvents to all listeners.
     * Note: Currently, the def'n of a "Data value change" is if the old String CAL value in the ValueNode differs
     * with the new String CAL value in the ValueNode.
     */
    public void valueChangedCheck() {

        // If we are flagged that the value is in transition, don't bother checking.
        if (isValueTransition) {
            return;
        }

        // Special case: Upon first starting up, oldValueNode is null. 
        // Just set it to the current ValueNode.  And, there's no need to fire change event (since it's not possible in this early stage).
        if (oldValueNode == null) {
            oldValueNode = getValueNode().copyValueNode();
        }

        // Now perform the value change check.
        if (!oldValueNode.sameValue(getValueNode())) {

            notifyValueChanged(oldValueNode);
            oldValueNode = getValueNode().copyValueNode();
        }
    }
   
    /**
     * Position the cursor of the value field based on the type and editability of the value.
     */
    private void positionValueFieldCursor() {

        JTextComponent textField = getValueField();
        TypeExpr typeExpr = getValueNode().getTypeExpr();

        // Just position the cursor at the beginning of the document if the text is not editable.
        // Otherwise highlight all of the text.
        if (!isEditable() || !valueEditorManager.isTextEditable(typeExpr)) {

            textField.setCaretPosition(0);

        } else {

            Document doc = textField.getDocument();
            if (doc != null) {
                textField.setCaretPosition(getValueField().getDocument().getLength());
                textField.moveCaretPosition(0);
            }
        }
    }
   
    /**
     * Returns a value editor which can switch the value type.
     */
    protected SwitchTypeValueEditor getSwitchTypeValueEditor() {
        return new SwitchTypeValueEditor(valueEditorHierarchyManager);
    }

    /**
     * Returns whether the type icon should be displayed in the editor.
     * This can be overridden by subclasses to return True or False regardless of the
     * displayTypeIcon member flag.
     */
    public boolean displayTypeIcon() {
        return displayTypeIcon;
    }

    /**
     * Sets whether the type icon should be displayed in the editor.
     */
    private void setDisplayTypeIcon(boolean displayTypeIcon) {
        this.displayTypeIcon = displayTypeIcon;

        getTypeIcon().setVisible(displayTypeIcon());
    }

    /**
     * Sets whether the launch button is always displayed, or only when the editor has focus.
     */
    public void setAlwaysShowLaunchButton(boolean alwaysShowLaunchButton) {
        if (alwaysShowLaunchButton != this.alwaysShowLaunchButton) {
            this.alwaysShowLaunchButton = alwaysShowLaunchButton;

            if (alwaysShowLaunchButton) {
                getLaunchEditorButton().setVisible(true);

            } else {
                getLaunchEditorButton().setVisible(hasFocus());
            }
        }
    }

    /**
     * This is used by the TableTopGemPainter to draw a fake VEP image onto the tabletop.
     * The paint() method can't be used directly since it results in weird drawing artifacts on the table top.
     * @param g the Graphics object to draw with
     */   
    public void paintOnTableTop(Graphics g) {
        paintComponent(g);
        paintBorder(g);
        paintChildren(g);
    }
   
    /**
     * We override this to set the caret color to white and deselect all text
     * when the VEP is hidden. This effectively makes the caret invisible
     * This is so that the TableTopGemPainter can paint the fake VEP ghost
     * image without the caret being visible, making it look like the text
     * field is non-focused.
     * @param visible whether or not the VEP should be visible
     */
    @Override
    public void setVisible(boolean visible) {
       
        if (visible) {
            getValueField().setCaretColor(Color.BLACK);
        } else {
            getValueField().setSelectionStart(0);
            getValueField().setSelectionEnd(0);
            getValueField().setCaretColor(Color.WHITE);
            getValueField().setCaretPosition(getValueField().getText().length());
        }
       
        super.setVisible(visible);
    }
   
    /**
     * This is used by the TableTop to determine the correct tooltip text to
     * use when the mouse is hovering over a certain part of a VEP ghost image.
     * @param location the location for which to get the tooltip text
     * @return the tooltip text for the child component at the given location
     */
    public String getToolTipText(Point location) {
       
        JComponent child = (JComponent) getComponentAt(location);
       
        if (child != null) {
            return child.getToolTipText();
        }
       
        return null;
    }
   
    /**
     * This is used by the TableTop to determine the correct cursor to use
     * when the mouse is hovering over a certain part of the VEP ghost image.
     * @param location the location for which to get a cursor
     * @return Cursor the cursor to use for the given location
     */
    public Cursor getCursor(Point location) {
       
        JComponent child = (JComponent) getComponentAt(location);
       
        if (child == getValueField()) {
            return Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
        }
       
        return Cursor.getDefaultCursor();
    }
}
TOP

Related Classes of org.openquark.gems.client.valueentry.ValueEntryPanel$ValueEntryPanelKeyListener

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.