/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.forms.widgets;
import java.util.Vector;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.forms.events.*;
import org.eclipse.ui.internal.forms.widgets.FormsResources;
/**
 * This is the base class for custom hyperlink widget. It is responsible for
 * processing mouse and keyboard events, and converting them into unified
 * hyperlink events. Subclasses are responsible for rendering the hyperlink in
 * the client area.
 * 
 * @since 3.0
 */
public abstract class AbstractHyperlink extends Canvas {
	private boolean hasFocus;
	private Vector listeners;
	/**
	 * Amount of the margin width around the hyperlink (default is 1).
	 */
	protected int marginWidth = 1;
	/**
	 * Amount of the margin height around the hyperlink (default is 1).
	 */
	protected int marginHeight = 1;
	/**
	 * Creates a new hyperlink in the provided parent.
	 * 
	 * @param parent
	 *            the control parent
	 * @param style
	 *            the widget style
	 */
	public AbstractHyperlink(Composite parent, int style) {
		super(parent, style);
		addListener(SWT.KeyDown, new Listener() {
			public void handleEvent(Event e) {
				if (e.character == '\r') {
					handleActivate(e);
				}
			}
		});
		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				paint(e);
			}
		});
		addListener(SWT.Traverse, new Listener() {
			public void handleEvent(Event e) {
				switch (e.detail) {
					case SWT.TRAVERSE_PAGE_NEXT :
					case SWT.TRAVERSE_PAGE_PREVIOUS :
					case SWT.TRAVERSE_ARROW_NEXT :
					case SWT.TRAVERSE_ARROW_PREVIOUS :
					case SWT.TRAVERSE_RETURN :
						e.doit = false;
						return;
				}
				e.doit = true;
			}
		});
		Listener listener = new Listener() {
			public void handleEvent(Event e) {
				switch (e.type) {
					case SWT.FocusIn :
						hasFocus = true;
						handleEnter(e);
						break;
					case SWT.FocusOut :
						hasFocus = false;
						handleExit(e);
						break;
					case SWT.DefaultSelection :
						handleActivate(e);
						break;
					case SWT.MouseEnter :
						handleEnter(e);
						break;
					case SWT.MouseExit :
						handleExit(e);
						break;
					case SWT.MouseUp :
						handleMouseUp(e);
						break;
				}
			}
		};
		addListener(SWT.MouseEnter, listener);
		addListener(SWT.MouseExit, listener);
		addListener(SWT.MouseUp, listener);
		addListener(SWT.FocusIn, listener);
		addListener(SWT.FocusOut, listener);
		setCursor(FormsResources.getHandCursor());
	}
	/**
	 * Adds the event listener to this hyperlink.
	 * 
	 * @param listener
	 *            the event listener to add
	 */
	public void addHyperlinkListener(IHyperlinkListener listener) {
		if (listeners == null)
			listeners = new Vector();
		if (!listeners.contains(listener))
			listeners.add(listener);
	}
	/**
	 * Removes the event listener from this hyperlink.
	 * 
	 * @param listener
	 *            the event listener to remove
	 */
	public void removeHyperlinkListener(IHyperlinkListener listener) {
		if (listeners == null)
			return;
		listeners.remove(listener);
	}
	/**
	 * Returns the selection state of the control. When focus is gained, the
	 * state will be <samp>true </samp>; it will switch to <samp>false </samp>
	 * when the control looses focus.
	 * 
	 * @return <code>true</code> if the widget has focus, <code>false</code>
	 *         otherwise.
	 */
	public boolean getSelection() {
		return hasFocus;
	}
	/**
	 * Called when hyperlink is entered. Subclasses that override this method
	 * must call 'super'.
	 */
	protected void handleEnter(Event e) {
		redraw();
		if (listeners == null)
			return;
		int size = listeners.size();
		HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask);
		for (int i = 0; i < size; i++) {
			IHyperlinkListener listener = (IHyperlinkListener) listeners.get(i);
			listener.linkEntered(he);
		}
	}
	/**
	 * Called when hyperlink is exited. Subclasses that override this method
	 * must call 'super'.
	 */
	protected void handleExit(Event e) {
		redraw();
		if (listeners == null)
			return;
		int size = listeners.size();
		HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask);
		for (int i = 0; i < size; i++) {
			IHyperlinkListener listener = (IHyperlinkListener) listeners.get(i);
			listener.linkExited(he);
		}
	}
	/**
	 * Called when hyperlink has been activated. Subclasses that override this
	 * method must call 'super'.
	 */
	protected void handleActivate(Event e) {
		if (listeners == null)
			return;
		int size = listeners.size();
		setCursor(FormsResources.getBusyCursor());
		HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), e.stateMask);
		for (int i = 0; i < size; i++) {
			IHyperlinkListener listener = (IHyperlinkListener) listeners.get(i);
			listener.linkActivated(he);
		}
		if (!isDisposed())
			setCursor(FormsResources.getHandCursor());
	}
	/**
	 * Sets the object associated with this hyperlink. Concrete implementation
	 * of this class can use if to store text, URLs or model objects that need
	 * to be processed on hyperlink events.
	 * 
	 * @param href
	 *            the hyperlink object reference
	 */
	public void setHref(Object href) {
		setData("href", href);
	}
	/**
	 * Returns the object associated with this hyperlink.
	 * 
	 * @see #setHref
	 * @return the hyperlink object reference
	 */
	public Object getHref() {
		return getData("href");
	}
	/**
	 * Returns the textual representation of this hyperlink suitable for
	 * showing in tool tips or on the status line.
	 * 
	 * @return the hyperlink text
	 */
	public String getText() {
		return getToolTipText();
	}
	/**
	 * Paints the hyperlink as a reaction to the provided paint event.
	 * 
	 * @param e
	 *            the paint event
	 */
	protected abstract void paintHyperlink(GC gc);
	/**
	 * Paints the control as a reaction to the provided paint event.
	 * 
	 * @param e
	 *            the paint event
	 */
	protected void paint(PaintEvent e) {
		GC gc = e.gc;
		Rectangle clientArea = getClientArea();
		if (clientArea.width==0 || clientArea.height==0) return;
		Image buffer = new Image(getDisplay(), clientArea.width, clientArea.height);
		buffer.setBackground(getBackground());
		GC bufferGC = new GC(buffer, gc.getStyle());
		bufferGC.setBackground(getBackground());
		bufferGC.fillRectangle(0, 0, clientArea.width, clientArea.height);
		paintHyperlink(bufferGC);
		if (hasFocus) {
			Rectangle carea = getClientArea();
			bufferGC.setForeground(getForeground());
			bufferGC.drawFocus(0, 0, carea.width, carea.height);
		}
		gc.drawImage(buffer, 0, 0);
		bufferGC.dispose();
		buffer.dispose();
	}
	private void handleMouseUp(Event e) {
		if (e.button != 1)
			return;
		Point size = getSize();
		// Filter out mouse up events outside
		// the link. This can happen when mouse is
		// clicked, dragged outside the link, then
		// released.
		if (e.x < 0)
			return;
		if (e.y < 0)
			return;
		if (e.x >= size.x)
			return;
		if (e.y >= size.y)
			return;
		handleActivate(e);
	}
}