com.mxgraph.swing.handler.mxConnectionHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jgraphx Show documentation
Show all versions of jgraphx Show documentation
JGraphX Swing Component - Java Graph Visualization Library
This is a binary & source redistribution of the original, unmodified JGraphX library originating from:
"https://github.com/jgraph/jgraphx/archive/v3.4.1.3.zip".
The purpose of this redistribution is to make the library available to other Maven projects.
/**
* $Id: mxConnectionHandler.java,v 1.33 2012/02/14 08:17:01 gaudenz Exp $
* 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 cell
* property contains the inserted edge, the event
and target
* 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);
}
}
}
}