/*
* 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.
*/
/*
* TableTopExplorerAdapter.java
* Creation date: Jan 16th 2003
* By: Ken Wong
*/
package org.openquark.gems.client;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DragSource;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.font.FontRenderContext;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.undo.UndoableEditSupport;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.valuenode.ValueNode;
import org.openquark.gems.client.DisplayedGem.DisplayedPartConnectable;
import org.openquark.gems.client.DisplayedGem.DisplayedPartInput;
import org.openquark.gems.client.Gem.PartInput;
import org.openquark.gems.client.explorer.CALNameEditor;
import org.openquark.gems.client.explorer.ExplorerGemNameEditor;
import org.openquark.gems.client.explorer.ExplorerTree;
import org.openquark.gems.client.explorer.TableTopExplorer;
import org.openquark.gems.client.explorer.TableTopExplorerOwner;
import org.openquark.gems.client.utilities.ExtendedUndoableEditSupport;
import org.openquark.gems.client.utilities.MouseClickDragAdapter;
import org.openquark.gems.client.valueentry.MetadataRunner;
import org.openquark.gems.client.valueentry.ValueEditorContext;
import org.openquark.gems.client.valueentry.ValueEditorManager;
/**
* This class is the 'owner' of the TableTop Explorer used in the GemCutter. However, generally speaking,
* this class just piggybacks off of the tabletop for its functionality.
* @author Ken Wong
*/
class TableTopExplorerAdapter implements TableTopExplorerOwner, DisplayedGemStateListener {
/**
* A tree cell editor component for editing gem names.
* @author Frank Worsley
*/
private class CodeAndCollectorNameEditor extends CALNameEditor {
private static final long serialVersionUID = -1115390462294213694L;
private CodeAndCollectorNameEditor(Gem gem) {
super(gem);
}
/**
* Returns whether a name is a valid name for this field
* @param name String the name to check for validity
*/
@Override
protected boolean isValidName(String name) {
if (!(super.isValidName(name))) {
return false;
}
return isAvailableCodeOrCollectorName(name, getGem());
}
@Override
protected void renameGem(String newName, String oldName, boolean commit) {
Gem gem = getGem();
if (gem instanceof CodeGem) {
renameCodeGem((CodeGem) gem, newName, oldName, false);
} else if (gem instanceof CollectorGem) {
renameCollectorGem((CollectorGem) gem, newName, oldName, false);
}
}
@Override
protected FontRenderContext getFontRenderContext() {
return ((Graphics2D)tableTopExplorer.getGraphics()).getFontRenderContext();
}
}
/**
* A listener that adds support for manual burning and editing value gems. This
* is done by double-clicking on a gem or an input. This listener will also
* stop intellicut or possibly add a gem if the mouse is pressed.
*/
private class ExplorerMouseClickListener extends MouseClickDragAdapter {
@Override
public boolean mouseReallyClicked(MouseEvent e) {
boolean doubleClicked = super.mouseReallyClicked(e);
if (doubleClicked) {
Object userObject = tableTopExplorer.getExplorerTree().getUserObjectAt(e.getPoint());
if (userObject instanceof Gem.PartInput) {
doBurnInputAction((Gem.PartInput)userObject);
} else if (userObject instanceof ValueGem) {
if (userObject instanceof ValueGem) {
tableTopExplorer.editValueGem((ValueGem)userObject);
}
}
}
e.consume();
return doubleClicked;
}
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
gemCutter.getIntellicutManager().stopIntellicut();
maybeAddGem();
}
}
/**
* A listener for updating the drag icon as the user moves the mouse over the explorer tree.
* @author Frank Worsley
*/
private class ExplorerMouseMotionListener extends MouseMotionAdapter {
@Override
public void mouseMoved(MouseEvent e) {
if (gemCutter.getGUIState() == GemCutter.GUIState.ADD_GEM) {
Object userObject = tableTopExplorer.getExplorerTree().getUserObjectAt(e.getPoint(), (JComponent) e.getSource());
DisplayedGem addingDisplayedGem = gemCutter.getAddingDisplayedGem();
Gem addingGem = addingDisplayedGem == null ? null : addingDisplayedGem.getGem();
if (userObject instanceof Gem.PartInput) {
AutoburnLogic.AutoburnUnifyStatus burnStatus = null;
if (addingDisplayedGem != null) {
burnStatus = canConnect(addingGem.getOutputPart(), (Gem.PartInput) userObject);
}
if (addingDisplayedGem == null || burnStatus.isAutoConnectable()) {
gemCutter.getGlassPane().setCursor(GemCutter.getCursorForAddGem(addingGem));
} else {
gemCutter.getGlassPane().setCursor(DragSource.DefaultLinkNoDrop);
}
tableTopExplorer.selectInput((Gem.PartInput) userObject);
} else {
tableTopExplorer.selectRoot();
gemCutter.getGlassPane().setCursor(GemCutter.getCursorForAddGem(addingGem));
}
}
}
}
/** Our reference to the gemCutter */
private final GemCutter gemCutter;
/** The tableTopExplorer instance that this adapter owns */
private TableTopExplorer tableTopExplorer = null;
/**
* Default constructor for the TableTopExplorer Adapter
* @param gemCutter
*/
TableTopExplorerAdapter(GemCutter gemCutter) {
if (gemCutter == null) {
throw new NullPointerException();
}
this.gemCutter = gemCutter;
}
/**
* Returns the tableTopExplorer (create one if one doesn't exist)
* @return TableTopExplorer
*/
TableTopExplorer getTableTopExplorer() {
if (tableTopExplorer == null) {
tableTopExplorer = new TableTopExplorer(this, GemCutter.getResourceString("TTX_Root_Node"));
tableTopExplorer.getExplorerTree().addMouseListener(new ExplorerMouseClickListener());
tableTopExplorer.getExplorerTree().addMouseMotionListener(new ExplorerMouseMotionListener());
// Although technically the explorer tree can support multiple selection, in the GemCutter
// we only support single selection. This is because the TableTop has no concept of selected
// inputs and enabling multiple selection screws up Explorer-TableTop synchronization.
tableTopExplorer.getExplorerTree().getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
gemCutter.getTableTop().addGemGraphChangeListener(tableTopExplorer);
}
return tableTopExplorer;
}
/**
* @see TableTopExplorerOwner#getPopupMenu(Gem[])
*/
public JPopupMenu getPopupMenu(Gem[] gems) {
if (gemCutter.getGUIState() == GemCutter.GUIState.RUN) {
return gemCutter.getTableTopPanel().getRunModePopupMenu();
} else if (gemCutter.getGUIState() != GemCutter.GUIState.EDIT) {
return null;
}
if (gems.length == 1) {
// Display the same menu as the table top, if there is only one gem.
DisplayedGem dGem = gemCutter.getTableTop().getDisplayedGem(gems[0]);
return gemCutter.getTableTopPanel().getGemPopupMenu(dGem, false);
}
throw new UnsupportedOperationException();
}
/**
* @see TableTopExplorerOwner#getPopupMenu(Gem.PartInput[])
*/
public JPopupMenu getPopupMenu(Gem.PartInput[] inputs) {
if (gemCutter.getGUIState() == GemCutter.GUIState.RUN) {
return gemCutter.getTableTopPanel().getRunModePopupMenu();
} else if (gemCutter.getGUIState() != GemCutter.GUIState.EDIT) {
return null;
}
if (inputs.length == 1) {
// Display the same menu as the table top, if there is only one input.
DisplayedGem dGem = gemCutter.getTableTop().getDisplayedGem(inputs[0].getGem());
DisplayedPartInput dInput = dGem.getDisplayedInputPart(inputs[0].getInputNum());
JPopupMenu popupMenu = gemCutter.getTableTopPanel().getGemPartPopupMenu(dInput, false);
// Add our Intellicut menu item first.
popupMenu.add(GemCutter.makeNewMenuItem(getIntellicutAction(inputs[0])), 0);
popupMenu.add(new JSeparator(SwingConstants.HORIZONTAL), 1);
return popupMenu;
}
throw new UnsupportedOperationException();
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#getPopupMenu()
*/
public JPopupMenu getPopupMenu() {
if (gemCutter.getGUIState() == GemCutter.GUIState.RUN) {
return gemCutter.getTableTopPanel().getRunModePopupMenu();
} else if (gemCutter.getGUIState() != GemCutter.GUIState.EDIT){
return null;
}
JPopupMenu popupMenu = gemCutter.getTableTopPanel().getNonGemPopupMenu(false);
// Add our own "Add Gem" menu item first.
popupMenu.add(GemCutter.makeNewMenuItem(getAddGemAction()), 0);
return popupMenu;
}
/**
* @see TableTopExplorerOwner#selectGem(Gem, boolean)
*/
public void selectGem(Gem gem, boolean isSingleton) {
TableTop tableTop = gemCutter.getTableTop();
if (gem == null) {
tableTop.selectGem(null, true);
return;
}
DisplayedGem displayedGem = tableTop.getDisplayedGem(gem);
// we must do this check because sometimes the explorer and the tabletop get
// out of sync. And so the gem in the explorer might not be on the tabletop.
if (displayedGem != null) {
if (!tableTop.isSelected(displayedGem)) {
tableTop.selectGem(gem, isSingleton);
}
tableTop.setFocusedDisplayedGem(displayedGem);
gemCutter.getTableTopPanel().scrollRectToVisible(displayedGem.getBounds());
}
}
/**
* @see TableTopExplorerOwner#connect(Gem.PartOutput, Gem.PartInput)
*/
public void connect(Gem.PartOutput source, Gem.PartInput destination) {
AutoburnLogic.AutoburnUnifyStatus burnStatus = canConnect (source, destination);
// If we can burn unambiguously then do it
if (burnStatus.isUnambiguous()) {
gemCutter.getTableTop().getBurnManager().handleAutoburnGemGesture(source.getGem(), destination.getType(),true);
}
TableTop tableTop = gemCutter.getTableTop();
// for connection, we want to move the connected gem to an appropriate place on the tabletop too.
Gem sourceGem = source.getGem();
Gem destinationGem = destination.getGem();
if (!tableTop.getGemGraph().getGems().contains(sourceGem)) {
tableTop.doAddGemUserAction(tableTop.createDisplayedGem(sourceGem, new Point()));
}
Connection connection = tableTop.handleConnectGemPartsGesture(source, destination);
if (connection == null) {
return;
}
// so we cheat a bit =)
// we create a layout arranger object that allows us to do a tidy operation on only the source and destination
DisplayedGem dest = tableTop.getDisplayedGem(destinationGem);
Gem.PartInput[] inputs = destinationGem.getInputParts();
Map<DisplayedGem, Point> connectedGemsToLocation = new HashMap<DisplayedGem, Point>();
for (final PartInput input : inputs) {
if (input.isConnected()) {
DisplayedGem displayedGem = tableTop.getDisplayedGem(input.getConnection().getSource().getGem());
connectedGemsToLocation.put(displayedGem, displayedGem.getLocation());
}
}
DisplayedGem[] displayedGems = new DisplayedGem[connectedGemsToLocation.size() + 1];
connectedGemsToLocation.keySet().toArray(displayedGems);
displayedGems[connectedGemsToLocation.size()] = dest;
Graph.LayoutArranger layoutArranger = new Graph.LayoutArranger(displayedGems);
tableTop.doTidyUserAction(layoutArranger, dest);
for (int i = 0; i < displayedGems.length - 1; i++) {
Set<Gem> subTree = GemGraph.obtainSubTree(displayedGems[i].getGem());
subTree.remove(displayedGems[i].getGem());
Point oldLocation = connectedGemsToLocation.get(displayedGems[i]);
// Then we move the connected subtree of the source, so that it looks like the entire tree was moved.
Point newLocation = displayedGems[i].getLocation();
Point offset = new Point(newLocation.x - oldLocation.x, newLocation.y - oldLocation.y);
for (final Gem gem : subTree) {
DisplayedGem displayedGem = tableTop.getDisplayedGem(gem);
Point oldSpot = displayedGem.getLocation();
Point moveGemTo = new Point(oldSpot.x + offset.x, oldSpot.y + offset.y);
tableTop.doChangeGemLocationUserAction(displayedGem, moveGemTo);
}
}
}
/**
* Add a gem to the tabletop if appropriate. This should be called from mousePressed().
*/
private void maybeAddGem() {
if (gemCutter.getGUIState() == GemCutter.GUIState.ADD_GEM) {
ExplorerTree explorerTree = tableTopExplorer.getExplorerTree();
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) explorerTree.getLastSelectedPathComponent();
Object userObject = treeNode != null ? treeNode.getUserObject() : null;
if (gemCutter.getAddingDisplayedGem() == null) {
// This means we have to invoke Intellicut to add a gem.
if (treeNode == null) {
treeNode = (DefaultMutableTreeNode) explorerTree.getModel().getRoot();
explorerTree.setSelectionPath(new TreePath(treeNode.getPath()));
}
Rectangle location = getIntellicutLocation(treeNode);
DisplayedPartConnectable displayedPart = null;
if (userObject instanceof Gem.PartInput) {
displayedPart = gemCutter.getTableTop().getDisplayedPartConnectable((Gem.PartInput) userObject);
}
gemCutter.getIntellicutManager().startIntellicutMode(displayedPart, location, false, null, explorerTree);
gemCutter.enterGUIState(GemCutter.GUIState.EDIT);
} else {
// Here we're adding a concrete gem.
Gem.PartInput connectTo = null;
if (userObject instanceof Gem.PartInput) {
tableTopExplorer.selectInput((Gem.PartInput)userObject);
AutoburnLogic.AutoburnUnifyStatus burnStatus = canConnect(gemCutter.getAddingDisplayedGem().getGem().getOutputPart(), (Gem.PartInput)userObject);
if (burnStatus.isAutoConnectable()) {
connectTo = (Gem.PartInput) userObject;
} else {
return;
}
}
beginUndoableEdit();
DisplayedGem displayedGemToAdd = gemCutter.getAddingDisplayedGem();
Gem gemToAdd = displayedGemToAdd.getGem();
gemCutter.getTableTop().doAddGemUserAction(displayedGemToAdd);
gemCutter.enterGUIState(GemCutter.GUIState.EDIT);
if (gemToAdd instanceof CodeGem || gemToAdd instanceof CollectorGem) {
tableTopExplorer.renameGem(gemToAdd);
}
if (connectTo != null) {
connect(gemToAdd.getOutputPart(), connectTo);
setUndoableName(GemCutter.getResourceString("TTX_Undo_ConnectNew"));
} else {
setUndoableName(GemCutter.getResourceString("TTX_Undo_AddNew"));
}
endUndoableEdit();
}
}
}
/**
* @see TableTopExplorerOwner#disconnect(Connection)
*/
public void disconnect (Connection connection) {
TableTop tableTop = gemCutter.getTableTop();
tableTop.handleDisconnectGesture(connection);
tableTop.getBurnManager().doUnburnAutomaticallyBurnedInputsUserAction(connection.getSource().getGem());
}
/**
* @see TableTopExplorerOwner#canConnect(Gem.PartOutput, Gem.PartInput)
*/
public AutoburnLogic.AutoburnUnifyStatus canConnect(Gem.PartOutput source, Gem.PartInput destination) {
if ((source == null) || (destination == null) || (source.getGem().isBroken()) || destination.isBurnt()) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE;
} else if (!GemGraph.arePartsConnectableIfDisconnected(source, destination)) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE;
} else if (source.getGem() instanceof ValueGem){
if (GemGraph.isDefaultableValueGemSource(source, destination, gemCutter.getConnectionContext())) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY;
} else if (GemGraph.isCompositionConnectionValidIfDisconnected(source, destination, gemCutter.getTypeCheckInfo())) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY;
} else {
return AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE;
}
} else {
Connection connection = source.getConnection();
if (connection != null) {
source.bindConnection(null);
destination.bindConnection(null);
}
AutoburnLogic.AutoburnUnifyStatus autoburnStatus = AutoburnLogic.getAutoburnInfo(destination.getType(),
source.getGem(), gemCutter.getTypeCheckInfo()).getAutoburnUnifyStatus();
if (connection != null) {
source.bindConnection(connection);
connection.getDestination().bindConnection(connection);
}
return autoburnStatus;
}
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#canConnect(org.openquark.cal.services.GemEntity, org.openquark.gems.client.Gem.PartInput)
*/
public AutoburnLogic.AutoburnUnifyStatus canConnect(GemEntity source, Gem.PartInput destination) {
if (source == null) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE;
}
if (destination == null || destination.isBurnt()) {
return AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE;
}
return AutoburnLogic.getAutoburnInfo(destination.getType(), source, gemCutter.getTypeCheckInfo()).getAutoburnUnifyStatus();
}
/**
* @see TableTopExplorerOwner#getDeleteGemAction(Gem)
*/
public Action getDeleteGemAction(Gem gem) {
return null;
}
/**
* @see TableTopExplorerOwner#getRoots()
*/
public Set<Gem> getRoots() {
TableTop tableTop = gemCutter.getTableTop();
Set<Gem> roots = tableTop.getGemGraph().getRoots();
return roots;
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#addGems(GemEntity[])
*/
public Gem[] addGems(GemEntity[] gemEntities) {
TableTop tableTop = gemCutter.getTableTop();
Gem[] gems = new Gem[gemEntities.length];
DisplayedGem[] displayedGems = new DisplayedGem[gemEntities.length];
for (int i = 0; i < displayedGems.length; i ++ ) {
DisplayedGem displayedGem = tableTop.createDisplayedFunctionalAgentGem(new Point(), gemEntities[i]);
tableTop.doAddGemUserAction(displayedGem);
gems[i] = displayedGem.getGem();
displayedGems[i] = displayedGem;
}
// Use the layout arranger to line them up
Graph.LayoutArranger layoutArranger = new Graph.LayoutArranger(displayedGems);
tableTop.doTidyUserAction(layoutArranger, null);
return gems;
}
/**
* This method uses the gem cutter undoable support to begin a new edit operation. It must be matched by a
* call to endUndoableEdit()
*/
public void beginUndoableEdit() {
getUndoableEditSupport().beginUpdate();
}
/**
* This method uses the gem cutter undoable support to end an edit operation. It must be called after
* begineUndoableEdit()
*/
public void endUndoableEdit() {
getUndoableEditSupport().endUpdate();
}
/**
* Sets the name of the current undoable operation.
* @param editName String - The name to be used for the command (may be used in UI)
*/
public void setUndoableName(String editName) {
UndoableEditSupport undoableEditSupport = getUndoableEditSupport();
if (undoableEditSupport instanceof ExtendedUndoableEditSupport) {
((ExtendedUndoableEditSupport)undoableEditSupport).setEditName(editName);
}
}
/**
* see org.openquark.gems.client.explorer.TableTopExplorerOwner#getUndoableEditSupport()
*/
private UndoableEditSupport getUndoableEditSupport() {
return gemCutter.getTableTop().getUndoableEditSupport();
}
/**
* Burns this input
* @param input
* @return boolean
*/
public boolean doBurnInputAction(Gem.PartInput input){
TableTop tableTop = gemCutter.getTableTop();
DisplayedGem.DisplayedPart displayedPart = tableTop.getDisplayedPartConnectable(input);
return tableTop.getBurnManager().handleBurnInputGesture(displayedPart);
}
/**
* @see org.openquark.gems.client.DisplayedGemStateListener#runStateChanged(DisplayedGemStateEvent)
*/
public void runStateChanged(DisplayedGemStateEvent e) {}
/**
* @see org.openquark.gems.client.DisplayedGemStateListener#selectionStateChanged(DisplayedGemStateEvent)
* Currently not available since Explorer currently only supports single selection.
*/
public void selectionStateChanged(DisplayedGemStateEvent e) {
DisplayedGem displayedGem = (DisplayedGem) e.getSource();
// Only select the gem in the explorer if the user has not already selected one
// of the gem's inputs. This ensures that if the user clicks on an input of the gem
// the selection will stay on the input and not move to the gem itself.
if (gemCutter.getTableTop().isSelected(displayedGem)) {
DefaultMutableTreeNode gemNode = tableTopExplorer.getGemNode(displayedGem.getGem());
TreePath newSelectionPath = tableTopExplorer.getExplorerTree().getSelectionPath();
TreePath oldSelectionPath = tableTopExplorer.getOldSelectionPath();
TreePath pathToGemNode = gemNode != null ? new TreePath(gemNode.getPath()) : null;
if (pathToGemNode == null) {
// This means the gem was deleted and as a result its selection changed.
// In that case don't do anything at all.
return;
} else if (pathToGemNode.isDescendant(newSelectionPath)) {
// This happens if we have the root node selected and click on an input.
// In this case don't change the selection, since we want the input to stay selected.
return;
} else if (oldSelectionPath == null) {
tableTopExplorer.selectGem(displayedGem.getGem());
} else if (pathToGemNode.isDescendant(newSelectionPath)) {
// Do nothing.
} else if (!pathToGemNode.isDescendant(oldSelectionPath)) {
tableTopExplorer.selectGem(displayedGem.getGem());
} else {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) oldSelectionPath.getLastPathComponent();
if (!(selectedNode.getUserObject() instanceof Gem.PartInput)) {
tableTopExplorer.selectGem(displayedGem.getGem());
} else {
// Now make sure the input is actually selected. The selection state changes here are a little
// complicated. Assuming that originally a gem is selected in the tree, this happens:
//
// 1. First gem is de-selected when you click on the input of another gem in the tree.
// That causes this method to be called with the old gem (which now is not selected)
// and hence we clear the selection for the tree (the outermost else-if clause below).
//
// 2. Select the gem whose input was clicked. This will cause this method to be called again.
// This time with the new selected gem and the code will end up here.
//
// 3. Select the input in the tree (what we do below). This will again cause the new
// gem to be selected, but because it already is selected it will not fire another
// selectionStateChanged event. Therefore the code wont cause an infinite loop.
//
// Note that steps 1 & 2 happen as a result of gem selection changing in the selectGem() method
// which is called by the TableTopExplorer's TreeSelectionListener because you clicked the tree.
//
// Any questions? ;-)
tableTopExplorer.getExplorerTree().setSelectionPath(oldSelectionPath);
}
}
} else if (gemCutter.getTableTop().getSelectedGems().length == 0) {
// The gem may be selected, then the user clicks on the Table Top node
// and the gem will be unselected. Then we get here and say selectGem(null).
// That will clear *all* selection paths and will force the user to click the
// TableTop node twice to get it to stay selected. To prevent that we reselect
// the selected node if it is different from the gem node.
TreePath newSelectionPath = tableTopExplorer.getExplorerTree().getSelectionPath();
DefaultMutableTreeNode gemNode = tableTopExplorer.getGemNode(displayedGem.getGem());
TreePath gemNodePath = gemNode != null ? new TreePath(gemNode.getPath()) : null;
tableTopExplorer.selectGem(null);
if (newSelectionPath != null) {
Object userObject = ((DefaultMutableTreeNode) newSelectionPath.getLastPathComponent()).getUserObject();
boolean isGemInput = userObject instanceof Gem.PartInput && ((Gem.PartInput) userObject).getGem() == displayedGem.getGem();
if (gemNode == null || (!gemNodePath.equals(newSelectionPath) && !isGemInput)) {
tableTopExplorer.getExplorerTree().setSelectionPath(newSelectionPath);
}
}
}
}
/**
* @return the Action that adds a new gem to the table top
*/
private Action getAddGemAction() {
Action addGemAction = new AbstractAction (GemCutter.getResourceString("PopItem_AddGem"),
new ImageIcon(getClass().getResource("/Resources/addNewGem.gif"))) {
private static final long serialVersionUID = 5114481077769168106L;
public void actionPerformed(ActionEvent evt) {
ExplorerTree explorerTree = tableTopExplorer.getExplorerTree();
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) explorerTree.getLastSelectedPathComponent();
if (treeNode == null) {
treeNode = (DefaultMutableTreeNode) explorerTree.getModel().getRoot();
explorerTree.setSelectionPath(new TreePath(treeNode.getPath()));
}
Rectangle location = getIntellicutLocation(treeNode);
gemCutter.getIntellicutManager().startIntellicutMode(null, location, false, null, explorerTree);
}
};
addGemAction.putValue(Action.MNEMONIC_KEY, Integer.valueOf(GemCutterActionKeys.MNEMONIC_INTELLICUT));
addGemAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_INTELLICUT);
return addGemAction;
}
/**
* @param inputPart the part input to start intellicut for, or null if intellicut should be started
* for the table top
* @return the action for starting Intellicut for an input part of the table top
*/
private Action getIntellicutAction(final Gem.PartInput inputPart) {
Action intellicutAction = new AbstractAction(GemCutter.getResourceString("PopItem_Intellicut"),
new ImageIcon(getClass().getResource("/Resources/intellicut.gif"))) {
private static final long serialVersionUID = 4980938816525148088L;
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode treeNode = tableTopExplorer.getInputNode(inputPart);
DisplayedPartConnectable displayedPart = gemCutter.getTableTop().getDisplayedPartConnectable(inputPart);
Rectangle location = getIntellicutLocation(treeNode);
gemCutter.getIntellicutManager().startIntellicutMode(displayedPart, location, false, null, tableTopExplorer.getExplorerTree());
}
};
intellicutAction.setEnabled(inputPart == null || (!inputPart.isBurnt() && !inputPart.isConnected()));
intellicutAction.putValue(Action.ACCELERATOR_KEY, GemCutterActionKeys.ACCELERATOR_INTELLICUT);
return intellicutAction;
}
/**
* @param name the CAL name that we want to check if it can be assigned to the gem without causing
* a name conflict
* @param gem the gem which will be assigned the CAL name.
* @return true if the gem can be renamed to the specfied name without causing a name conflict. If
* the specified name is the same as the gems current name then true is returned since it can be
* assigned the same name without causing a conflict.
*/
public boolean isAvailableCodeOrCollectorName(String name, Gem gem) {
TableTop tableTop = gemCutter.getTableTop();
return tableTop.isAvailableCodeOrCollectorName(name, gem);
}
/**
* Used to rename a code gem. If the commit flag is true then the change is posted to the undoable
* edit support. If the commit flag is false then the change still occurs, but the edit is not
* posted. Also, if the newName and oldName are equivalent then then the edit is never posted
* regardless of the commit flag.
* @param codeGem CodeGem - The code gem to rename
* @param newName String - The new name for the gem
* @param oldName String - The old name for the gem
* @param commit boolean - Whether the undo support should be committed
*/
public void renameCodeGem(CodeGem codeGem, String newName, String oldName, boolean commit) {
TableTop tableTop = gemCutter.getTableTop();
tableTop.renameCodeGem(codeGem, newName);
// the text size wouldn't change on commit so no need to repaint let gems
// Notify the undo manager of the name change, if any
if (!codeGem.getUnqualifiedName().equals(oldName) && commit) {
getUndoableEditSupport().postEdit(new UndoableChangeCodeGemNameEdit(tableTop, codeGem, oldName));
}
}
/**
* Used to rename a collector gem. If the commit flag is true then the change is posted to the undoable
* edit support. If the commit flag is false then the change still occurs, but the edit is not
* posted. Also, if the newName and oldName are equivalent then then the edit is never posted
* regardless of the commit flag.
* @param collectorGem CollectorGem - The code gem to rename
* @param newName String - The new name for the gem
* @param oldName String - The old name for the gem
* @param commit boolean - Whether the undo support should be committed
*/
public void renameCollectorGem(CollectorGem collectorGem, String newName, String oldName, boolean commit) {
TableTop tableTop = gemCutter.getTableTop();
collectorGem.setName(newName);
tableTop.resizeForGems();
// The text size wouldn't change on commit so no need to repaint let gems
// Notify the undo manager of the name change, if any
if (!collectorGem.getUnqualifiedName().equals(oldName) && commit) {
getUndoableEditSupport().postEdit(new UndoableChangeCollectorNameEdit(tableTop, collectorGem, oldName));
}
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#isDNDEnabled()
*/
public boolean isDNDEnabled() {
return true;
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#getValueEditorManager()
*/
public ValueEditorManager getValueEditorManager() {
return gemCutter.getValueEditorManager();
}
/**
* {@inheritDoc}
*/
public ValueEditorContext getValueEditorContext(final ValueGem valueGem) {
return new ValueEditorContext() {
public TypeExpr getLeastConstrainedTypeExpr() {
return gemCutter.getTableTop().getGemGraph().getLeastConstrainedValueType(valueGem, gemCutter.getTypeCheckInfo());
}
};
}
/**
* {@inheritDoc}
*/
public ValueEditorContext getValueEditorContext(PartInput partInput) {
return null;
}
/**
* @see TableTopExplorerOwner#getValueNode(ValueGem)
*/
public ValueNode getValueNode(ValueGem valueGem){
return valueGem.getValueNode();
}
/**
* @see TableTopExplorerOwner#changeValueNode(ValueGem, ValueNode)
*/
public void changeValueNode(ValueGem valueGem, ValueNode valueNode) {
ValueNode oldValue = valueGem.getValueNode();
if (valueNode.sameValue(oldValue)) {
return;
}
gemCutter.getTableTop().handleValueGemCommitted(valueGem, oldValue, valueNode);
}
/**
* @see TableTopExplorerOwner#getHTMLFormattedMetadata(Gem.PartInput)
*/
public String getHTMLFormattedMetadata(Gem.PartInput input) {
TableTop tableTop = gemCutter.getTableTop();
ScopedEntityNamingPolicy namingPolicy = new ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous(tableTop.getCurrentModuleTypeInfo());
return ToolTipHelpers.getPartToolTip(input, tableTop.getGemGraph(), namingPolicy, tableTopExplorer.getExplorerTree());
}
/**
* @see TableTopExplorerOwner#getHTMLFormattedFunctionalAgentGemDescription(org.openquark.gems.client.FunctionalAgentGem)
*/
public String getHTMLFormattedFunctionalAgentGemDescription(FunctionalAgentGem gem) {
return ToolTipHelpers.getFunctionalAgentToolTip(gem, tableTopExplorer.getExplorerTree(), GemCutter.getLocaleFromPreferences());
}
/**
* @see TableTopExplorerOwner#getValueNode(Gem.PartInput)
*/
public ValueNode getValueNode(Gem.PartInput inputPart) {
return null;
}
/**
* @see TableTopExplorerOwner#canEditInputsAsValues()
*/
public boolean canEditInputsAsValues() {
return false;
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#highlightInput(Gem.PartInput)
*/
public boolean highlightInput(Gem.PartInput input) {
return false;
}
/**
* @see TableTopExplorerOwner#changeValueNode(Gem.PartInput, ValueNode)
*/
public void changeValueNode(Gem.PartInput partInput, ValueNode valueNode) {
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#hasPhotoLook()
*/
public boolean hasPhotoLook() {
return gemCutter.getTableTop().getTableTopPanel().isPhotoLook();
}
/**
* @param treeNode the tree node to get the intellicut position for
* @return the position at which intellicut should be displayed for a given node
*/
private Rectangle getIntellicutLocation(DefaultMutableTreeNode treeNode) {
ExplorerTree explorerTree = tableTopExplorer.getExplorerTree();
Rectangle location = explorerTree.getPathBounds(new TreePath(treeNode.getPath()));
if (treeNode == explorerTree.getModel().getRoot()) {
// For the root node we want to display Intellicut off to the right.
// This is so it doesn't obscure the rest of the tree.
location.x += location.width + 3;
location.y += 3;
location.height = 0;
location.width = 0;
} else {
// Normal nodes get the Intellicut list right below them.
location.x += 3;
location.y += location.height + 3;
location.width = 0;
location.height = 0;
}
return location;
}
/**
* This will display the intellicut menu if the explorer has focus and an input is selected.
* This is called by the GemCutter if the user activates the Intellicut keyboard shortcut.
* @return true if menu was displayed, false otherwise
*/
boolean maybeDisplayIntellicut() {
ExplorerTree explorerTree = getTableTopExplorer().getExplorerTree();
if (explorerTree.isFocusOwner()) {
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) explorerTree.getLastSelectedPathComponent();
if (treeNode == null) {
treeNode = (DefaultMutableTreeNode) explorerTree.getModel().getRoot();
}
Rectangle location = getIntellicutLocation(treeNode);
Object userObject = treeNode.getUserObject();
if (userObject instanceof Gem.PartInput) {
Gem.PartInput input = (Gem.PartInput) userObject;
if (input.isBurnt() || input.isConnected()) {
return false;
}
DisplayedPartConnectable displayedPart = gemCutter.getTableTop().getDisplayedPartConnectable(input);
gemCutter.getIntellicutManager().startIntellicutMode(displayedPart, location, false, null, explorerTree);
return true;
} else {
gemCutter.getIntellicutManager().startIntellicutMode(null, location, false, null, explorerTree);
return true;
}
}
return false;
}
/**
* @see org.openquark.gems.client.explorer.TableTopExplorerOwner#getCurrentModuleTypeInfo()
*/
public ModuleTypeInfo getCurrentModuleTypeInfo() {
return gemCutter.getPerspective().getWorkingModuleTypeInfo();
}
/**
* Returns a metadata runner for the specified gem. The metadata runner may be different for every
* gem so this method needs to be called everytime metadata needs to be run for a new gem.
* @param gem
* @return A helper object that can calculate metadata for the specified gem. This can be null if
* no metadata can be calculated for the specified gem.
*/
public MetadataRunner getMetadataRunner(Gem gem) {
return null;
}
/**
* Returns an editor customized for editing gem names for the current client
* @param gem
* @return A gem name editor customized for editing the specified gem
*/
public ExplorerGemNameEditor getGemNameEditor(Gem gem) {
return new CodeAndCollectorNameEditor(gem);
}
/**
* {@inheritDoc}
*/
public String getTypeString(final TypeExpr typeExpr) {
final ScopedEntityNamingPolicy namingPolicy;
final ModuleTypeInfo currentModuleTypeInfo = getCurrentModuleTypeInfo();
if (currentModuleTypeInfo == null) {
namingPolicy = ScopedEntityNamingPolicy.FULLY_QUALIFIED;
} else {
namingPolicy = new ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous(currentModuleTypeInfo);
}
return gemCutter.getTableTop().getGemGraph().getTypeString(typeExpr, namingPolicy);
}
}