mxHandle.js 7.57 KB
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxHandle
 * 
 * Implements a single custom handle for vertices.
 * 
 * Constructor: mxHandle
 * 
 * Constructs a new handle for the given state.
 * 
 * Parameters:
 * 
 * state - <mxCellState> of the cell to be handled.
 */
function mxHandle(state, cursor, image)
{
	this.graph = state.view.graph;
	this.state = state;
	this.cursor = (cursor != null) ? cursor : this.cursor;
	this.image = (image != null) ? image : this.image;
	this.init();
};

/**
 * Variable: cursor
 * 
 * Specifies the cursor to be used for this handle. Default is 'default'.
 */
mxHandle.prototype.cursor = 'default';

/**
 * Variable: image
 * 
 * Specifies the <mxImage> to be used to render the handle. Default is null.
 */
mxHandle.prototype.image = null;

/**
 * Variable: image
 * 
 * Specifies the <mxImage> to be used to render the handle. Default is null.
 */
mxHandle.prototype.ignoreGrid = false;

/**
 * Function: getPosition
 * 
 * Hook for subclassers to return the current position of the handle.
 */
mxHandle.prototype.getPosition = function(bounds) { };

/**
 * Function: setPosition
 * 
 * Hooks for subclassers to update the style in the <state>.
 */
mxHandle.prototype.setPosition = function(bounds, pt, me) { };

/**
 * Function: execute
 * 
 * Hook for subclassers to execute the handle.
 */
mxHandle.prototype.execute = function() { };

/**
 * Function: copyStyle
 * 
 * Sets the cell style with the given name to the corresponding value in <state>.
 */
mxHandle.prototype.copyStyle = function(key)
{
	this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
};

/**
 * Function: processEvent
 * 
 * Processes the given <mxMouseEvent> and invokes <setPosition>.
 */
mxHandle.prototype.processEvent = function(me)
{
	var scale = this.graph.view.scale;
	var tr = this.graph.view.translate;
	var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
	
	// Center shape on mouse cursor
	if (this.shape != null && this.shape.bounds != null)
	{
		pt.x -= this.shape.bounds.width / scale / 4;
		pt.y -= this.shape.bounds.height / scale / 4;
	}

	// Snaps to grid for the rotated position then applies the rotation for the direction after that
	var alpha1 = -mxUtils.toRadians(this.getRotation());
	var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;
	pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1),
			this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));
	this.setPosition(this.state.getPaintBounds(), pt, me);
	this.positionChanged();
	this.redraw();
};

/**
 * Function: positionChanged
 * 
 * Called after <setPosition> has been called in <processEvent>. This repaints
 * the state using <mxCellRenderer>.
 */
mxHandle.prototype.positionChanged = function()
{
	if (this.state.text != null)
	{
		this.state.text.apply(this.state);
	}
	
	if (this.state.shape != null)
	{
		this.state.shape.apply(this.state);
	}
	
	this.graph.cellRenderer.redraw(this.state, true);
};

/**
 * Function: getRotation
 * 
 * Returns the rotation defined in the style of the cell.
 */
mxHandle.prototype.getRotation = function()
{
	if (this.state.shape != null)
	{
		return this.state.shape.getRotation();
	}
	
	return 0;
};

/**
 * Function: getTotalRotation
 * 
 * Returns the rotation from the style and the rotation from the direction of
 * the cell.
 */
mxHandle.prototype.getTotalRotation = function()
{
	if (this.state.shape != null)
	{
		return this.state.shape.getShapeRotation();
	}
	
	return 0;
};

/**
 * Function: init
 * 
 * Creates and initializes the shapes required for this handle.
 */
mxHandle.prototype.init = function()
{
	var html = this.isHtmlRequired();
	
	if (this.image != null)
	{
		this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);
		this.shape.preserveImageAspect = false;
	}
	else
	{
		this.shape = this.createShape(html);
	}
	
	this.initShape(html);
};

/**
 * Function: createShape
 * 
 * Creates and returns the shape for this handle.
 */
mxHandle.prototype.createShape = function(html)
{
	var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);
	
	return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
};

/**
 * Function: initShape
 * 
 * Initializes <shape> and sets its cursor.
 */
mxHandle.prototype.initShape = function(html)
{
	if (html && this.shape.isHtmlAllowed())
	{
		this.shape.dialect = mxConstants.DIALECT_STRICTHTML;
		this.shape.init(this.graph.container);
	}
	else
	{
		this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
		
		if (this.cursor != null)
		{
			this.shape.init(this.graph.getView().getOverlayPane());
		}
	}

	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
	this.shape.node.style.cursor = this.cursor;
};

/**
 * Function: redraw
 * 
 * Renders the shape for this handle.
 */
mxHandle.prototype.redraw = function()
{
	if (this.shape != null && this.state.shape != null)
	{
		var pt = this.getPosition(this.state.getPaintBounds());
		
		if (pt != null)
		{
			var alpha = mxUtils.toRadians(this.getTotalRotation());
			pt = this.rotatePoint(this.flipPoint(pt), alpha);
	
			var scale = this.graph.view.scale;
			var tr = this.graph.view.translate;
			this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);
			this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);
			
			// Needed to force update of text bounds
			this.shape.redraw();
		}
	}
};

/**
 * Function: isHtmlRequired
 * 
 * Returns true if this handle should be rendered in HTML. This returns true if
 * the text node is in the graph container.
 */
mxHandle.prototype.isHtmlRequired = function()
{
	return this.state.text != null && this.state.text.node.parentNode == this.graph.container;
};

/**
 * Function: rotatePoint
 * 
 * Rotates the point by the given angle.
 */
mxHandle.prototype.rotatePoint = function(pt, alpha)
{
	var bounds = this.state.getCellBounds();
	var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
	var cos = Math.cos(alpha);
	var sin = Math.sin(alpha); 

	return mxUtils.getRotatedPoint(pt, cos, sin, cx);
};

/**
 * Function: flipPoint
 * 
 * Flips the given point vertically and/or horizontally.
 */
mxHandle.prototype.flipPoint = function(pt)
{
	if (this.state.shape != null)
	{
		var bounds = this.state.getCellBounds();
		
		if (this.state.shape.flipH)
		{
			pt.x = 2 * bounds.x + bounds.width - pt.x;
		}
		
		if (this.state.shape.flipV)
		{
			pt.y = 2 * bounds.y + bounds.height - pt.y;
		}
	}
	
	return pt;
};

/**
 * Function: snapPoint
 * 
 * Snaps the given point to the grid if ignore is false. This modifies
 * the given point in-place and also returns it.
 */
mxHandle.prototype.snapPoint = function(pt, ignore)
{
	if (!ignore)
	{
		pt.x = this.graph.snap(pt.x);
		pt.y = this.graph.snap(pt.y);
	}
	
	return pt;
};

/**
 * Function: setVisible
 * 
 * Shows or hides this handle.
 */
mxHandle.prototype.setVisible = function(visible)
{
	if (this.shape != null && this.shape.node != null)
	{
		this.shape.node.style.display = (visible) ? '' : 'none';
	}
};

/**
 * Function: reset
 * 
 * Resets the state of this handle by setting its visibility to true.
 */
mxHandle.prototype.reset = function()
{
	this.setVisible(true);
	this.state.style = this.graph.getCellStyle(this.state.cell);
	this.positionChanged();
};

/**
 * Function: destroy
 * 
 * Destroys this handle.
 */
mxHandle.prototype.destroy = function()
{
	if (this.shape != null)
	{
		this.shape.destroy();
		this.shape = null;
	}
};