/*
* 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.
*/
/*
* TableTopPanel.java
* Creation date: February 13th 2003
* By: Ken Wong
*/
package org.openquark.gems.client.explorer;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreePath;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.CodeGem;
import org.openquark.gems.client.CollectorGem;
import org.openquark.gems.client.FunctionalAgentGem;
import org.openquark.gems.client.Gem;
import org.openquark.gems.client.ValueGem;
import org.openquark.gems.client.ValueGemChangeEvent;
import org.openquark.gems.client.ValueGemChangeListener;
import org.openquark.gems.client.valueentry.ValueEditor;
import org.openquark.gems.client.valueentry.ValueEditorDirector;
import org.openquark.gems.client.valueentry.ValueEditorEvent;
import org.openquark.gems.client.valueentry.ValueEditorHierarchyManager;
import org.openquark.gems.client.valueentry.ValueEditorListener;
import org.openquark.gems.client.valueentry.ValueEntryPanel;
/**
* The cell editor for the explorer tree. Displays a text field to edit gem names or a value entry
* panel to edit value gem and part input values.
* @author Ken Wong
*/
class ExplorerTreeCellEditor implements TreeCellEditor {
/** The explorer owner that will provide all the logic for this IdentifierTextField */
private final TableTopExplorerOwner explorerOwner;
/** The TableTopExplorer where this field will appear */
private final TableTopExplorer tableTopExplorer;
/** List of cell editor listeners. Currently not used */
private final List<CellEditorListener> cellEditorListeners = new ArrayList<CellEditorListener>();
/** The component currently being used as the cell editor. */
private Component editorComponent = null;
/** The value gem, if applicable, currently being edited by the editor component */
private ValueGem editorValueGem = null;
/** The gem definition listener used when the editor component is editing a value gem */
private ValueGemChangeListener editorValueGemListener = null;
/** The user object currently being edited. */
private Object userObject = null;
/** The tree the editor component is for. */
private JTree tree = null;
/**
* Constructor for a new ExplorerTreeCellEditor.
* @param explorer the explorer this editor is for
*/
ExplorerTreeCellEditor(TableTopExplorer explorer) {
if (explorer == null) {
throw new NullPointerException();
}
this.tableTopExplorer = explorer;
this.explorerOwner = explorer.getExplorerOwner();
}
/**
* @see javax.swing.CellEditor#addCellEditorListener(CellEditorListener)
*/
public void addCellEditorListener (CellEditorListener cellEditorListener) {
cellEditorListeners.add(cellEditorListener);
}
/**
* @see org.openquark.gems.client.explorer.ExplorerTreeCellEditor#removeCellEditorListener(javax.swing.event.CellEditorListener)
*/
public void removeCellEditorListener(CellEditorListener l) {
cellEditorListeners.remove(l);
}
/**
* @see javax.swing.CellEditor#stopCellEditing()
*/
public boolean stopCellEditing() {
if (editorComponent instanceof ValueEditor) {
removeValueEditor((ValueEditor)editorComponent, true);
} else if (editorComponent instanceof ExplorerGemNameEditor) {
ExplorerGemNameEditor nameField = (ExplorerGemNameEditor) editorComponent;
nameField.stopEditing();
} else {
throw new IllegalStateException("invalid type of editor component: " + editorComponent);
}
return true;
}
/**
* @see javax.swing.CellEditor#cancelCellEditing()
*/
public void cancelCellEditing() {
if (editorComponent instanceof ValueEditor) {
removeValueEditor((ValueEditor)editorComponent, false);
} else if (editorComponent instanceof ExplorerGemNameEditor) {
((ExplorerGemNameEditor) editorComponent).cancelEditing();
} else {
throw new IllegalStateException("invalid type of editor component: " + editorComponent);
}
}
private void removeValueEditor(ValueEditor editorComponent, boolean commit) {
// Remove the gem definition listener if necessary
if (editorValueGem != null && editorValueGemListener != null) {
editorValueGem.removeValueChangeListener(editorValueGemListener);
editorValueGem = null;
editorValueGemListener = null;
}
ValueEditorHierarchyManager vehm = tableTopExplorer.getValueEditorHierarchyManager();
// Under some circumstances this method can be called twice.
// Therefore only remove the value editor if it really is managed by the hierarchy manager.
if (vehm.getTopValueEditors().contains(editorComponent)) {
vehm.removeValueEditor(editorComponent, commit);
}
}
/**
* @see javax.swing.CellEditor#getCellEditorValue()
*/
public Object getCellEditorValue() {
return userObject;
}
/**
* @see javax.swing.CellEditor#isCellEditable(EventObject)
*/
public boolean isCellEditable(EventObject e) {
TreePath path = tableTopExplorer.getExplorerTree().getSelectionPath();
if (path == null) {
return false;
}
DefaultMutableTreeNode defaultMutableTreeNode = (DefaultMutableTreeNode) path.getLastPathComponent();
Object userObject = defaultMutableTreeNode.getUserObject();
boolean hasValueEditorManager = explorerOwner.getValueEditorManager() != null;
if (userObject instanceof Gem.PartInput) {
Gem.PartInput input = (Gem.PartInput) userObject;
return hasValueEditorManager && !input.isBurnt() &&
explorerOwner.canEditInputsAsValues() &&
explorerOwner.getValueNode(input) != null;
}
return userObject instanceof CodeGem ||
userObject instanceof CollectorGem ||
(userObject instanceof ValueGem && hasValueEditorManager);
}
/**
* @see javax.swing.CellEditor#shouldSelectCell(EventObject)
*/
public boolean shouldSelectCell(EventObject anEvent) {
return false;
}
/**
* @see org.openquark.gems.client.explorer.ExplorerTreeCellEditor#getTreeCellEditorComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int)
*/
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
this.userObject = node.getUserObject();
this.tree = tree;
this.editorComponent = null;
if (userObject instanceof ValueGem) {
ValueGem valueGem = (ValueGem)userObject;
Gem connectedGem = valueGem.getOutputPart().getConnectedGem();
int connectedInputNum = valueGem.isConnected() ? valueGem.getOutputPart().getConnection().getDestination().getInputNum() : -1;
QualifiedName entityName = null;
if (connectedGem instanceof FunctionalAgentGem) {
entityName = ((FunctionalAgentGem)connectedGem).getName();
}
ValueEditorDirector ved = tableTopExplorer.getValueEditorDirector();
ValueEditor valueEditor = ved.getRootValueEditor(
tableTopExplorer.getValueEditorHierarchyManager(),
valueGem.getValueNode(),
entityName,
connectedInputNum,
connectedGem == null ? null : tableTopExplorer.getMetadataRunner(connectedGem));
setupEditorComponentForValueGem(valueEditor, valueGem);
setupValueEditor(node, valueEditor);
} else if (userObject instanceof Gem.PartInput) {
Gem.PartInput input = (Gem.PartInput)userObject;
Gem gem = input.getGem();
QualifiedName entityName = null;
if (gem instanceof FunctionalAgentGem) {
entityName = ((FunctionalAgentGem)gem).getName();
}
ValueEditorDirector ved = tableTopExplorer.getValueEditorDirector();
ValueEditor valueEditor = ved.getRootValueEditor(
tableTopExplorer.getValueEditorHierarchyManager(),
input.getType(),
entityName,
input.getInputNum(),
tableTopExplorer.getMetadataRunner(gem));
setupEditorComponentForPartInput(valueEditor, input);
setupValueEditor(node, valueEditor);
} else if (userObject instanceof CodeGem || userObject instanceof CollectorGem) {
final Gem editedGem = (Gem)userObject;
final ExplorerGemNameEditor nameEditor = explorerOwner.getGemNameEditor(editedGem);
editorComponent = nameEditor.getComponent();
// Add a focus listener to commit the changes when the field loses focus.
editorComponent.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
stopCellEditing();
tableTopExplorer.refreshForRename(editedGem);
}
});
// Add a key listener to commit the changes when the user presses 'Enter' and cancel on 'ESC'.
editorComponent.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) {
// Stop the cell editing which will commit any changes to the value. Follow this
// by ensuring the that JTree itself switches out of editing mode and refreshes
// to reflect any changes
stopCellEditing();
tableTopExplorer.getExplorerTree().stopEditing();
tableTopExplorer.refreshForRename(editedGem);
} else if (keyCode == KeyEvent.VK_ESCAPE) {
cancelCellEditing();
tableTopExplorer.refreshForRename(editedGem);
}
}
});
/* We can only get focus if the component is actually visible on
* the screen. It only gets created here, so it wont be visible
* until later. So we invoke requestFocus later once the
* component is visible.
*/
SwingUtilities.invokeLater(new Runnable() {
public void run() {
nameEditor.getComponent().requestFocusInWindow();
}
});
}
if (editorComponent != null) {
// We have to invoke this later since request focus has no effect
// if the component is not yet visible.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (editorComponent instanceof ValueEntryPanel &&
((ValueEntryPanel) editorComponent).getDefaultFocusComponent() != null) {
((ValueEntryPanel) editorComponent).getDefaultFocusComponent().requestFocusInWindow();
} else {
editorComponent.requestFocusInWindow();
}
}
});
}
return editorComponent;
}
/**
* Sets up a a value entry panel by adding it to the value editor hierarchy and configuring it to be displayed
* in the explorer tree as an editor.
* @param node the tree node the panel is editing
* @param valueEditor the panel to configure
*/
private void setupValueEditor(DefaultMutableTreeNode node, ValueEditor valueEditor) {
ValueEditorHierarchyManager valueEditorHierarchyManager = tableTopExplorer.getValueEditorHierarchyManager();
valueEditorHierarchyManager.addTopValueEditor(valueEditor);
// Make the panel as wide as the visible part of the tree.
Dimension preferredSize = valueEditor.getPreferredSize();
preferredSize.width = tree.getVisibleRect().width - tree.getPathBounds(new TreePath(node.getPath())).x - 50;
valueEditor.setPreferredSize(preferredSize);
valueEditor.revalidate();
}
private void setupEditorComponentForValueGem(final ValueEditor valueEditor, final ValueGem valueGem) {
// Set the current editor component to be the value editor specified
editorComponent = valueEditor;
editorValueGem = valueGem;
// Create and add a listener for value change events so we can cancel the editor
editorValueGemListener = new ValueGemChangeListener() {
public void valueChanged(ValueGemChangeEvent e) {
tree.cancelEditing();
}
};
valueGem.addValueChangeListener(editorValueGemListener);
// Create a listener so that we can do some extra work when committing the value
ValueEditorListener vel = new ValueEditorListener() {
public void valueChanged(ValueEditorEvent evt) {
}
public void valueCommitted(ValueEditorEvent evt) {
// HACK: We have to remove the listener so that it doesn't cancel editing
// as a result of our own change.
valueGem.removeValueChangeListener(editorValueGemListener);
ValueEditor valueEditor = ((ValueEditor)editorComponent);
ValueNode vn = valueEditor.getValueNode();
explorerOwner.changeValueNode(valueGem, vn);
valueEditor.changeOwnerValue(explorerOwner.getValueNode(valueGem));
valueGem.addValueChangeListener(editorValueGemListener);
}
public void valueCanceled(ValueEditorEvent evt) {
}
};
valueEditor.addValueEditorListener(vel);
valueEditor.setContext(explorerOwner.getValueEditorContext(valueGem));
}
private void setupEditorComponentForPartInput(final ValueEditor valueEditor, final Gem.PartInput input) {
// Set the current editor component to be the value editor specified
editorComponent = valueEditor;
// Get a value node for the input and set it into the value editor as an initial value
valueEditor.setOwnerValueNode(explorerOwner.getValueNode(input));
// Register a listener so that we can do some extra work when committing the value
valueEditor.addValueEditorListener(new ValueEditorListener() {
public void valueChanged(ValueEditorEvent evt) {
}
public void valueCommitted(ValueEditorEvent evt) {
explorerOwner.changeValueNode(input, ((ValueEditor)editorComponent).getValueNode());
ValueNode valueNode = explorerOwner.getValueNode(input);
if (valueNode != null) {
// todoFW: What is the correct behaviour in this case?
// Sometimes the returned value will be null.
// This should really never happen, but since the client
// restores the tree anyway it is ok, since we will never use this VEP again,
// so therefore we don't need to change the owner value node.
((ValueEditor)editorComponent).changeOwnerValue(valueNode);
}
}
public void valueCanceled(ValueEditorEvent evt) {
}
});
}
}