/*******************************************************************************
 * 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.swt.widgets;

 
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
 
/**
 * Instances of this class represent a column in a table widget.
 *  <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>LEFT, RIGHT, CENTER</dd>
 * <dt><b>Events:</b></dt>
 * <dd> Move, Resize, Selection</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles LEFT, RIGHT and CENTER may be specified.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 */
public class TableColumn extends Item {
	static final int FIRST = 0;							// index of the first column
	static final int FILL = -1;							// index that identifies the column used to 
														// fill space not used by other columns.
	private Table parent;
	private int index;									// 0-based column index
	private Rectangle bounds = new Rectangle(0, 0, 0, 0);
	private boolean isDefaultWidth = true;
	private boolean resize = true;

/**
 * Create a new TableColumn without adding it to the parent.
 * Currently used to create fill columns and default columns.
 * @see createFillColumn
 * @see createDefaultColumn
 * @param parent - Table widget the new instance will be a child of.
 */
TableColumn(Table parent) {
	super(parent, SWT.NULL);
	this.parent = parent;		
}

/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Table</code>) and a style value
 * describing its behavior and appearance. The item is added
 * to the end of the items maintained by its parent.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together 
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a composite control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT#LEFT
 * @see SWT#RIGHT
 * @see SWT#CENTER
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TableColumn(Table parent, int style) {
	this(parent, style, checkNull(parent).getColumnCount());
}

/**
 * Constructs a new instance of this class given its parent
 * (which must be a <code>Table</code>), a style value
 * describing its behavior and appearance, and the index
 * at which to place it in the items maintained by its parent.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together 
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a composite control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 * @param index the index to store the receiver in its parent
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT#LEFT
 * @see SWT#RIGHT
 * @see SWT#CENTER
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public TableColumn(Table parent, int style, int index) {
	super(parent, checkStyle (style), index);
	
	this.parent = parent;
	if (index < 0 || index > parent.getColumnCount()) {
		error(SWT.ERROR_INVALID_RANGE);
	}
	setIndex(index);	
	parent.addColumn(this);
	setDefaultWidth(true);
	addListener(SWT.Dispose, new Listener() {
		public void handleEvent(Event event) {disposeColumn();}
	});
}
/**
 * Adds the listener to the collection of listeners who will
 * be notified when the control is moved or resized, by sending
 * it one of the messages defined in the <code>ControlListener</code>
 * interface.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see ControlListener
 * @see #removeControlListener
 */
public void addControlListener(ControlListener listener) {
	checkWidget();
	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
	TypedListener typedListener = new TypedListener (listener);
	addListener (SWT.Resize,typedListener);
	addListener (SWT.Move,typedListener);
}
/**
 * Adds the listener to the collection of listeners who will
 * be notified when the control is selected, by sending
 * it one of the messages defined in the <code>SelectionListener</code>
 * interface.
 * <p>
 * <code>widgetSelected</code> is called when the column header is selected.
 * <code>widgetDefaultSelected</code> is not called.
 * </p>
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see SelectionListener
 * @see #removeSelectionListener
 * @see SelectionEvent
 */
public void addSelectionListener (SelectionListener listener) {
	checkWidget();
	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
	TypedListener typedListener = new TypedListener (listener);
	addListener (SWT.Selection,typedListener);
	addListener (SWT.DefaultSelection,typedListener);
}
/**
 * Throw an SWT.ERROR_NULL_ARGUMENT exception if 'table' is null.
 * Otherwise return 'table'
 */
static Table checkNull(Table table) {
	if (table == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
	return table;
}
static int checkStyle (int style) {
	return checkBits (style, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0);
}
protected void checkSubclass () {
	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
}
/**
 * Create a new instance of TableColumn that acts as a default column
 * if the user does not create a TableColumn.
 * @param parent - Table widget the new instance will be a child of.
 */
static TableColumn createDefaultColumn(Table parent) {
	TableColumn defaultColumn = new TableColumn(parent);
	
	defaultColumn.setIndex(FIRST);
	defaultColumn.setDefaultWidth(true);
	return defaultColumn;
}
/**
 * Create a new instance of TableColumn that acts as the rightmost 
 * fill column in a Table. The new object is not added to the parent
 * like a regular column is.
 * @param parent - Table widget the new instance will be a child of.
 */
static TableColumn createFillColumn(Table parent) {
	TableColumn fillColumn = new TableColumn(parent);
	
	fillColumn.setIndex(FILL);
	return fillColumn;
}
/**
 * Remove the receiver from its parent
 */
void disposeColumn() {
	getParent().removeColumn(this);
}
/**
 * Returns a value which describes the position of the
 * text or image in the receiver. The value will be one of
 * <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>.
 *
 * @return the alignment 
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getAlignment () {
	checkWidget();
	if ((style & SWT.LEFT) != 0) return SWT.LEFT;
	if ((style & SWT.CENTER) != 0) return SWT.CENTER;
	if ((style & SWT.RIGHT) != 0) return SWT.RIGHT;
	return SWT.LEFT;
}

/**
 * Answer the bounding rectangle of the receiver.
 */
Rectangle getBounds() {
	return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);	// copy the object to prevent changes
}
/**
 * Answer the index of the receiver. Specifies the position of the
 * receiver relative to other columns in the parent.
 */
int getIndex() {
	return index;
}
/**
 * Returns the receiver's parent, which must be a <code>Table</code>.
 *
 * @return the receiver's parent
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public Table getParent() {
	checkWidget();
	return parent;
}
/**
 * Gets the resizable attribute. A column that is
 * not resizable cannot be dragged by the user but
 * may be resized by the programmer.
 *
 * @return the resizable attribute
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public boolean getResizable() {
	checkWidget();
	return resize;
}
/**
 * Gets the width of the receiver.
 *
 * @return the width
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getWidth () {
	checkWidget();
	return getBounds().width;
}
/**
 * Set the colun bounds.
 */
void internalSetBounds(Rectangle newBounds) {
	bounds = newBounds;
}
/**
 * Answer whether the column has a default width or if a width has been 
 * set by the user.
 * @return 
 *  true=column width is a default width set internally
 *	false=column width has been set by the user.
 */
boolean isDefaultWidth() {
	return isDefaultWidth;
}
/**
 * Causes the receiver to be resized to its preferred size.
 * For a composite, this involves computing the preferred size
 * from its layout, if there is one.
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 */
public void pack() {
	checkWidget();
	Table parent = getParent();
	int index = parent.indexOf(this);

	if (getIndex() != TableColumn.FILL && index != -1) {
		setWidth(parent.getPreferredColumnWidth(index));
	}
}

/**
 * Removes the listener from the collection of listeners who will
 * be notified when the control is moved or resized.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see ControlListener
 * @see #addControlListener
 */
public void removeControlListener (ControlListener listener) {
	checkWidget();
	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (eventTable == null) return;
	eventTable.unhook (SWT.Move, listener);
	eventTable.unhook (SWT.Resize, listener);
}
/**
 * Removes the listener from the collection of listeners who will
 * be notified when the control is selected.
 *
 * @param listener the listener which should be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 *
 * @see SelectionListener
 * @see #addSelectionListener
 */
public void removeSelectionListener(SelectionListener listener) {
	checkWidget();
	if (listener == null) {
		error(SWT.ERROR_NULL_ARGUMENT);
	}
	removeListener(SWT.Selection, listener);
	removeListener(SWT.DefaultSelection, listener);
}
/**
 * Controls how text and images will be displayed in the receiver.
 * The argument should be one of <code>LEFT</code>, <code>RIGHT</code>
 * or <code>CENTER</code>.
 *
 * @param alignment the new alignment 
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setAlignment(int alignment) {
	checkWidget();
	int index = getIndex();
	
	if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) != 0 && index != 0) { // ignore calls for the first column to match Windows behavior
		style &= ~(SWT.LEFT | SWT.RIGHT | SWT.CENTER);
		style |= alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
		getParent().getHeader().redraw(index);	
	}
}
/**
 * Set the bounding rectangle of the receiver to 'newBounds'.
 * Notify the table widget if the column width changes.
 * @param newBounds - the new bounding rectangle of the receiver,
 *	consisting of x, y, width, height
 */
void setBounds(Rectangle newBounds) {
	if (newBounds.width != bounds.width) {
		if (isDefaultWidth() == true) {
			setDefaultWidth(false);
		}
		getParent().columnChange(this, newBounds);
	}
	else {
		// columnChange causes update (via scroll) which may flush redraw 
		// based on old bounds. Setting bounds after notifying table fixes 1GABZR5
		// Table sets column bounds at appropriate time when called above with 
		// width change. Only set bounds when table was not called. Fixes 1GCGDPB
		bounds = newBounds;
	}
}
/**
 * Set whether the column has a default width or if a width has been 
 * set by the user.
 * @param isDefaultWidth
 *	true=column width is a default width set internally
 *	false=column width has been set by the user
 */
void setDefaultWidth(boolean isDefaultWidth) {
	this.isDefaultWidth = isDefaultWidth;
}
public void setImage(Image image) {
	super.setImage(image);
	Header header = parent.getHeader();
	header.setHeaderHeight();
	header.redraw();
	parent.redraw();
}
/**
 * Set the index of the receiver to 'newIndex'. The index specifies the
 * position of the receiver relative to other columns in the parent.
 */
void setIndex(int newIndex) {
	this.index = newIndex;
}
/**
 * Sets the resizable attribute.  A column that is
 * not resizable cannot be dragged by the user but
 * may be resized by the programmer.
 *
 * @param resizable the resize attribute
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setResizable(boolean resize) {
	checkWidget();
	this.resize = resize;
}
public void setText(String newText) {
	checkWidget();
	int index = getIndex();
	
	if (newText == null) {
		error(SWT.ERROR_NULL_ARGUMENT);
	}
	if (index != FILL && (text == null || text.equals(newText) == false)) {
		super.setText(newText);
		getParent().getHeader().redraw(index);
	}	
}
/**
 * Sets the width of the receiver.
 *
 * @param width the new width
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public void setWidth(int width) {
	checkWidget();
	Rectangle bounds = getBounds();
	int oldWidth = bounds.width;
	int redrawX;

	if (width != oldWidth) {
		redrawX = bounds.x;
		bounds.width = width;
		setBounds(bounds);
		// redraw at old column position if column was resized wider.
		// fixes focus rectangle. 
		redrawX += Math.min(width, oldWidth);
		parent.redraw(																
			redrawX - 2, 0, 
			2, parent.getClientArea().height, false);	// redraw 2 pixels wide to redraw item focus rectangle and grid line

		sendEvent(SWT.Resize);
		int count = parent.getColumnCount();
		for (int i = index + 1; i < count; i++) {
			parent.getColumn(i).sendEvent(SWT.Move);
		}
	}
}
}
