Customizing and switching between several EditModes

Demo Source Code

Summary

Demonstrates how to customize node drag handling in EditMode. Additionally, the sample code shows how to switch between several view modes in a Graph2DView to provide different modes of interactive editing.
For a better user experience, please go to the integrated documentation viewer to read this article.

Description

package demo.view.viewmode;

import demo.view.DemoBase;
import y.view.EditMode;
import y.view.Graph2D;
import y.view.MoveSelectionMode;
import y.view.BendList;
import y.view.ViewMode;
import y.view.BendCursor;
import y.base.Node;
import y.base.NodeList;
import y.base.EdgeCursor;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.JToolBar;
import javax.swing.JToggleButton;
import javax.swing.ButtonGroup;
import javax.swing.AbstractAction;
import javax.swing.Action;

/**
 * Demonstrates how to customize <code>EditMode</code>'s
 * <code>nodeDragged</code> behavior.
 */
public class CustomEditModeDemo extends DemoBase {
  private EditMode editMode;

  protected EditMode createEditMode() {
    editMode = new EditMode();
    return editMode;
  }

  protected JToolBar createToolBar() {
    final JToolBar jtb = super.createToolBar();
    jtb.addSeparator();

    // set up actions to switch between several policies for editing

    final MyEditMode defaultMode = new MyEditMode();
    defaultMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_DEFAULT);

    final MyEditMode createEdgeMode = new MyEditMode();
    createEdgeMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_CREATE_EDGE);

    final MyEditMode moveNodeMode = new MyEditMode();
    moveNodeMode.setNodeDragPolicy(MyEditMode.NODE_DRAG_POLICY_MOVE_NODE);

    final Action[] policySwitches = {
            new AbstractAction("Default") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = defaultMode;
                view.addViewMode(editMode);
              }
            },
            new AbstractAction("Create Edge") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = createEdgeMode;
                view.addViewMode(editMode);
              }
            },
            new AbstractAction("Move Node") {
              public void actionPerformed( final ActionEvent e ) {
                view.removeViewMode(editMode);
                editMode = moveNodeMode;
                view.addViewMode(editMode);
              }
            }
    };
    final ButtonGroup group = new ButtonGroup();
    for (int i = 0; i < policySwitches.length; ++i) {
      final JToggleButton jb = new JToggleButton(policySwitches[i]);
      jb.setSelected(i == 0);
      group.add(jb);
      jtb.add(jb);
    }

    return jtb;
  }

  public static void main( String[] args ) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        initLnF();
        (new CustomEditModeDemo()).start();
      }
    });
  }

  /**
   * Custom <code>EditMode</code> that uses policies to determine its
   * <code>nodeDragged</code> behavior.
   */
  private static final class MyEditMode extends EditMode {
    /**
     * Node dragged behavior policy for the default behavior inherited from
     * EditMode.
     */
    public static final byte NODE_DRAG_POLICY_DEFAULT = 0;
    /**
     * Node dragged behavior policy that triggers <code>CreateEdgeMode</code>
     * no matter of the dragged node was selected or not.
     */
    public static final byte NODE_DRAG_POLICY_CREATE_EDGE = 1;
    /**
     * Node dragged behavior policy that triggers <code>MoveSelectionMode</code>
     * no matter of the dragged node was selected or not.
     */
    public static final byte NODE_DRAG_POLICY_MOVE_NODE = 2;

    private byte nodeDragPolicy;

    public byte getNodeDragPolicy() {
      return nodeDragPolicy;
    }

    public void setNodeDragPolicy( final byte policy ) {
      switch (policy) {
        case NODE_DRAG_POLICY_DEFAULT:
        case NODE_DRAG_POLICY_CREATE_EDGE:
        case NODE_DRAG_POLICY_MOVE_NODE:
          this.nodeDragPolicy = policy;
          break;
        default:
          throw new IllegalArgumentException("unsupported policy: " + policy);
      }
    }

    /**
     * Overwritten to behave according to the current node drag policy.
     */
    protected void nodeDragged(
            final Graph2D graph,
            final Node node,
            final boolean wasSelected,
            final double x,
            final double y,
            final boolean firstDrag
    ) {
      switch (getNodeDragPolicy()) {
        case NODE_DRAG_POLICY_DEFAULT:
          super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          break;
        case NODE_DRAG_POLICY_CREATE_EDGE:
          if (doAllowEdgeCreation() && getCreateEdgeMode() != null) {
            setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
          } else {
            super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          }
          break;
        case NODE_DRAG_POLICY_MOVE_NODE:
          if (firstDrag && getMoveSelectionMode() != null) {
            if (!wasSelected && getMoveSelectionMode() instanceof MyMoveSelectionMode) {
              ((MyMoveSelectionMode) getMoveSelectionMode()).setDraggedNode(node);
            }
            setChild(getMoveSelectionMode(), lastPressEvent, lastDragEvent);
          } else {
            super.nodeDragged(graph, node, wasSelected, x, y, firstDrag);
          }
          break;
      }
    }

    protected ViewMode createMoveSelectionMode() {
      return new MyMoveSelectionMode();
    }
  }

  /**
   * Custom <code>MoveSelectionMode</code> that supports single unselected
   * nodes, too.
   */
  private static final class MyMoveSelectionMode extends MoveSelectionMode {
    private Node draggedNode;

    public Node getDraggedNode() {
      return draggedNode;
    }

    public void setDraggedNode( final Node draggedNode ) {
      this.draggedNode = draggedNode;
    }

    /**
     * Overwritten to ensure that only the node that is marked as
     * <code>draggedNode</code> is moved if there is such a node.
     */
    protected NodeList getNodesToBeMoved() {
      if (draggedNode == null) {
        return super.getNodesToBeMoved();
      } else {
        return new NodeList(draggedNode);
      }
    }

    /**
     * Overwritten to ensure that only the node that is marked as
     * <code>draggedNode</code> is moved if there is such a node.
     */
    protected BendList getBendsToBeMoved() {
      if (draggedNode == null) {
        return super.getBendsToBeMoved();
      } else {
        final BendList bends = new BendList();
        for (EdgeCursor ec = draggedNode.inEdges(); ec.ok(); ec.next()) {
          if (ec.edge().source() == draggedNode) {
            for (BendCursor bc = view.getGraph2D().getRealizer(ec.edge()).bends(); bc.ok(); bc.next()) {
              bends.add(bc.bend());
            }
          }
        }
        return bends;
      }
    }

    /**
     * Overwritten to clear the <code>draggedNode</code> property upon
     * parent mode reactivation to prevent stale data when the mode
     * is activated the next time.
     */
    public void reactivateParent() {
      draggedNode = null;
      super.reactivateParent();
    }
  }
}

To compile and run the above sample code, copy the attached file into src/demo/view/viewmode of your yFiles installation directory.

See Customizing EditMode to move unselected nodes for an alternative approach to moving nodes that are not selected while still providing means to create edges.

Resources

Categories this article belongs to:
yFiles for Java > yFiles Viewer > Displaying and Editing Graphs > User Interaction
Applies to:
yFiles for Java 2: 2.6, 2.7, 2.8, 2.9, 2.10, 2.11, 2.12, 2.13, 2.14, 2.15, 2.16, 2.17, 2.18
Keywords:
EditMode - ViewMode - edit mode - view mode - switching view modes