/*******************************************************************************
 * 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.jface.viewers;

import org.eclipse.swt.events.HelpEvent;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;

import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.util.ListenerList;
import org.eclipse.jface.util.SafeRunnable;

/**
 * A viewer is a model-based adapter on a widget.
 * <p>
 * A viewer can be created as an adapter on a pre-existing control (e.g., 
 * creating a <code>ListViewer</code> on an existing <code>List</code> control).
 * All viewers also provide a convenience constructor for creating the control.
 * </p>
 * <p>
 * Implementing a concrete viewer typically involves the following steps:
 * <ul>
 * <li>
 * create SWT controls for viewer (in constructor) (optional)
 * </li>
 * <li>
 * initialize SWT controls from input (inputChanged)
 * </li>
 * <li>
 * define viewer-specific update methods
 * </li>
 * <li>
 * support selections (<code>setSelection</code>, <code>getSelection</code>)
 * </li>
 * </ul>
 * </p>
 */
public abstract class Viewer implements IInputSelectionProvider {

	/**
	 * List of selection change listeners (element type: <code>ISelectionChangedListener</code>).
	 *
	 * @see #fireSelectionChanged
	 */
	private ListenerList selectionChangedListeners = new ListenerList(3);

	/**
	 * List of help request listeners (element type: <code>org.eclipse.swt.events.HelpListener</code>).
	 * Help request listeners.
	 *
	 * @see #handleHelpRequest
	 */
	private ListenerList helpListeners = new ListenerList(1);

	/**
	 * The names of this viewer's properties.
	 * <code>null</code> if this viewer has no properties.
	 *
	 * @see #setData
	 */
	private String[] keys;

	/**
	 * The values of this viewer's properties.
	 * <code>null</code> if this viewer has no properties.
	 * This array parallels the value of the <code>keys</code> field.
	 *
	 * @see #setData
	 */
	private Object[] values;

	/**
	 * Remembers whether we've hooked the help listener on the control or not.
	 */
	private boolean helpHooked = false;

	/**
	 * Help listener for the control, created lazily when client's first help listener is added.
	 */
	private HelpListener helpListener = null;
	
	/**
	 * Unique key for associating element data with widgets.
	 * @see org.eclipse.swt.widgets.Widget#setData(String, Object)
	 */
	protected static final String WIDGET_DATA_KEY = "org.eclipse.jface.viewers.WIDGET_DATA";//$NON-NLS-1$
/**
 * Creates a new viewer.
 */
protected Viewer() {
}
/**
 * Adds a listener for help requests in this viewer.
 * Has no effect if an identical listener is already registered.
 *
 * @param listener a help listener
 */
public void addHelpListener(HelpListener listener) {
	helpListeners.add(listener);
	if (!helpHooked) {
		Control control = getControl();
		if (control != null && !control.isDisposed()) {
			if (this.helpListener == null) {
				this.helpListener = new HelpListener() {
					public void helpRequested(HelpEvent event) {
						handleHelpRequest(event);
					}
				};
			}
			control.addHelpListener(this.helpListener);
			helpHooked = true;
		}
	}
}
/* (non-Javadoc)
 * Method declared on ISelectionProvider.
 */
public void addSelectionChangedListener(ISelectionChangedListener listener) {
	selectionChangedListeners.add(listener);
}
/**
 * Notifies any help listeners that help has been requested.
 * Only listeners registered at the time this method is called are notified.
 *
 * @param event a help event
 *
 * @see HelpListener#helpRequested(org.eclipse.swt.events.HelpEvent)
 */
protected void fireHelpRequested(HelpEvent event) {
	Object[] listeners = helpListeners.getListeners();
	for (int i = 0; i < listeners.length; ++i) {
		((HelpListener) listeners[i]).helpRequested(event);
	}
}
/**
 * Notifies any selection changed listeners that the viewer's selection has changed.
 * Only listeners registered at the time this method is called are notified.
 *
 * @param event a selection changed event
 *
 * @see ISelectionChangedListener#selectionChanged
 */
protected void fireSelectionChanged(final SelectionChangedEvent event) {
	Object[] listeners = selectionChangedListeners.getListeners();
	for (int i = 0; i < listeners.length; ++i) {
		final ISelectionChangedListener l = (ISelectionChangedListener)listeners[i];
		Platform.run(new SafeRunnable() {
			public void run() {
				l.selectionChanged(event);
			}
		});		
	}	
}
/**
 * Returns the primary control associated with this viewer.
 *
 * @return the SWT control which displays this viewer's content
 */
public abstract Control getControl();
/**
 * Returns the value of the property with the given name,
 * or <code>null</code> if the property is not found.
 * <p>
 * The default implementation performs a (linear) search of
 * an internal table. Overriding this method is generally not
 * required if the number of different keys is small. If a more
 * efficient representation of a viewer's properties is required,
 * override both <code>getData</code> and <code>setData</code>.
 * </p>
 *
 * @param key the property name
 * @return the property value, or <code>null</code> if
 *    the property is not found
 */
public Object getData(String key) {
	Assert.isNotNull(key);
	if (keys == null) 
		return null;
	for (int i=0; i < keys.length; i++) {
		if (keys[i].equals(key)) 
			return values[i];
	}
	return null;
}
/* (non-Javadoc)
 * Copy-down of method declared on <code>IInputProvider</code>.
 */
public abstract Object getInput();
/* (non-Javadoc)
 * Copy-down of method declared on <code>ISelectionProvider</code>.
 */
public abstract ISelection getSelection();
/**
 * Handles a help request from the underlying SWT control.
 * The default behavior is to fire a help request,
 * with the event's data modified to hold this viewer.
 */
protected void handleHelpRequest(HelpEvent event) {
	Object oldData = event.data;
	event.data = this;
	fireHelpRequested(event);
	event.data = oldData;
}
/**
 * Internal hook method called when the input to this viewer is
 * initially set or subsequently changed.
 * <p>
 * The default implementation does nothing. Subclassers may override 
 * this method to do something when a viewer's input is set.
 * A typical use is populate the viewer.
 * </p>
 *
 * @param input the new input of this viewer, or <code>null</code> if none
 * @param oldInput the old input element or <code>null</code> if there
 *   was previously no input
 */
protected void inputChanged(Object input, Object oldInput) {
}
/**
 * Refreshes this viewer completely with information freshly obtained from this
 * viewer's model.
 */
public abstract void refresh();
/**
 * Removes the given help listener from this viewer.
 * Has no affect if an identical listener is not registered.
 *
 * @param listener a help listener
 */
public void removeHelpListener(HelpListener listener) {
	helpListeners.remove(listener);
	if (helpListeners.size() == 0) {
		Control control = getControl();
		if (control != null && !control.isDisposed()) {
			control.removeHelpListener(this.helpListener);
			helpHooked = false;
		}
	}
}
/* (non-Javadoc)
 * Method declared on ISelectionProvider.
 */
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
	selectionChangedListeners.remove(listener);
}
/**
 * Scrolls the viewer's control down by one item from the given
 * display-relative coordinates.  Returns the newly revealed Item,
 * or <code>null</code> if no scrolling occurred or if the viewer
 * doesn't represent an item-based widget.
 *
 * @param x horizontal coordinate
 * @param y vertical coordinate
 */
public Item scrollDown(int x, int y) {
	return null;
}
/**
 * Scrolls the viewer's control up by one item from the given
 * display-relative coordinates.  Returns the newly revealed Item,
 * or <code>null</code> if no scrolling occurred or if the viewer
 * doesn't represent an item-based widget.
 *
 * @param x horizontal coordinate
 * @param y vertical coordinate
 */
public Item scrollUp(int x, int y) {
	return null;
}
/**
 * Sets the value of the property with the given name to the
 * given value, or to <code>null</code> if the property is to be
 * removed. If this viewer has such a property, its value is
 * replaced; otherwise a new property is added.
 * <p>
 * The default implementation records properties in an internal
 * table which is searched linearly. Overriding this method is generally not
 * required if the number of different keys is small. If a more
 * efficient representation of a viewer's properties is required,
 * override both <code>getData</code> and <code>setData</code>.
 * </p>
 *
 * @param key the property name
 * @param value the property value, or <code>null</code> if
 *    the property is not found
 */
public void setData (String key, Object value) {
	Assert.isNotNull(key);
	/* Remove the key/value pair */
	if (value == null) {
		if (keys == null) return;
		int index = 0;
		while (index < keys.length && !keys[index].equals (key)) index++;
		if (index == keys.length) return;
		if (keys.length == 1) {
			keys = null;
			values = null;
		} else {
			String [] newKeys = new String[keys.length - 1];
			Object [] newValues = new Object[values.length - 1];
			System.arraycopy(keys, 0, newKeys, 0, index);
			System.arraycopy(keys, index + 1, newKeys, index, newKeys.length - index);
			System.arraycopy(values, 0, newValues, 0, index);
			System.arraycopy(values, index + 1, newValues, index, newValues.length - index);
			keys = newKeys;
			values = newValues;
		}
		return;
	}
	
	/* Add the key/value pair */
	if (keys == null) {
		keys = new String[] {key};
		values = new Object[] {value};
		return;
	}
	for (int i=0; i < keys.length; i++) {
		if (keys [i].equals(key)) {
			values [i] = value;
			return;
		}
	}
	String [] newKeys = new String[keys.length + 1];
	Object [] newValues = new Object[values.length + 1];
	System.arraycopy(keys, 0, newKeys, 0, keys.length);
	System.arraycopy(values, 0, newValues, 0, values.length);
	newKeys [keys.length] = key;
	newValues [values.length] = value;
	keys = newKeys;
	values = newValues;
}
/**
 * Sets or clears the input for this viewer.
 *
 * @param input the input of this viewer, or <code>null</code> if none
 */
public abstract void setInput(Object input);
/**
 * The viewer implementation of this <code>ISelectionProvider</code> 
 * method make the new selection for this viewer without making it visible.
 * <p>
 * This method is equivalent to <code>setSelection(selection,false)</code>.
 * </p>
 */
public void setSelection(ISelection selection) {
	setSelection(selection, false);
}
/**
 * Sets a new selection for this viewer and optionally makes it visible.
 * <p>
 * Subclasses must implement this method.
 * </p>
 *
 * @param selection the new selection
 * @param reveal <code>true</code> if the selection is to be made
 *   visible, and <code>false</code> otherwise
 */
public abstract void setSelection(ISelection selection, boolean reveal);
}
