Package com.mxgraph.swing.handler

Source Code of com.mxgraph.swing.handler.mxConnectionHandler

/**
* Copyright (c) 2008, Gaudenz Alder
*/
package com.mxgraph.swing.handler;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.mxGraphComponent.mxGraphControl;
import com.mxgraph.swing.util.mxMouseAdapter;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;

/**
* Connection handler creates new connections between cells. This control is used to display the connector
* icon, while the preview is used to draw the line.
*
* mxEvent.CONNECT fires between begin- and endUpdate in mouseReleased. The <code>cell</code>
* property contains the inserted edge, the <code>event</code> and <code>target</code>
* properties contain the respective arguments that were passed to mouseReleased.
*/
public class mxConnectionHandler extends mxMouseAdapter
{

  /**
   *
   */
  private static final long serialVersionUID = -2543899557644889853L;

  /**
   *
   */
  public static Cursor CONNECT_CURSOR = new Cursor(Cursor.HAND_CURSOR);

  /**
   *
   */
  protected mxGraphComponent graphComponent;

  /**
   * Holds the event source.
   */
  protected mxEventSource eventSource = new mxEventSource(this);

  /**
   *
   */
  protected mxConnectPreview connectPreview;

  /**
   * Specifies the icon to be used for creating new connections. If this is
   * specified then it is used instead of the handle. Default is null.
   */
  protected ImageIcon connectIcon = null;

  /**
   * Specifies the size of the handle to be used for creating new
   * connections. Default is mxConstants.CONNECT_HANDLE_SIZE.
   */
  protected int handleSize = mxConstants.CONNECT_HANDLE_SIZE;

  /**
   * Specifies if a handle should be used for creating new connections. This
   * is only used if no connectIcon is specified. If this is false, then the
   * source cell will be highlighted when the mouse is over the hotspot given
   * in the marker. Default is mxConstants.CONNECT_HANDLE_ENABLED.
   */
  protected boolean handleEnabled = mxConstants.CONNECT_HANDLE_ENABLED;

  /**
   *
   */
  protected boolean select = true;

  /**
   * Specifies if the source should be cloned and used as a target if no
   * target was selected. Default is false.
   */
  protected boolean createTarget = false;

  /**
   * Appearance and event handling order wrt subhandles.
   */
  protected boolean keepOnTop = true;

  /**
   *
   */
  protected boolean enabled = true;

  /**
   *
   */
  protected transient Point first;

  /**
   *
   */
  protected transient boolean active = false;

  /**
   *
   */
  protected transient Rectangle bounds;

  /**
   *
   */
  protected transient mxCellState source;

  /**
   *
   */
  protected transient mxCellMarker marker;

  /**
   *
   */
  protected transient String error;

  /**
   *
   */
  protected transient mxIEventListener resetHandler = new mxIEventListener()
  {
    public void invoke(Object source, mxEventObject evt)
    {
      reset();
    }
  };

  /**
   *
   * @param graphComponent
   */
  public mxConnectionHandler(mxGraphComponent graphComponent)
  {
    this.graphComponent = graphComponent;

    // Installs the paint handler
    graphComponent.addListener(mxEvent.AFTER_PAINT, new mxIEventListener()
    {
      public void invoke(Object sender, mxEventObject evt)
      {
        Graphics g = (Graphics) evt.getProperty("g");
        paint(g);
      }
    });

    connectPreview = createConnectPreview();

    mxGraphControl graphControl = graphComponent.getGraphControl();
    graphControl.addMouseListener(this);
    graphControl.addMouseMotionListener(this);

    // Installs the graph listeners and keeps them in sync
    addGraphListeners(graphComponent.getGraph());

    graphComponent.addPropertyChangeListener(new PropertyChangeListener()
    {
      public void propertyChange(PropertyChangeEvent evt)
      {
        if (evt.getPropertyName().equals("graph"))
        {
          removeGraphListeners((mxGraph) evt.getOldValue());
          addGraphListeners((mxGraph) evt.getNewValue());
        }
      }
    });

    marker = new mxCellMarker(graphComponent)
    {
      /**
       *
       */
      private static final long serialVersionUID = 103433247310526381L;

      // Overrides to return cell at location only if valid (so that
      // there is no highlight for invalid cells that have no error
      // message when the mouse is released)
      protected Object getCell(MouseEvent e)
      {
        Object cell = super.getCell(e);

        if (isConnecting())
        {
          if (source != null)
          {
            error = validateConnection(source.getCell(), cell);

            if (error != null && error.length() == 0)
            {
              cell = null;

              // Enables create target inside groups
              if (createTarget)
              {
                error = null;
              }
            }
          }
        }
        else if (!isValidSource(cell))
        {
          cell = null;
        }

        return cell;
      }

      // Sets the highlight color according to isValidConnection
      protected boolean isValidState(mxCellState state)
      {
        if (isConnecting())
        {
          return error == null;
        }
        else
        {
          return super.isValidState(state);
        }
      }

      // Overrides to use marker color only in highlight mode or for
      // target selection
      protected Color getMarkerColor(MouseEvent e, mxCellState state,
          boolean isValid)
      {
        return (isHighlighting() || isConnecting()) ? super
            .getMarkerColor(e, state, isValid) : null;
      }

      // Overrides to use hotspot only for source selection otherwise
      // intersects always returns true when over a cell
      protected boolean intersects(mxCellState state, MouseEvent e)
      {
        if (!isHighlighting() || isConnecting())
        {
          return true;
        }

        return super.intersects(state, e);
      }
    };

    marker.setHotspotEnabled(true);
  }

  /**
   * Installs the listeners to update the handles after any changes.
   */
  protected void addGraphListeners(mxGraph graph)
  {
    // LATER: Install change listener for graph model, view
    if (graph != null)
    {
      mxGraphView view = graph.getView();
      view.addListener(mxEvent.SCALE, resetHandler);
      view.addListener(mxEvent.TRANSLATE, resetHandler);
      view.addListener(mxEvent.SCALE_AND_TRANSLATE, resetHandler);

      graph.getModel().addListener(mxEvent.CHANGE, resetHandler);
    }
  }

  /**
   * Removes all installed listeners.
   */
  protected void removeGraphListeners(mxGraph graph)
  {
    if (graph != null)
    {
      mxGraphView view = graph.getView();
      view.removeListener(resetHandler, mxEvent.SCALE);
      view.removeListener(resetHandler, mxEvent.TRANSLATE);
      view.removeListener(resetHandler, mxEvent.SCALE_AND_TRANSLATE);

      graph.getModel().removeListener(resetHandler, mxEvent.CHANGE);
    }
  }

  /**
   *
   */
  protected mxConnectPreview createConnectPreview()
  {
    return new mxConnectPreview(graphComponent);
  }

  /**
   *
   */
  public mxConnectPreview getConnectPreview()
  {
    return connectPreview;
  }

  /**
   *
   */
  public void setConnectPreview(mxConnectPreview value)
  {
    connectPreview = value;
  }

  /**
   * Returns true if the source terminal has been clicked and a new
   * connection is currently being previewed.
   */
  public boolean isConnecting()
  {
    return connectPreview.isActive();
  }

  /**
   *
   */
  public boolean isActive()
  {
    return active;
  }
 
  /**
   * Returns true if no connectIcon is specified and handleEnabled is false.
   */
  public boolean isHighlighting()
  {
    return connectIcon == null && !handleEnabled;
  }

  /**
   *
   */
  public boolean isEnabled()
  {
    return enabled;
  }

  /**
   *
   */
  public void setEnabled(boolean value)
  {
    enabled = value;
  }

  /**
   *
   */
  public boolean isKeepOnTop()
  {
    return keepOnTop;
  }

  /**
   *
   */
  public void setKeepOnTop(boolean value)
  {
    keepOnTop = value;
  }

  /**
   *
   */
  public void setConnectIcon(ImageIcon value)
  {
    connectIcon = value;
  }

  /**
   *
   */
  public ImageIcon getConnecIcon()
  {
    return connectIcon;
  }

  /**
   *
   */
  public void setHandleEnabled(boolean value)
  {
    handleEnabled = value;
  }

  /**
   *
   */
  public boolean isHandleEnabled()
  {
    return handleEnabled;
  }

  /**
   *
   */
  public void setHandleSize(int value)
  {
    handleSize = value;
  }

  /**
   *
   */
  public int getHandleSize()
  {
    return handleSize;
  }

  /**
   *
   */
  public mxCellMarker getMarker()
  {
    return marker;
  }

  /**
   *
   */
  public void setMarker(mxCellMarker value)
  {
    marker = value;
  }

  /**
   *
   */
  public void setCreateTarget(boolean value)
  {
    createTarget = value;
  }

  /**
   *
   */
  public boolean isCreateTarget()
  {
    return createTarget;
  }

  /**
   *
   */
  public void setSelect(boolean value)
  {
    select = value;
  }

  /**
   *
   */
  public boolean isSelect()
  {
    return select;
  }

  /**
   *
   */
  public void reset()
  {
    connectPreview.stop(false);
    setBounds(null);
    marker.reset();
    active = false;
    source = null;
    first = null;
    error = null;
  }

  /**
   *
   */
  public Object createTargetVertex(MouseEvent e, Object source)
  {
    mxGraph graph = graphComponent.getGraph();
    Object clone = graph.cloneCells(new Object[] { source })[0];
    mxIGraphModel model = graph.getModel();
    mxGeometry geo = model.getGeometry(clone);

    if (geo != null)
    {
      mxPoint point = graphComponent.getPointForEvent(e);
      geo.setX(graph.snap(point.getX() - geo.getWidth() / 2));
      geo.setY(graph.snap(point.getY() - geo.getHeight() / 2));
    }

    return clone;
  }

  /**
   *
   */
  public boolean isValidSource(Object cell)
  {
    return graphComponent.getGraph().isValidSource(cell);
  }

  /**
   * Returns true. The call to mxGraph.isValidTarget is implicit by calling
   * mxGraph.getEdgeValidationError in validateConnection. This is an
   * additional hook for disabling certain targets in this specific handler.
   */
  public boolean isValidTarget(Object cell)
  {
    return true;
  }

  /**
   * Returns the error message or an empty string if the connection for the
   * given source target pair is not valid. Otherwise it returns null.
   */
  public String validateConnection(Object source, Object target)
  {
    if (target == null && createTarget)
    {
      return null;
    }

    if (!isValidTarget(target))
    {
      return "";
    }

    return graphComponent.getGraph().getEdgeValidationError(
        connectPreview.getPreviewState().getCell(), source, target);
  }

  /**
   *
   */
  public void mousePressed(MouseEvent e)
  {
    if (!graphComponent.isForceMarqueeEvent(e)
        && !graphComponent.isPanningEvent(e)
        && !e.isPopupTrigger()
        && graphComponent.isEnabled()
        && isEnabled()
        && !e.isConsumed()
        && ((isHighlighting() && marker.hasValidState()) || (!isHighlighting()
            && bounds != null && bounds.contains(e.getPoint()))))
    {
      start(e, marker.getValidState());
      e.consume();
    }
  }

  /**
   *
   */
  public void start(MouseEvent e, mxCellState state)
  {
    first = e.getPoint();
    connectPreview.start(e, state, "");
  }

  /**
   *
   */
  public void mouseMoved(MouseEvent e)
  {
    mouseDragged(e);

    if (isHighlighting() && !marker.hasValidState())
    {
      source = null;
    }

    if (!isHighlighting() && source != null)
    {
      int imgWidth = handleSize;
      int imgHeight = handleSize;

      if (connectIcon != null)
      {
        imgWidth = connectIcon.getIconWidth();
        imgHeight = connectIcon.getIconHeight();
      }

      int x = (int) source.getCenterX() - imgWidth / 2;
      int y = (int) source.getCenterY() - imgHeight / 2;

      if (graphComponent.getGraph().isSwimlane(source.getCell()))
      {
        mxRectangle size = graphComponent.getGraph().getStartSize(
            source.getCell());

        if (size.getWidth() > 0)
        {
          x = (int) (source.getX() + size.getWidth() / 2 - imgWidth / 2);
        }
        else
        {
          y = (int) (source.getY() + size.getHeight() / 2 - imgHeight / 2);
        }
      }

      setBounds(new Rectangle(x, y, imgWidth, imgHeight));
    }
    else
    {
      setBounds(null);
    }

    if (source != null && (bounds == null || bounds.contains(e.getPoint())))
    {
      graphComponent.getGraphControl().setCursor(CONNECT_CURSOR);
      e.consume();
    }
  }

  /**
   *
   */
  public void mouseDragged(MouseEvent e)
  {
    if (!e.isConsumed() && graphComponent.isEnabled() && isEnabled())
    {
      // Activates the handler
      if (!active && first != null)
      {
        double dx = Math.abs(first.getX() - e.getX());
        double dy = Math.abs(first.getY() - e.getY());
        int tol = graphComponent.getTolerance();
       
        if (dx > tol || dy > tol)
        {
          active = true;
        }
      }
     
      if (e.getButton() == 0 || (isActive() && connectPreview.isActive()))
      {
        mxCellState state = marker.process(e);
 
        if (connectPreview.isActive())
        {
          connectPreview.update(e, marker.getValidState(), e.getX(),
              e.getY());
          setBounds(null);
          e.consume();
        }
        else
        {
          source = state;
        }
      }
    }
  }

  /**
   *
   */
  public void mouseReleased(MouseEvent e)
  {
    if (isActive())
    {
      if (error != null)
      {
        if (error.length() > 0)
        {
          JOptionPane.showMessageDialog(graphComponent, error);
        }
      }
      else if (first != null)
      {
        mxGraph graph = graphComponent.getGraph();
        double dx = first.getX() - e.getX();
        double dy = first.getY() - e.getY();
 
        if (connectPreview.isActive()
            && (marker.hasValidState() || isCreateTarget() || graph
                .isAllowDanglingEdges()))
        {
          graph.getModel().beginUpdate();
 
          try
          {
            Object dropTarget = null;
 
            if (!marker.hasValidState() && isCreateTarget())
            {
              Object vertex = createTargetVertex(e, source.getCell());
              dropTarget = graph.getDropTarget(
                  new Object[] { vertex }, e.getPoint(),
                  graphComponent.getCellAt(e.getX(), e.getY()));
 
              if (vertex != null)
              {
                // Disables edges as drop targets if the target cell was created
                if (dropTarget == null
                    || !graph.getModel().isEdge(dropTarget))
                {
                  mxCellState pstate = graph.getView().getState(
                      dropTarget);
 
                  if (pstate != null)
                  {
                    mxGeometry geo = graph.getModel()
                        .getGeometry(vertex);
 
                    mxPoint origin = pstate.getOrigin();
                    geo.setX(geo.getX() - origin.getX());
                    geo.setY(geo.getY() - origin.getY());
                  }
                }
                else
                {
                  dropTarget = graph.getDefaultParent();
                }
 
                graph.addCells(new Object[] { vertex }, dropTarget);
              }
 
              // FIXME: Here we pre-create the state for the vertex to be
              // inserted in order to invoke update in the connectPreview.
              // This means we have a cell state which should be created
              // after the model.update, so this should be fixed.
              mxCellState targetState = graph.getView().getState(
                  vertex, true);
              connectPreview.update(e, targetState, e.getX(),
                  e.getY());
            }
 
            Object cell = connectPreview.stop(
                graphComponent.isSignificant(dx, dy), e);
 
            if (cell != null)
            {
              graphComponent.getGraph().setSelectionCell(cell);
              eventSource.fireEvent(new mxEventObject(
                  mxEvent.CONNECT, "cell", cell, "event", e,
                  "target", dropTarget));
            }
 
            e.consume();
          }
          finally
          {
            graph.getModel().endUpdate();
          }
        }
      }
    }

    reset();
  }

  /**
   *
   */
  public void setBounds(Rectangle value)
  {
    if ((bounds == null && value != null)
        || (bounds != null && value == null)
        || (bounds != null && value != null && !bounds.equals(value)))
    {
      Rectangle tmp = bounds;

      if (tmp != null)
      {
        if (value != null)
        {
          tmp.add(value);
        }
      }
      else
      {
        tmp = value;
      }

      bounds = value;

      if (tmp != null)
      {
        graphComponent.getGraphControl().repaint(tmp);
      }
    }
  }

  /**
   * Adds the given event listener.
   */
  public void addListener(String eventName, mxIEventListener listener)
  {
    eventSource.addListener(eventName, listener);
  }

  /**
   * Removes the given event listener.
   */
  public void removeListener(mxIEventListener listener)
  {
    eventSource.removeListener(listener);
  }

  /**
   * Removes the given event listener for the specified event name.
   */
  public void removeListener(mxIEventListener listener, String eventName)
  {
    eventSource.removeListener(listener, eventName);
  }

  /**
   *
   */
  public void paint(Graphics g)
  {
    if (bounds != null)
    {
      if (connectIcon != null)
      {
        g.drawImage(connectIcon.getImage(), bounds.x, bounds.y,
            bounds.width, bounds.height, null);
      }
      else if (handleEnabled)
      {
        g.setColor(Color.BLACK);
        g.draw3DRect(bounds.x, bounds.y, bounds.width - 1,
            bounds.height - 1, true);
        g.setColor(Color.GREEN);
        g.fill3DRect(bounds.x + 1, bounds.y + 1, bounds.width - 2,
            bounds.height - 2, true);
        g.setColor(Color.BLUE);
        g.drawRect(bounds.x + bounds.width / 2 - 1, bounds.y
            + bounds.height / 2 - 1, 1, 1);
      }
    }
  }

}
TOP

Related Classes of com.mxgraph.swing.handler.mxConnectionHandler

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.