All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.netbeans.visual.NodeScene Maven / Gradle / Ivy

The newest version!
/***********************************************************************************************************************
 *
 * OpenBlueSky - NetBeans Platform Enhancements
 * Copyright (C) 2006-2012 by Tidalwave s.a.s. (http://www.tidalwave.it)
 *
 ***********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 ***********************************************************************************************************************
 *
 * WWW: http://openbluesky.java.net
 * SCM: https://bitbucket.org/tidalwave/openbluesky-src
 *
 **********************************************************************************************************************/
package it.tidalwave.netbeans.visual;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.io.Serializable;
import java.awt.Point;
import java.awt.Rectangle;
import org.openide.util.Parameters;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.explorer.ExplorerManager;
import org.netbeans.api.visual.anchor.AnchorFactory;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.MoveProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.action.WidgetAction.Chain;
import org.netbeans.api.visual.graph.GraphScene;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Scene;
import org.netbeans.api.visual.widget.Widget;
import static org.netbeans.api.visual.model.ObjectSceneEventType.*;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.netbeans.visual.action.DefaultMoveStrategy;
import it.tidalwave.netbeans.visual.impl.ConnectionWidgetAnchorFixer;
import it.tidalwave.netbeans.visual.impl.PopupMenuBridge;
import it.tidalwave.netbeans.visual.impl.SelectionHandler;
import it.tidalwave.netbeans.visual.impl.WidgetMoveProviderAdapter;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
public class NodeScene extends GraphScene implements ExplorerManager.Provider
  {
    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Immutable
    public static class DragEvent implements Serializable
      {
        @Nonnull
        private final Node node;

        private final Widget widget;

        private final Point location;

        public DragEvent (final @Nonnull Node node,
                          final @Nonnull Widget widget,
                          final @Nonnull Point location)
          {
            this.node = node;
            this.widget = widget;
            this.location = location;
          }

        @Nonnull
        public Point getLocation()
          {
            return location;
          }

        @Nonnull
        public Node getNode()
          {
            return node;
          }

        @Nonnull
        public Widget getWidget()
          {
            return widget;
          }
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public static interface DragBehaviour extends Serializable
      {
        public enum Confirmation
          {
            CONFIRMED, CANCELLED
          }

        /***************************************************************************************************************
         *
         * Invoked when a drag operation is started on a {@link Node}.
         *
         * @param  node  the node
         *
         **************************************************************************************************************/
        public void nodeDragStarted (@Nonnull DragEvent dragEvent);

        /***************************************************************************************************************
         *
         * Invoked during a drag operation on a {@link Node}.
         *
         * @param  node      the node
         * @param  location  the new location where the node is dragged
         *
         **************************************************************************************************************/
        public void nodeDragged (@Nonnull DragEvent dragEvent);

        /***************************************************************************************************************
         *
         * Invoked when a drag operation is terminated on a {@link Node}.
         *
         * @param  node  the node
         *
         **************************************************************************************************************/
        public void nodeDragFinished (@Nonnull DragEvent dragEvent, @Nonnull Confirmation confirmation);

        /***************************************************************************************************************
         *
         * @param  node  the node
         *
         **************************************************************************************************************/
        @Nonnull
        public Confirmation confirmDrag (@Nonnull DragEvent dragEvent);
      }

    private final static String CLASS = NodeScene.class.getName();
    private final static Logger logger = Logger.getLogger(CLASS);
    
    private final LayerWidget mainLayer = new LayerWidget(this);
    
    private final LayerWidget connectionLayer = new LayerWidget(this);

    private final IdentityHashMap initialLocationMapByNode = new IdentityHashMap();
    
    private final Scene.SceneListener connectionWidgetAnchorFixer = new ConnectionWidgetAnchorFixer(mainLayer);

    private final Node rootNode = new AbstractNode(new Children.Array());

    private final WidgetAction selectAction = createSelectAction();

    private final DefaultMoveStrategy defaultMoveStrategy = new DefaultMoveStrategy();

    private final ExplorerManager explorerManager = new ExplorerManager();

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public NodeScene()
      {
        addChild(mainLayer);
        addChild(connectionLayer);

        setOpaque(false); // or the background won't be visible

        getActions().addAction(ActionFactory.createZoomAction());
        // Let Widgets slip into negative values withouht expanding the Scene
        setMaximumBounds(new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));
        addSceneListener(connectionWidgetAnchorFixer);
        addObjectSceneListener(new SelectionHandler(explorerManager), OBJECT_SELECTION_CHANGED, OBJECT_STATE_CHANGED);
        explorerManager.setRootContext(rootNode);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public ExplorerManager getExplorerManager()
      {
        return explorerManager;
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    public Node getRootNode()
      {
        return rootNode;
      }

    /*******************************************************************************************************************
     *
     * Adds a {@code Node}, placing the related {@link Widget} at the given initial location.
     *
     * @param  node              the {@code Node}
     * @param  initialLocation   the initial location
     *
     ******************************************************************************************************************/
    public void addNode (@Nonnull Node node, final @Nonnull Point initialLocation)
      {
        initialLocationMapByNode.put(node, initialLocation);
        addNode(node);
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    public void removeAllNodes()
      {
        logger.fine("removeAllNodes()");

        connectionLayer.removeChildren();
        mainLayer.removeChildren();

        for (final Object object : new ArrayList(getObjects()))
          {
            final Node node = (Node)object;
            rootNode.getChildren().remove(new Node[]{node});

            for (final Widget widget : findWidgets(node))
              {
                widget.removeFromParent();
              }

            removeObject(node);
          }
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    protected Widget attachNodeWidget (final @Nonnull Node node)
      {
        logger.finer("attachNodeWidget(%s)", node);
        final Point initialLocation = initialLocationMapByNode.remove(node); // always consume it
        final NodeWidgetFactory nodeWidgetFactory = node.getLookup().lookup(NodeWidgetFactory.class);
        Parameters.notNull("No NodeWidgetFactory in Node's Lookup", nodeWidgetFactory);
        logger.finest(">>>> initialLocation: %s, nodeWidgetFactory: %s", initialLocation, nodeWidgetFactory);

        rootNode.getChildren().add(new Node[]{node.cloneNode()});

        final Collection widgets = nodeWidgetFactory.createWidgets(this, node);

        // Add them in reverse order, as the first one is the main and should be drawn over the others
        final List tmp = new ArrayList(widgets);
        Collections.reverse(tmp);
        
        final WidgetAction createPopupMenuAction = ActionFactory.createPopupMenuAction(new PopupMenuBridge(node));

        for (final Widget widget : tmp)
          {
            logger.finest(">>>> adding widget: %s", widget);
            mainLayer.addChild(widget);
            
            if (initialLocation != null)
              {
                widget.setPreferredLocation(convertViewToLocal(initialLocation));
              }
            
            final Chain actions = widget.getActions();
            actions.addAction(selectAction);
            actions.addAction(createPopupMenuAction);

            final DragBehaviour dragBehaviour = widget.getLookup().lookup(DragBehaviour.class);

            if (dragBehaviour != null)
              {
                // TODO: this will create lots of object - try to use a flyweight indexed by the dragBehaviour
                final MoveProvider moveProvider = new WidgetMoveProviderAdapter(this, mainLayer, node, dragBehaviour);
                actions.addAction(ActionFactory.createMoveAction(defaultMoveStrategy, moveProvider));
              }
          }

        validate(); 

//        addObject(node, widgets.toArray(new Widget[0])); can't work, as GraphScene, that is calling us, will
//        call addObject() with the widget we're going to return, and this operation will fail an assertion

        return widgets.iterator().next();
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @CheckForNull
    protected Widget attachEdgeWidget (final @Nonnull Node node)
      {
        final EdgeWidgetFactory edgeWidgetFactory = node.getLookup().lookup(EdgeWidgetFactory.class);
        Parameters.notNull("No EdgeWidgetFactory in Node's Lookup", edgeWidgetFactory);
        final ConnectionWidget connectionWidget = edgeWidgetFactory.createConnectionWidget(this, node).iterator().next();
        connectionLayer.addChild(connectionWidget);
        return connectionWidget;
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    protected void attachEdgeSourceAnchor (final @Nonnull Node edge,
                                           final @Nonnull Node oldSourceNode,
                                           final @Nonnull Node sourceNode)
      {
        final ConnectionWidget edgeWidget = (ConnectionWidget)findWidget(edge);
        assert edgeWidget != null : "No ConnectionWidget for " + edge;
        edgeWidget.setSourceAnchor(AnchorFactory.createRectangularAnchor(findWidget(sourceNode)));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    protected void attachEdgeTargetAnchor (final @Nonnull Node edge,
                                           final @Nonnull Node oldTargetNode,
                                           final @Nonnull Node targetNode)
      {
        final ConnectionWidget edgeWidget = (ConnectionWidget)findWidget(edge);
        assert edgeWidget != null : "No ConnectionWidget for " + edge;
        edgeWidget.setTargetAnchor(AnchorFactory.createRectangularAnchor(findWidget(targetNode)));
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    private Point convertViewToLocal (final @Nonnull Point viewLocation)
      {
        final Point sceneLocation = convertViewToScene(viewLocation);
        final Point localLocation = mainLayer.convertSceneToLocal(sceneLocation);
        return localLocation;
      }
  }