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