Package org.openquark.gems.client

Source Code of org.openquark.gems.client.TableTopExplorerAdapter

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

Related Classes of org.openquark.gems.client.TableTopExplorerAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.