/*
* 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.
*/
/*
* AlgebraicValueEditor.java
* Creation date: Oct 22, 2003
* By: Frank Worsley
*/
package org.openquark.gems.client.valueentry;
import java.awt.Adjustable;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.PolymorphicVarContext;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeVar;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous;
import org.openquark.cal.valuenode.DataConstructorValueNode;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.ToolTipHelpers;
/**
* A value editor for editing an algebraic type. It allows the user to see all supported data
* constructors of the algebraic type, select a data constructor and enter values for the
* data constructor arguments.
* @author Frank Worsley
*/
public class AlgebraicValueEditor extends StructuredValueEditor {
private static final long serialVersionUID = 3772409667881908123L;
/**
* A custom value editor provider for the AlgebraicValueEditor.
*/
public static class AlgebraicValueEditorProvider extends ValueEditorProvider<AlgebraicValueEditor> {
public AlgebraicValueEditorProvider(ValueEditorManager valueEditorManager) {
super(valueEditorManager);
}
/**
* {@inheritDoc}
*/
@Override
public boolean canHandleValue(ValueNode valueNode, SupportInfo providerSupportInfo) {
return valueNode instanceof DataConstructorValueNode &&
hasSupportedChildren((DataConstructorValueNode) valueNode, providerSupportInfo);
}
/**
* {@inheritDoc}
*/
@Override
public AlgebraicValueEditor getEditorInstance(ValueEditorHierarchyManager valueEditorHierarchyManager, ValueNode valueNode) {
AlgebraicValueEditor editor = new AlgebraicValueEditor(valueEditorHierarchyManager);
editor.setOwnerValueNode(valueNode);
return editor;
}
/**
* Checks if all children of a data constructor value node are supported by value editors.
* @param valueNode the data constructor value node
* @param supportInfo
* @return true if all children are supported by value editors
*/
private boolean hasSupportedChildren(DataConstructorValueNode valueNode, SupportInfo supportInfo) {
// Notify the info object that the value node's type is supported..
supportInfo.markSupported(valueNode.getTypeExpr());
ValueEditorManager valueEditorManager = getValueEditorManager();
// Return false if any of the children are unsupported.
for (final ValueNode childValueNode : valueNode.getChildrenList()) {
if (!valueEditorManager.isSupportedValueNode(childValueNode, supportInfo)) {
return false;
}
}
return true;
}
}
/**
* A listener that updates the values nodes of the child editors if a child editor is committed.
* @author Frank Worsley
*/
private class ChildValueEditorListener extends ValueEditorAdapter {
@Override
public void valueCommitted(ValueEditorEvent e) {
ValueEditor source = (ValueEditor) e.getSource();
ValueNode oldChildValueNode = e.getOldValue();
ValueNode newChildValueNode = source.getValueNode();
if (!isEditable() || oldChildValueNode.sameValue(newChildValueNode)) {
return;
}
// Get the commit value map and update the editor panel value nodes.
Map<ValueNode, TypeExpr> valueNodeToUnconstrainedTypeMap = getValueNodeToUnconstrainedTypeMap();
Map<ValueNode, ValueNode> commitValueMap = valueEditorManager.getValueNodeCommitHelper().getCommitValues(oldChildValueNode, newChildValueNode, valueNodeToUnconstrainedTypeMap);
for (final DataConstructorEditorPanel editorPanel : editorPanelList) {
editorPanel.updateValueNode(commitValueMap);
}
// Replace the current value node with the value node of the active panel.
ValueNode newValueNode = null;
if (editorPanelList.size() == 1) {
newValueNode = editorPanelList.get(0).getValueNode().copyValueNode();
} else {
newValueNode = focusChangeListener.getFocusedPanel().getValueNode().copyValueNode();
}
replaceValueNode(newValueNode, true);
refreshDisplay();
}
}
/**
* A property change listener that tracks the currently focused editor panel.
* It updates the focused look of the editor panels, if the focused panel changes.
* @author Frank Worsley
*/
private class FocusChangeListener implements PropertyChangeListener {
/** The currently focused editor panel. */
DataConstructorEditorPanel currentFocusedPanel = null;
/**
* @return the currently focused editor panel.
*/
public DataConstructorEditorPanel getFocusedPanel() {
return currentFocusedPanel;
}
public void setFocusedPanel(DataConstructorEditorPanel focusedPanel) {
if (currentFocusedPanel != null) {
currentFocusedPanel.setFocusedLook(false);
}
currentFocusedPanel = focusedPanel;
currentFocusedPanel.setFocusedLook(true);
}
/**
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
if (!isEditable()) {
return;
}
Component focusedValueEntryPanel = null;
Component newFocusOwner = (Component) evt.getNewValue();
while (newFocusOwner != null) {
if (newFocusOwner instanceof ValueEntryPanel) {
focusedValueEntryPanel = newFocusOwner;
}
if (newFocusOwner instanceof DataConstructorEditorPanel &&
((DataConstructorEditorPanel) newFocusOwner).getParentEditor() == AlgebraicValueEditor.this) {
if (newFocusOwner == currentFocusedPanel) {
// If there is a newly focused value entry panel, then scroll into view.
if (focusedValueEntryPanel != null) {
Rectangle bounds = SwingUtilities.convertRectangle(focusedValueEntryPanel.getParent(), focusedValueEntryPanel.getBounds(), currentFocusedPanel);
currentFocusedPanel.scrollRectToVisible(bounds);
}
return;
}
if (currentFocusedPanel != null) {
currentFocusedPanel.setFocusedLook(false);
}
currentFocusedPanel = (DataConstructorEditorPanel) newFocusOwner;
currentFocusedPanel.setFocusedLook(true);
return;
}
newFocusOwner = newFocusOwner.getParent();
}
}
}
/**
* A specialized panel for embedding the data constructor editor panels. It implements
* the scrollable interface to scroll the desired amount for data constructor editors.
* @author Frank Worsley
*/
private class EditorContentPanel extends JPanel implements Scrollable {
private static final long serialVersionUID = -137516687784049682L;
/**
* @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
*/
public boolean getScrollableTracksViewportHeight() {
return false;
}
/**
* @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
*/
public boolean getScrollableTracksViewportWidth() {
return false;
}
/**
* @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
*/
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
/**
* @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int)
*/
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 2 * getScrollableUnitIncrement(visibleRect, orientation, direction);
}
/**
* @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
*/
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
int editorHeight = editorPanelList.get(0).getPreferredSize().height;
int separatorHeight = (new JSeparator(SwingConstants.HORIZONTAL)).getPreferredSize().height;
if (orientation == SwingConstants.VERTICAL) {
return editorHeight + separatorHeight;
}
return 30;
}
}
/**
* A key listener for committing/canceling and navigating the editor panels.
* @author Frank Worsley
*/
private class EditorPanelKeyListener extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
int index = editorPanelList.indexOf(e.getSource());
if (keyCode == KeyEvent.VK_ENTER) {
handleCommitGesture();
e.consume();
} else if (keyCode == KeyEvent.VK_ESCAPE) {
handleCancelGesture();
e.consume();
} else if (keyCode == KeyEvent.VK_UP && index > 0) {
editorPanelList.get(index - 1).requestFocusInWindow();
e.consume();
} else if (keyCode == KeyEvent.VK_DOWN && index < editorPanelList.size() - 1) {
editorPanelList.get(index + 1).requestFocusInWindow();
e.consume();
}
}
}
/** The maximum initial width at which the editor should display. */
private static final int MAX_INITIAL_WIDTH = 600;
/** The maximum initial number of items the editor should display. */
private static final int MAX_INITIAL_ROWS = 10;
/** The list of data constructor editor panels. One panel for each supported constructor. */
private final List<DataConstructorEditorPanel> editorPanelList = new ArrayList<DataConstructorEditorPanel>();
/** The main panel that displays the controls of this editor. */
private final EditorContentPanel contentPanel = new EditorContentPanel();
/** The scroll pane for the content panel. */
private final JScrollPane contentScrollPane = new JScrollPane(contentPanel);
/** The focus change listener that updates the focused look of the editor panels. */
private final FocusChangeListener focusChangeListener = new FocusChangeListener();
/** The length in pixels of the data constructor with the longest name. */
private int longestNameLength = 0;
/**
* Constructs a new AlgebraicValueEditor.
* @param valueEditorHierarchyManager
*/
public AlgebraicValueEditor(ValueEditorHierarchyManager valueEditorHierarchyManager) {
super(valueEditorHierarchyManager);
setFocusCycleRoot(true);
setLayout(new BorderLayout());
setResizable(true);
add(contentScrollPane, BorderLayout.CENTER);
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
contentScrollPane.getHorizontalScrollBar().setCursor(Cursor.getDefaultCursor());
contentScrollPane.getVerticalScrollBar().setCursor(Cursor.getDefaultCursor());
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener("permanentFocusOwner", focusChangeListener);
}
/**
* Removes the focus change listener that is installed by this editor.
*/
private void removeFocusListener() {
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.removePropertyChangeListener("permanentFocusOwner", focusChangeListener);
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setEditable(boolean)
*/
@Override
public void setEditable(boolean editable) {
super.setEditable(editable);
for (final DataConstructorEditorPanel dataConstructorEditorPanel : editorPanelList) {
dataConstructorEditorPanel.setArgumentEditorsEditable(editable);
}
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#commitValue()
*/
@Override
public void commitValue() {
removeFocusListener();
// Make sure we are using the correct value node.
if (focusChangeListener.getFocusedPanel() != null) {
replaceValueNode(focusChangeListener.getFocusedPanel().getValueNode().copyValueNode(), true);
}
super.commitValue();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#cancelValue()
*/
@Override
public void cancelValue() {
removeFocusListener();
super.cancelValue();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#getDefaultFocusComponent()
*/
@Override
public Component getDefaultFocusComponent() {
return null;
}
/**
* @see org.openquark.gems.client.valueentry.StructuredValueEditor#handleElementLaunchingEditor()
*/
@Override
protected void handleElementLaunchingEditor() {
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#setInitialValue()
*/
@Override
public void setInitialValue() {
DataConstructorValueNode valueNode = getDataConstructorValueNode();
TypeExpr valueNodeTypeExpr = valueNode.getTypeExpr();
QualifiedName typeConstructorName = valueNode.getTypeExpr().rootTypeConsApp().getName();
DataConstructor[] dataConsList = valueEditorManager.getPerspective().getDataConstructorsForType(typeConstructorName);
DataConstructor currentDataCons = valueNode.getDataConstructor();
ValueEditorListener childListener = new ChildValueEditorListener();
KeyAdapter editorPanelKeyListener = new EditorPanelKeyListener();
boolean haveAddedPanel = false;
FontMetrics fontMetrics = getFontMetrics(getFont().deriveFont(Font.BOLD));
ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(valueEditorManager.getPerspective().getWorkingModuleTypeInfo());
// Remove components in case this is called twice.
contentPanel.removeAll();
// Create an editor panel for each supported data constructor.
for (final DataConstructor dataCons : dataConsList) {
DataConstructorValueNode newValueNode = null;
if (currentDataCons.getName().equals(dataCons.getName())) {
// If this is the current data constructor, just transmute the value node.
newValueNode = (DataConstructorValueNode) valueNode.transmuteValueNode(valueEditorManager.getValueNodeBuilderHelper(),
valueEditorManager.getValueNodeTransformer(),
valueNodeTypeExpr);
} else {
// Create a new value node. newValueNode will be null if the data constructor is not supported.
newValueNode = valueEditorManager.getValueNodeBuilderHelper().
getValueNodeProvider(DataConstructorValueNode.class).getNodeInstance(null, dataCons, valueNodeTypeExpr);
}
// Create a data constructor editor panel if the data constructor is supported.
if (newValueNode != null) {
DataConstructorEditorPanel editorPanel = new DataConstructorEditorPanel(this, newValueNode);
editorPanel.setFocusedLook(false);
editorPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
editorPanel.addArgumentEditorListener(childListener);
editorPanel.addKeyListener(editorPanelKeyListener);
if (haveAddedPanel) {
contentPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
}
editorPanelList.add(editorPanel);
contentPanel.add(editorPanel);
haveAddedPanel = true;
int titleLength = fontMetrics.stringWidth(dataCons.getAdaptedName(namingPolicy));
if (titleLength > longestNameLength) {
longestNameLength = titleLength;
}
}
}
// Make the width of all title labels the same so that the editor panels align.
for (final DataConstructorEditorPanel dataConstructorEditorPanel : editorPanelList) {
dataConstructorEditorPanel.setTitleLabelWidth(longestNameLength);
}
// If we have only one child editor, remove the focus listener.
// Also make the only editor's value entry panels visible, but give it the normal background.
if (editorPanelList.size() == 1) {
removeFocusListener();
DataConstructorEditorPanel childEditor = editorPanelList.get(0);
childEditor.setFocusedLook(false);
childEditor.setArgumentEditorsVisible(true);
}
resetSize();
}
/**
* Resets the current size and min/max sizes of this value editor.
*/
private void resetSize() {
validate();
// Figure out the height needed for various components.
int editorHeight = editorPanelList.get(0).getPreferredSize().height;
int separatorHeight = (new JSeparator(SwingConstants.HORIZONTAL)).getPreferredSize().height;
int scrollBarHeight = (new JScrollBar(Adjustable.HORIZONTAL)).getPreferredSize().height;
int scrollBarWidth = (new JScrollBar(Adjustable.VERTICAL)).getPreferredSize().width;
// Figure out the maximum initial height in pixels.
int maxInitialHeight = MAX_INITIAL_ROWS * editorHeight + (MAX_INITIAL_ROWS - 1) * separatorHeight;
Dimension prefSize = getPreferredSize();
int bestHeight = maxInitialHeight;
int bestWidth = prefSize.width;
if (editorPanelList.size() <= MAX_INITIAL_ROWS) {
bestHeight = prefSize.height;
} else {
bestWidth += scrollBarHeight;
prefSize.height += scrollBarHeight;
}
if (bestWidth > MAX_INITIAL_WIDTH) {
Dimension size = getSize();
if (size.width > MAX_INITIAL_WIDTH) {
bestWidth = size.width < prefSize.width ? size.width : prefSize.width;
} else {
bestWidth = MAX_INITIAL_WIDTH;
}
bestHeight += scrollBarWidth;
prefSize.height += scrollBarWidth;
}
// Setup the sizes of the editor.
Dimension bestSize = new Dimension(bestWidth, bestHeight);
setSize(bestSize);
setMaxResizeDimension(prefSize);
setMinResizeDimension(new Dimension(longestNameLength + scrollBarWidth + 30, editorHeight + scrollBarHeight));
revalidate();
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#editorActivated()
*/
@Override
public void editorActivated() {
DataConstructor dataCons = getDataConstructor();
for (final DataConstructorEditorPanel childEditor : editorPanelList) {
if (dataCons.getName().equals(childEditor.getDataConstructor().getName())) {
focusChangeListener.setFocusedPanel(childEditor);
break;
}
}
}
/**
* @see org.openquark.gems.client.valueentry.ValueEditor#refreshDisplay()
*/
@Override
public void refreshDisplay() {
PolymorphicVarContext polymorphicVarContext = PolymorphicVarContext.make();
for (final DataConstructorEditorPanel childEditor : editorPanelList) {
childEditor.refreshDisplay(polymorphicVarContext);
}
resetSize();
}
/**
* @return this editor's value node casted to a DataConstructorValueNode
*/
private DataConstructorValueNode getDataConstructorValueNode() {
return (DataConstructorValueNode) getValueNode();
}
/**
* @return the data constructor used by this editor's data constructor value node
*/
private DataConstructor getDataConstructor() {
return getDataConstructorValueNode().getDataConstructor();
}
/**
* Get a map from owner value node to type for the child editors used in this editor.
* @return Map For the owner value node of each child editor used by this editor.
*/
private Map<ValueNode, TypeExpr> getValueNodeToUnconstrainedTypeMap() {
Map<ValueNode, TypeExpr> returnMap = new HashMap<ValueNode, TypeExpr>();
TypeExpr leastConstrainedType = getContext().getLeastConstrainedTypeExpr();
if (leastConstrainedType.rootTypeVar() != null) {
leastConstrainedType = getDataConstructor().getTypeExpr().getResultType();
}
returnMap.put(getValueNode(), leastConstrainedType);
for (final DataConstructorEditorPanel childEditor : editorPanelList) {
returnMap.putAll(childEditor.getValueNodeToUnconstrainedTypeMap(leastConstrainedType));
}
return returnMap;
}
}
/**
* A panel that embeds value entry panels within itself and handles editing of
* data constructor argument values.
* @author Frank Worsley
*/
class DataConstructorEditorPanel extends JPanel {
private static final long serialVersionUID = -759944179399335050L;
/**
* A panel that embeds a value entry panel and a label. If the focused look is set then this
* panel will display the value entry panel. Otherwise it will display the place holder label.
* @author Frank Worsley
*/
private class ArgumentEditorPanel extends JPanel {
private static final long serialVersionUID = 2659046723149452021L;
/** The label we display instead of the value entry panel is the editor is not focused. */
private final JLabel placeHolderLabel = new JLabel();
/** The value entry panel for this argument editor panel. */
private final ValueEntryPanel valueEntryPanel;
public ArgumentEditorPanel(ValueEntryPanel valueEntryPanel) {
if (valueEntryPanel == null) {
throw new NullPointerException();
}
this.valueEntryPanel = valueEntryPanel;
setLayout(new BorderLayout());
setCursor(Cursor.getDefaultCursor());
setOpaque(true);
// Initially we are in the focused look and the VEP is shown.
add(valueEntryPanel);
// Setup the placeholder label.
placeHolderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
placeHolderLabel.setHorizontalAlignment(SwingConstants.CENTER);
placeHolderLabel.setFont(TYPE_LABEL_FONT);
placeHolderLabel.setForeground(Color.DARK_GRAY);
placeHolderLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
placeHolderLabel.setCursor(Cursor.getDefaultCursor());
// Activate the value entry panel this label is for is the user clicks on it.
placeHolderLabel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
valueEntryPanelToFocus = ArgumentEditorPanel.this.valueEntryPanel;
requestFocusInWindow();
}
});
// Finally make sure we only grow as big as the VEP needs to be.
setPreferredSize(valueEntryPanel.getPreferredSize());
setMaximumSize(getPreferredSize());
}
/**
* @return the value entry panel used by this argument panel.
*/
public ValueEntryPanel getValueEntryPanel() {
return valueEntryPanel;
}
/**
* Sets the focused look of this argument panel.
* @param focused whether to use the focused look
*/
public void setFocusedLook(boolean focused) {
setValueEntryPanelVisible(focused);
setBackground(focused ? FOCUSED_BACKGROUND_COLOR : NORMAL_BACKGROUND_COLOR);
}
/**
* Sets whether or not the argument value entry panels are visible.
* @param visible whether or not to show the VEPs
*/
public void setValueEntryPanelVisible(boolean visible) {
if (visible) {
remove(placeHolderLabel);
add(valueEntryPanel);
} else {
remove (valueEntryPanel);
add(placeHolderLabel);
}
setBorder(visible ? BorderFactory.createEmptyBorder(1, 1, 1, 1) : BorderFactory.createLineBorder(valueEntryPanel.getBackground(), 1));
revalidate();
}
/**
* Updates the text and tooltip of the placeholder label.
* @param polymorphicVarContext used for toString'ing the argument TypeExpr
*/
public void refreshDisplay(PolymorphicVarContext polymorphicVarContext) {
TypeExpr leastConstrainedType = valueEntryPanel.getContext().getLeastConstrainedTypeExpr();
TypeExpr actualType = valueEntryPanel.getValueNode().getTypeExpr();
// Show the more specialized of the two types..
if (actualType instanceof TypeVar) {
placeHolderLabel.setText(leastConstrainedType.toString(polymorphicVarContext, namingPolicy));
} else {
placeHolderLabel.setText(actualType.toString(polymorphicVarContext, namingPolicy));
}
if (getBorder() instanceof LineBorder) {
setBorder(BorderFactory.createLineBorder(valueEntryPanel.getBackground(), 1));
}
}
}
/** The font to use for labels that display type signatures. */
private static final Font TYPE_LABEL_FONT = Font.decode("courier-italic-12");
/** The normal background color of the editor panel. */
private static final Color NORMAL_BACKGROUND_COLOR = UIManager.getColor("Panel.background");
/** The normal foreground color of the editor panel. */
private static final Color NORMAL_FOREGROUND_COLOR = UIManager.getColor("Panel.foreground");
/** The focused background color of the editor panel. */
private static final Color FOCUSED_BACKGROUND_COLOR = UIManager.getColor("List.selectionBackground");
/** The focused foreground color of the editor panel. */
private static final Color FOCUSED_FOREGROUND_COLOR = UIManager.getColor("List.selectionForeground");
/** The normal border of the editor panel. */
private static final Border NORMAL_BORDER = BorderFactory.createLineBorder(NORMAL_BACKGROUND_COLOR, 2);
/** The focused border of the editor panel. */
private static final Border FOCUSED_BORDER = BorderFactory.createLineBorder(FOCUSED_BACKGROUND_COLOR, 2);
/** The naming policy used by this editor. */
private final ScopedEntityNamingPolicy namingPolicy;
/** The title label that displays the data constructor name. */
private final JLabel titleLabel = new JLabel();
/** The parent editor this panel belongs to. */
private final StructuredValueEditor parentEditor;
/** The array that holds on to the editor panels for the constructor arguments. */
private ArgumentEditorPanel[] editorPanels = null;
/** The value entry panel the user clicked on and that should be focused. */
private ValueEntryPanel valueEntryPanelToFocus = null;
/** The data constructor value node used by this editor panel. */
private DataConstructorValueNode valueNode = null;
/**
* Constructs a new editor panel.
* @param parentEditor the value editor this panel is for
* @param valueNode the value node this panel is for
*/
public DataConstructorEditorPanel(StructuredValueEditor parentEditor, DataConstructorValueNode valueNode) {
if (parentEditor == null || valueNode == null) {
throw new NullPointerException();
}
this.parentEditor = parentEditor;
this.valueNode = valueNode;
namingPolicy = new UnqualifiedUnlessAmbiguous(parentEditor.valueEditorManager.getPerspective().getWorkingModuleTypeInfo());
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setBackground(NORMAL_BACKGROUND_COLOR);
setBorder(NORMAL_BORDER);
setCursor(Cursor.getDefaultCursor());
setFocusable(true);
initialize();
}
/**
* Initializes the components of the editor panel.
*/
private void initialize() {
titleLabel.setText(getDataConstructor().getAdaptedName(namingPolicy));
titleLabel.setFont(getFont().deriveFont(Font.BOLD));
titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
titleLabel.setForeground(NORMAL_FOREGROUND_COLOR);
titleLabel.setCursor(Cursor.getDefaultCursor());
add(titleLabel);
Dimension prefSize = titleLabel.getPreferredSize();
titleLabel.setPreferredSize(new Dimension(prefSize.width, ValueEntryPanel.PANEL_HEIGHT));
List<ValueNode> childValueNodes = valueNode.getChildrenList();
int size = childValueNodes.size();
// Add an argument editor panel for each argument.
editorPanels = new ArgumentEditorPanel[size];
for (int i = 0; i < size; i++) {
ValueNode childValueNode = childValueNodes.get(i);
ValueEntryPanel vep = new ValueEntryPanel(parentEditor.valueEditorHierarchyManager);
vep.setContext(getValueEntryPanelContext(i));
vep.setParentValueEditor(parentEditor);
vep.setOwnerValueNode(childValueNode);
editorPanels[i] = new ArgumentEditorPanel(vep);
add(Box.createHorizontalStrut(5));
add(editorPanels[i]);
}
add(Box.createHorizontalGlue());
// Focus this panel if the user clicks on a component.
MouseAdapter focusMouseListener = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
requestFocusInWindow();
}
};
addMouseListener(focusMouseListener);
titleLabel.addMouseListener(focusMouseListener);
}
/**
* Update the tooltips. Note: we can only do this when the panel is parented,
* since the ToolTipHelpers functions need a Graphics context to make font size calculations.
* A graphics context is only available once a component has been parented.
* @param polymorphicVarContext used to toString() the TypeExpr of the arguments
*/
public void refreshDisplay(PolymorphicVarContext polymorphicVarContext) {
for (final ArgumentEditorPanel editorPanel : editorPanels) {
editorPanel.refreshDisplay(polymorphicVarContext);
}
DataConstructor dataConstructor = getDataConstructor();
titleLabel.setToolTipText(ToolTipHelpers.getEntityToolTip(dataConstructor, namingPolicy, parentEditor.valueEditorManager.getWorkspace(), this));
}
/**
* Sets the focused look of this editor panel. If the editor is focused it will display
* value entry panels for the data constructor argument values and use a different background
* color. If focused is true then the first value entry panel will be added to the editor hierarchy.
* @param focused whether to have the focused look
*/
public void setFocusedLook(boolean focused) {
setBorder(focused ? FOCUSED_BORDER : NORMAL_BORDER);
setBackground(focused ? FOCUSED_BACKGROUND_COLOR : NORMAL_BACKGROUND_COLOR);
titleLabel.setForeground(focused ? FOCUSED_FOREGROUND_COLOR : NORMAL_FOREGROUND_COLOR);
for (int i = 0; editorPanels != null && i < editorPanels.length; i++) {
editorPanels[i].setFocusedLook(focused);
}
if (focused && parentEditor.isEditable()) {
addChildEditorToHierarchy();
}
}
/**
* Sets whether or not the argument value entry panels are visible.
* @param visible whether or not to show the VEPs
*/
public void setArgumentEditorsVisible(boolean visible) {
for (int i = 0; editorPanels != null && i < editorPanels.length; i++) {
editorPanels[i].setValueEntryPanelVisible(visible);
}
if (visible) {
addChildEditorToHierarchy();
}
}
/**
* Sets the editable state of the argument editor panels.
* @param editable whether or not the argument editor panels should be editable
*/
public void setArgumentEditorsEditable(boolean editable) {
for (int i = 0; editorPanels != null && i < editorPanels.length; i++) {
editorPanels[i].getValueEntryPanel().setEditable(editable);
}
}
/**
* Adds the value entry panel that was clicked on to the value editor hierarchy. If no
* value entry panel was clicked on it adds the first value entry panel. If there are no
* value entry panels it simply requests focus.
* This method is called by setFocusedLook and setArgumentEditorsVisible.
*/
private void addChildEditorToHierarchy() {
if (valueEntryPanelToFocus == null && editorPanels.length > 0) {
valueEntryPanelToFocus = editorPanels[0].getValueEntryPanel();
}
if (valueEntryPanelToFocus != null) {
parentEditor.valueEditorHierarchyManager.addEditorToHierarchy(valueEntryPanelToFocus, parentEditor);
if (editorPanels.length > 0 && valueEntryPanelToFocus == editorPanels[0].getValueEntryPanel()) {
// If the first value entry panel is being focused, then scroll to the start of the
// editor panel, so that the data constructor name is visible.
scrollRectToVisible(new Rectangle(0, 0, 1, 1));
} else {
// Otherwise just scroll the value entry panel itself into view.
Rectangle bounds = SwingUtilities.convertRectangle(valueEntryPanelToFocus.getParent(), valueEntryPanelToFocus.getBounds(), this);
scrollRectToVisible(bounds);
}
valueEntryPanelToFocus = null;
} else {
// Scroll to the start of the panel so the data constructor name is visible.
scrollRectToVisible(new Rectangle(0, 0, 1, 1));
requestFocusInWindow();
}
}
/**
* Sets the preferred width of the title label. Use this function if you embed several
* data constructor editor panels and want to align them.
* @param width the new preferred width
*/
public void setTitleLabelWidth(int width) {
Dimension prefSize = titleLabel.getPreferredSize();
Insets insets = titleLabel.getInsets();
titleLabel.setPreferredSize(new Dimension(width + insets.left + insets.right,
prefSize.height + insets.top + insets.bottom));
}
/**
* Adds a listener to all value entry panels used to edit the data constructor argument values.
* @param listener the listener to add
*/
public void addArgumentEditorListener(ValueEditorListener listener) {
for (final ArgumentEditorPanel editorPanel : editorPanels) {
editorPanel.getValueEntryPanel().addValueEditorListener(listener);
}
}
/**
* @return the value editor context used by the value entry panels in this editor
* @param argNum the argument number that the panel is for
*/
private ValueEditorContext getValueEntryPanelContext(final int argNum) {
return new ValueEditorContext() {
public TypeExpr getLeastConstrainedTypeExpr() {
DataConstructor dataConstructor = getDataConstructor();
TypeExpr leastConstrainedParentType = parentEditor.getContext().getLeastConstrainedTypeExpr();
if (leastConstrainedParentType instanceof TypeVar) {
// The parent is parametric, that means the child can be whatever it wants.
return dataConstructor.getTypeExpr().getTypePieces()[argNum];
} else {
// The parent is not parametric, so the child must fit the parent constraint.
return TypeExpr.getComponentTypeExpr(leastConstrainedParentType, argNum, dataConstructor);
}
}
};
}
/**
* @return the data constructor used by this editor's data constructor value node
*/
public DataConstructor getDataConstructor() {
return valueNode.getDataConstructor();
}
/**
* @return the value node being edited by this panel
*/
public DataConstructorValueNode getValueNode() {
return valueNode;
}
/**
* @return the parent editor of this panel
*/
public StructuredValueEditor getParentEditor() {
return parentEditor;
}
/**
* Updates the value node of this panel and the child value nodes of its editor panels using
* the given map of committed values.
* Called by AlgebraicValueEditor's child commit listener.
* @param commitValueMap a committed value map from a ValueNodeCommitHelper
*/
void updateValueNode(Map<ValueNode, ValueNode> commitValueMap) {
// Get the new data constructor value node.
DataConstructorValueNode newValueNode = (DataConstructorValueNode) commitValueMap.get(valueNode);
if (newValueNode == null) {
newValueNode = (DataConstructorValueNode) valueNode.copyValueNode();
}
List<ValueNode> newValueNodeChildren = new ArrayList<ValueNode>(newValueNode.getChildrenList());
// Iterate over the argument panels and update their value nodes.
for (int i = 0; i < editorPanels.length; i++) {
ValueEntryPanel vep = editorPanels[i].getValueEntryPanel();
ValueNode oldChildValueNode = vep.getOwnerValueNode();
ValueNode newChildValueNode = commitValueMap.get(oldChildValueNode);
if (newChildValueNode != null) {
vep.changeOwnerValue(newChildValueNode);
vep.validate();
// The preferred size may have changed if the value node type changed.
editorPanels[i].setPreferredSize(vep.getPreferredSize());
// Make sure we have the correct value nodes in the children list.
newValueNodeChildren.set(i, newChildValueNode);
}
}
newValueNode.setChildrenList(newValueNodeChildren);
valueNode = newValueNode;
}
/**
* Builds a map from value node to type expression for the value node this panel is editing
* and the owner value nodes of each value entry panel.
* @param leastConstrainedType the least constrained type that is allowed
* @return the resulting map
*/
Map<ValueNode, TypeExpr> getValueNodeToUnconstrainedTypeMap(TypeExpr leastConstrainedType) {
Map<ValueNode, TypeExpr> returnMap = new HashMap<ValueNode, TypeExpr>();
DataConstructor dataCons = getDataConstructor();
returnMap.put(valueNode, leastConstrainedType);
for (int i = 0; i < editorPanels.length; i++) {
ValueNode argValueNode = editorPanels[i].getValueEntryPanel().getOwnerValueNode();
TypeExpr argType = TypeExpr.getComponentTypeExpr(leastConstrainedType, i, dataCons);
returnMap.put(argValueNode, argType);
}
return returnMap;
}
}