/**
 * $Id: mxCellHandler.java,v 1.1 2012/11/15 13:26:44 gaudenz Exp $
 * Copyright (c) 2008-2012, JGraph Ltd
 */
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.Stroke;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxSwingConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

/**
 * @author Administrator
 * 
 */
public class mxCellHandler
{
	/**
	 * Reference to the enclosing graph component.
	 */
	protected mxGraphComponent graphComponent;

	/**
	 * Holds the cell state associated with this handler.
	 */
	protected mxCellState state;

	/**
	 * Holds the rectangles that define the handles.
	 */
	protected Rectangle[] handles;

	/**
	 * Specifies if the handles should be painted. Default is true.
	 */
	protected boolean handlesVisible = true;

	/**
	 * Holds the bounding box of the handler.
	 */
	protected transient Rectangle bounds;

	/**
	 * Holds the component that is used for preview.
	 */
	protected transient JComponent preview;

	/**
	 * Holds the start location of the mouse gesture.
	 */
	protected transient Point first;

	/**
	 * Holds the index of the handle that was clicked.
	 */
	protected transient int index;

	/**
	 * Constructs a new cell handler for the given cell state.
	 * 
	 * @param graphComponent Enclosing graph component.
	 * @param state Cell state for which the handler is created.
	 */
	public mxCellHandler(mxGraphComponent graphComponent, mxCellState state)
	{
		this.graphComponent = graphComponent;
		refresh(state);
	}

	/**
	 * 
	 */
	public boolean isActive()
	{
		return first != null;
	}

	/**
	 * Refreshes the cell handler.
	 */
	public void refresh(mxCellState state)
	{
		this.state = state;
		handles = createHandles();
		mxGraph graph = graphComponent.getGraph();
		mxRectangle tmp = graph.getBoundingBox(state.getCell());

		if (tmp != null)
		{
			bounds = tmp.getRectangle();

			if (handles != null)
			{
				for (int i = 0; i < handles.length; i++)
				{
					if (isHandleVisible(i))
					{
						bounds.add(handles[i]);
					}
				}
			}
		}
	}

	/**
	 * 
	 */
	public mxGraphComponent getGraphComponent()
	{
		return graphComponent;
	}

	/**
	 * Returns the cell state that is associated with this handler.
	 */
	public mxCellState getState()
	{
		return state;
	}

	/**
	 * Returns the index of the current handle.
	 */
	public int getIndex()
	{
		return index;
	}

	/**
	 * Returns the bounding box of this handler.
	 */
	public Rectangle getBounds()
	{
		return bounds;
	}

	/**
	 * Returns true if the label is movable.
	 */
	public boolean isLabelMovable()
	{
		mxGraph graph = graphComponent.getGraph();
		String label = graph.getLabel(state.getCell());

		return graph.isLabelMovable(state.getCell()) && label != null
				&& label.length() > 0;
	}

	/**
	 * Returns true if the handles should be painted.
	 */
	public boolean isHandlesVisible()
	{
		return handlesVisible;
	}

	/**
	 * Specifies if the handles should be painted.
	 */
	public void setHandlesVisible(boolean handlesVisible)
	{
		this.handlesVisible = handlesVisible;
	}

	/**
	 * Returns true if the given index is the index of the last handle.
	 */
	public boolean isLabel(int index)
	{
		return index == getHandleCount() - 1;
	}

	/**
	 * Creates the rectangles that define the handles.
	 */
	protected Rectangle[] createHandles()
	{
		return null;
	}

	/**
	 * Returns the number of handles in this handler.
	 */
	protected int getHandleCount()
	{
		return (handles != null) ? handles.length : 0;
	}

	/**
	 * Hook for subclassers to return tooltip texts for certain points on the
	 * handle.
	 */
	public String getToolTipText(MouseEvent e)
	{
		return null;
	}

	/**
	 * Returns the index of the handle at the given location.
	 * 
	 * @param x X-coordinate of the location.
	 * @param y Y-coordinate of the location.
	 * @return Returns the handle index for the given location.
	 */
	public int getIndexAt(int x, int y)
	{
		if (handles != null && isHandlesVisible())
		{
			int tol = graphComponent.getTolerance();
			Rectangle rect = new Rectangle(x - tol / 2, y - tol / 2, tol, tol);

			for (int i = handles.length - 1; i >= 0; i--)
			{
				if (isHandleVisible(i) && handles[i].intersects(rect))
				{
					return i;
				}
			}
		}

		return -1;
	}

	/**
	 * Processes the given event.
	 */
	public void mousePressed(MouseEvent e)
	{
		if (!e.isConsumed())
		{
			int tmp = getIndexAt(e.getX(), e.getY());

			if (!isIgnoredEvent(e) && tmp >= 0 && isHandleEnabled(tmp))
			{
				graphComponent.stopEditing(true);
				start(e, tmp);
				e.consume();
			}
		}
	}

	/**
	 * Processes the given event.
	 */
	public void mouseMoved(MouseEvent e)
	{
		if (!e.isConsumed() && handles != null)
		{
			int index = getIndexAt(e.getX(), e.getY());

			if (index >= 0 && isHandleEnabled(index))
			{
				Cursor cursor = getCursor(e, index);

				if (cursor != null)
				{
					graphComponent.getGraphControl().setCursor(cursor);
					e.consume();
				}
				else
				{
					graphComponent.getGraphControl().setCursor(
							new Cursor(Cursor.HAND_CURSOR));
				}
			}
		}
	}

	/**
	 * Processes the given event.
	 */
	public void mouseDragged(MouseEvent e)
	{
		// empty
	}

	/**
	 * Processes the given event.
	 */
	public void mouseReleased(MouseEvent e)
	{
		reset();
	}

	/**
	 * Starts handling a gesture at the given handle index.
	 */
	public void start(MouseEvent e, int index)
	{
		this.index = index;
		first = e.getPoint();
		preview = createPreview();

		if (preview != null)
		{
			graphComponent.getGraphControl().add(preview, 0);
		}
	}

	/**
	 * Returns true if the given event should be ignored.
	 */
	protected boolean isIgnoredEvent(MouseEvent e)
	{
		return graphComponent.isEditEvent(e);
	}

	/**
	 * Creates the preview for this handler.
	 */
	protected JComponent createPreview()
	{
		return null;
	}

	/**
	 * Resets the state of the handler and removes the preview.
	 */
	public void reset()
	{
		if (preview != null)
		{
			preview.setVisible(false);
			preview.getParent().remove(preview);
			preview = null;
		}

		first = null;
	}

	/**
	 * Returns the cursor for the given event and handle.
	 */
	protected Cursor getCursor(MouseEvent e, int index)
	{
		return null;
	}

	/**
	 * Paints the visible handles of this handler.
	 */
	public void paint(Graphics g)
	{
		if (handles != null && isHandlesVisible())
		{
			for (int i = 0; i < handles.length; i++)
			{
				if (isHandleVisible(i)
						&& g.hitClip(handles[i].x, handles[i].y,
								handles[i].width, handles[i].height))
				{
					g.setColor(getHandleFillColor(i));
					g.fillRect(handles[i].x, handles[i].y, handles[i].width,
							handles[i].height);

					g.setColor(getHandleBorderColor(i));
					g.drawRect(handles[i].x, handles[i].y,
							handles[i].width - 1, handles[i].height - 1);
				}
			}
		}
	}

	/**
	 * Returns the color used to draw the selection border. This implementation
	 * returns null.
	 */
	public Color getSelectionColor()
	{
		return null;
	}

	/**
	 * Returns the stroke used to draw the selection border. This implementation
	 * returns null.
	 */
	public Stroke getSelectionStroke()
	{
		return null;
	}

	/**
	 * Returns true if the handle at the specified index is enabled.
	 */
	protected boolean isHandleEnabled(int index)
	{
		return true;
	}

	/**
	 * Returns true if the handle at the specified index is visible.
	 */
	protected boolean isHandleVisible(int index)
	{
		return !isLabel(index) || isLabelMovable();
	}

	/**
	 * Returns the color to be used to fill the handle at the specified index.
	 */
	protected Color getHandleFillColor(int index)
	{
		if (isLabel(index))
		{
			return mxSwingConstants.LABEL_HANDLE_FILLCOLOR;
		}

		return mxSwingConstants.HANDLE_FILLCOLOR;
	}

	/**
	 * Returns the border color of the handle at the specified index.
	 */
	protected Color getHandleBorderColor(int index)
	{
		return mxSwingConstants.HANDLE_BORDERCOLOR;
	}
	
	/**
	 * Invoked when the handler is no longer used. This is an empty
	 * hook for subclassers.
	 */
	protected void destroy()
	{
		// nop
	}

}
