/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d;

import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleControlListener;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.accessibility.AccessibleListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Control;

/**
 * The SWTEventDispatcher provides draw2d with the ability to dispatch SWT Events. The 
 * {@link org.eclipse.draw2d.LightweightSystem} adds SWT event listeners on its Canvas. 
 * When the Canvas receives an SWT event, it calls the appropriate dispatcher method in 
 * SWTEventDispatcher. 
 */
public class SWTEventDispatcher
	extends EventDispatcher
{

/** 
 * Used to tell if any button is pressed without regard to the specific button. 
 * @deprecated Use {@link SWT#BUTTON_MASK} instead. 
 */
protected static final int ANY_BUTTON = SWT.BUTTON_MASK;

private boolean figureTraverse = true;

private boolean captured;
private IFigure root;
private IFigure mouseTarget;
private IFigure cursorTarget;
private IFigure focusOwner;
private IFigure hoverSource;

private MouseEvent currentEvent;
private Cursor cursor;
/** The control this dispatcher is listening to. */
protected org.eclipse.swt.widgets.Control control;

private ToolTipHelper toolTipHelper;
private FocusTraverseManager focusManager = new FocusTraverseManager();

/**
 * Implements {@link EventDispatcher.AccessibilityDispatcher} but
 * does nothing in the implementation.
 */
protected class FigureAccessibilityDispatcher
	extends AccessibilityDispatcher
{
	/** @see AccessibleControlListener#getChildAtPoint(AccessibleControlEvent) */
	public void getChildAtPoint(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getChildCount(AccessibleControlEvent) */
	public void getChildCount(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getChildren(AccessibleControlEvent) */
	public void getChildren(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getDefaultAction(AccessibleControlEvent) */
	public void getDefaultAction(AccessibleControlEvent e) { }
	/** @see AccessibleListener#getDescription(AccessibleEvent) */
	public void getDescription(AccessibleEvent e) { }
	/** @see AccessibleControlListener#getFocus(AccessibleControlEvent) */
	public void getFocus(AccessibleControlEvent e) { }
	/** @see AccessibleListener#getHelp(AccessibleEvent) */
	public void getHelp(AccessibleEvent e) { }
	/** @see AccessibleListener#getKeyboardShortcut(AccessibleEvent) */
	public void getKeyboardShortcut(AccessibleEvent e) { }
	/** @see AccessibleControlListener#getLocation(AccessibleControlEvent) */
	public void getLocation(AccessibleControlEvent e) { }
	/** @see AccessibleListener#getName(AccessibleEvent) */
	public void getName(AccessibleEvent e) { }
	/** @see AccessibleControlListener#getRole(AccessibleControlEvent) */
	public void getRole(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getSelection(AccessibleControlEvent) */
	public void getSelection(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getState(AccessibleControlEvent) */
	public void getState(AccessibleControlEvent e) { }
	/** @see AccessibleControlListener#getValue(AccessibleControlEvent) */
	public void getValue(AccessibleControlEvent e) { }
}

/**
 * @see EventDispatcher#dispatchFocusGained(org.eclipse.swt.events.FocusEvent)
 */
public void dispatchFocusGained(org.eclipse.swt.events.FocusEvent e) {
	IFigure currentFocusOwner = getFocusTraverseManager().getCurrentFocusOwner();

	/*
	 * Upon focus gained, if there is no current focus owner,
	 * set focus on first focusable child. 
	 */
	if (currentFocusOwner == null)
		currentFocusOwner = 
					getFocusTraverseManager().getNextFocusableFigure(root, focusOwner);
	setFocus(currentFocusOwner);
}

/**
 * @see EventDispatcher#dispatchFocusLost(org.eclipse.swt.events.FocusEvent)
 */
public void dispatchFocusLost(org.eclipse.swt.events.FocusEvent e) {
	setFocus(null);
}

/**
 * @see EventDispatcher#dispatchKeyPressed(org.eclipse.swt.events.KeyEvent)
 */
public void dispatchKeyPressed(org.eclipse.swt.events.KeyEvent e) {
	if (focusOwner != null) {
		KeyEvent event = new KeyEvent(this, focusOwner, e);
		focusOwner.handleKeyPressed(event);
	}
}

/**
 * @see EventDispatcher#dispatchKeyReleased(org.eclipse.swt.events.KeyEvent)
 */
public void dispatchKeyReleased(org.eclipse.swt.events.KeyEvent e) {
	if (focusOwner != null) {
		KeyEvent event = new KeyEvent(this, focusOwner, e);
		focusOwner.handleKeyReleased(event);
	}
}

/**
 * @see EventDispatcher#dispatchKeyTraversed(TraverseEvent)
 */
public void dispatchKeyTraversed(TraverseEvent e) {
	if (!figureTraverse)
		return;
	IFigure nextFigure = null;
	
	if (e.detail == SWT.TRAVERSE_TAB_NEXT)
		nextFigure = getFocusTraverseManager().getNextFocusableFigure(root, focusOwner);
	else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
		nextFigure = 
				getFocusTraverseManager().getPreviousFocusableFigure(root, focusOwner);

	if (nextFigure != null) {
		e.doit = false;	
		setFocus(nextFigure);
	}
}

/**
 * @see EventDispatcher#dispatchMouseHover(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseHover(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
	if (mouseTarget != null)
		mouseTarget.handleMouseHover(currentEvent);		
	/*
	 * Check Tooltip source.
	 * Get Tooltip source's Figure.
	 * Set that tooltip as the lws contents on the helper.
	 */
	if (hoverSource != null) {
		toolTipHelper = getToolTipHelper();
		IFigure tip = hoverSource.getToolTip();
		Control control = (Control)me.getSource();
		org.eclipse.swt.graphics.Point absolute;
		absolute = control.toDisplay(new org.eclipse.swt.graphics.Point(me.x, me.y));
		toolTipHelper.displayToolTipNear(hoverSource, tip, absolute.x, absolute.y);
	}
}

/**
 * @see EventDispatcher#dispatchMouseDoubleClicked(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseDoubleClicked(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
	if (mouseTarget != null)
		mouseTarget.handleMouseDoubleClicked(currentEvent);
}

/**
 * @see EventDispatcher#dispatchMouseEntered(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseEntered(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
}

/**
 * @see EventDispatcher#dispatchMouseExited(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseExited(org.eclipse.swt.events.MouseEvent me) {
	setHoverSource(null, me);
	if (mouseTarget != null) {
		currentEvent = 
				new MouseEvent(me.x, me.y, this, mouseTarget, me.button, me.stateMask);
		mouseTarget.handleMouseExited(currentEvent);
		releaseCapture();
		mouseTarget = null;
	}
}

/**
 * @see EventDispatcher#dispatchMousePressed(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMousePressed(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
	if (mouseTarget != null) {
		mouseTarget.handleMousePressed(currentEvent);
		if (currentEvent.isConsumed())
			setCapture(mouseTarget);
	}
}

/**
 * @see EventDispatcher#dispatchMouseMoved(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseMoved(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
	if (mouseTarget != null) {
		if ((me.stateMask & SWT.BUTTON_MASK) != 0)
			mouseTarget.handleMouseDragged(currentEvent);
		else
			mouseTarget.handleMouseMoved(currentEvent);
	}
}

/**
 * @see EventDispatcher#dispatchMouseReleased(org.eclipse.swt.events.MouseEvent)
 */
public void dispatchMouseReleased(org.eclipse.swt.events.MouseEvent me) {
	receive(me);
	if (mouseTarget != null) {
		mouseTarget.handleMouseReleased(currentEvent);
	}
	releaseCapture();
	receive(me);
}

/**
 * @see EventDispatcher#getAccessibilityDispatcher()
 */
protected AccessibilityDispatcher getAccessibilityDispatcher() {
	return null;
}

/**
 * Returns the current mouse event.
 * @return the current mouse event; can be <code>null</code>
 */
protected MouseEvent getCurrentEvent() {
	return currentEvent;
}

private IFigure getCurrentToolTip() {
	if (hoverSource != null)
		return hoverSource.getToolTip();
	else
		return null;	
}

/**
 * Returns the figure that the cursor is over.
 * @return the cursor target
 */
protected IFigure getCursorTarget() { 
	return cursorTarget;
}

/**
 * Returns the ToolTipHelper used to display tooltips on hover events.
 * @return the ToolTipHelper
 */
protected ToolTipHelper getToolTipHelper() {
	if (toolTipHelper == null)
		toolTipHelper = new ToolTipHelper(control);
	return toolTipHelper;
}

/**
 * Returns the FocusTraverseManager which is used to determine which figure will get focus 
 * when a TAB or ALT+TAB key sequence occurs.
 * @return the FocusTraverseManager
 */
protected final FocusTraverseManager getFocusTraverseManager() {
	if (focusManager == null) {
		focusManager = new FocusTraverseManager();
	}	
	return focusManager;
}				

/**
 * @see EventDispatcher#getFocusOwner()
 */
/*package*/ IFigure getFocusOwner() {
	return focusOwner;
}

/**
 * Returns the figure that is the target of mouse events.  This may not be the figure 
 * beneath the cursor because another figure may have captured the mouse and will continue 
 * to get mouse events until capture is released.
 * @return the mouse target
 */
protected IFigure getMouseTarget() {
	return mouseTarget;
}

/**
 * Returns the root figure for this dispatcher.
 * @return the root figure
 */
protected IFigure getRoot() {
	return root;
}

/**
 * @see EventDispatcher#isCaptured()
 */
public boolean isCaptured() {
	return captured;
}

private void receive(org.eclipse.swt.events.MouseEvent me) {
	currentEvent = null;
	updateFigureUnderCursor(me);
	int state = me.stateMask;
	if (captured) {
		if (mouseTarget != null)
			currentEvent = new MouseEvent(me.x, me.y, this, mouseTarget, me.button, state);
	} else {
		IFigure f = root.findMouseEventTargetAt(me.x, me.y);
		if (f == mouseTarget) {
			if (mouseTarget != null)
				currentEvent = 
						new MouseEvent(me.x, me.y, this, mouseTarget, me.button, state);
			return;
		}
		if (mouseTarget != null) {
			currentEvent = new MouseEvent(me.x, me.y, this, mouseTarget, me.button, state);
			mouseTarget.handleMouseExited(currentEvent);
		}
		setMouseTarget(f);
		if (mouseTarget != null) {
			currentEvent = new MouseEvent(me.x, me.y, this, mouseTarget, me.button, state);
			mouseTarget.handleMouseEntered(currentEvent);
		}
	}
}

/**
 * @see EventDispatcher#releaseCapture()
 */
protected void releaseCapture() {
	captured = false;
}

/**
 * @see EventDispatcher#requestFocus(IFigure)
 */
public void requestFocus(IFigure fig) {
	setFocus(fig);
}

/**
 * @see EventDispatcher#requestRemoveFocus(IFigure)
 */
public void requestRemoveFocus(IFigure fig) {
	if (getFocusOwner() == fig)
		setFocus(null);
	if (mouseTarget == fig)
		mouseTarget = null;
	if (cursorTarget == fig)
		cursorTarget = null;
	if (hoverSource == fig)
		hoverSource = null;
	getFocusTraverseManager().setCurrentFocusOwner(null);
}

/**
 * @see EventDispatcher#setCapture(IFigure)
 */
protected void setCapture(IFigure figure) {
	captured = true;
	mouseTarget = figure;
}

/**
 * @see EventDispatcher#setControl(Control)
 */
public void setControl(Control c) {
	if (c == control)
		return;
	if (control != null && !control.isDisposed())
		throw new RuntimeException(
				"Can not set control again once it has been set"); //$NON-NLS-1$
	if (c != null)
		c.addDisposeListener(new org.eclipse.swt.events.DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (toolTipHelper != null)
					toolTipHelper.dispose();
			}
		});
	control = c;
}

/**
 * Sets the mouse cursor.
 * @param c the new cursor
 */
protected void setCursor(Cursor c) {
	if (c == null && cursor == null) {
		return;
	} else if ((c != cursor) || (!c.equals(cursor))) {
		cursor = c;
		if (control != null && !control.isDisposed())
			control.setCursor(c);
	}
}

/**
 * Enables key traversal via TAB and ALT+TAB if <i>traverse</i> is <code>true</code>.
 * Disables it otherwise.
 * @param traverse whether key traversal should be enabled
 */
public void setEnableKeyTraversal(boolean traverse) {
	figureTraverse = traverse;
}

/**
 * Sets the figure under the mouse cursor.
 * @param f the new figure under the cursor
 */
protected void setFigureUnderCursor(IFigure f) {
	if (cursorTarget == f)
		return;
	cursorTarget = f;
	updateCursor();
}

/**
 * Sets the focus figure.  If the figure currently with focus is not <code>null</code>, 
 * {@link IFigure#handleFocusLost(FocusEvent)} is called on the current focused figure. If 
 * the new focus figure is not <code>null</code>, this will call 
 * {@link IFigure#handleFocusGained(FocusEvent)} on the new focused figure.
 * @param fig the new focus figure
 */
protected void setFocus(IFigure fig) {
	if (fig == focusOwner)
		return;
	FocusEvent fe = new FocusEvent(focusOwner, fig);
	IFigure oldOwner = focusOwner;
	focusOwner = fig;
	if (oldOwner != null)
		oldOwner.handleFocusLost(fe);
	if (fig != null)
		getFocusTraverseManager().setCurrentFocusOwner(fig);
	if (focusOwner != null)
		focusOwner.handleFocusGained(fe);
}

/**
 * Sets the figure that the mouse cursor is hovering over.
 * @param figure the new hover source
 * @param me the mouse event
 */
protected void setHoverSource(Figure figure, org.eclipse.swt.events.MouseEvent me) {
	hoverSource = figure;
	if (figure != null) {
		Control control = (Control)me.getSource();
		org.eclipse.swt.graphics.Point absolute;
		absolute = control.toDisplay(new org.eclipse.swt.graphics.Point(me.x, me.y));
		toolTipHelper = getToolTipHelper();
		toolTipHelper.updateToolTip(
							hoverSource, getCurrentToolTip(), absolute.x, absolute.y);	
	} else if (toolTipHelper != null) {
		// Update with null to clear hoverSource in ToolTipHelper
		toolTipHelper.updateToolTip(hoverSource, getCurrentToolTip(), me.x, me.y);
	}
}	

/**
 * Sets the given figure to be the target of future mouse events.
 * @param figure the new mouse target
 */
protected void setMouseTarget(IFigure figure) {
	mouseTarget = figure;
}

/**
 * @see EventDispatcher#setRoot(IFigure)
 */
public void setRoot(IFigure figure) {
	root = figure;
}

/**
 * @see EventDispatcher#updateCursor()
 */
protected void updateCursor() {
	Cursor newCursor = null;
	if (cursorTarget != null)
		newCursor = cursorTarget.getCursor();
	setCursor(newCursor);
}

/**
 * Updates the figure under the cursor, unless the mouse is captured, in which case all 
 * mouse events will be routed to the figure that captured the mouse.
 * @param me the mouse event
 */
protected void updateFigureUnderCursor(org.eclipse.swt.events.MouseEvent me) {
	if (!captured) {
		IFigure f = root.findFigureAt(me.x, me.y);
		setFigureUnderCursor(f);
		if (cursorTarget != hoverSource)
			updateHoverSource(me);
	}
}

/**
 * Updates the figure that will receive hover events.  The hover source must have a 
 * tooltip.  If the figure under the mouse doesn't have a tooltip set, this method will 
 * walk up the ancestor hierarchy until either a figure with a tooltip is found or it
 * gets to the root figure.
 * @param me the mouse event
 */
protected void updateHoverSource(org.eclipse.swt.events.MouseEvent me) {
	/* 
	 * Derive source from figure under cursor.
	 * Set the source in setHoverSource();
	 * If figure.getToolTip() is null, get parent's toolTip
	 * Continue parent traversal until a toolTip is found or root is reached.
	 */
	if (cursorTarget != null) {
		boolean sourceFound = false;
		Figure source = (Figure)cursorTarget;
		while (!sourceFound && source.getParent() != null) {
			if (source.getToolTip() != null)
				sourceFound = true;
			else
				source = (Figure)source.getParent();		
		}
		setHoverSource(source, me);							
	} else {
		setHoverSource(null, me);
	}	
}

}
