// Copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.

package com.sun.solarisinstall.panels;

import java.awt.*;
import java.util.*;

/**
 * <CODE>Table</CODE> displays <CODE>Strings</CODE>, <CODE>Images</CODE>,
 * and numbers in row and columns.  <CODE>Rows</CODE> are added as
 * <CODE>Vectors</CODE>.<p>
 *
 * You can start an example program or view an example
 * program by using the following selections:<p>
 *
 * <menu>
 *  <li> <a href="test/TableTest.html">Run Example Program</a>
 *  <li> <a href="test/TableTest.java">View Example Program Code</a>
 * </menu>
 * 
 * @version	1.6	11/19/98
 * @author	Sun Microsystems, Inc.
 */
public class Table extends Scroller {
	private static final String sccs_id = "@(#)Table.java 1.6 98/11/19 SMI";

	/*
	public static final int READ_ONLY = 0;
	public static final int EDITABLE  = 1;
	public static final int CHOICE    = 2;
	*/

	// Components
	ViewPanel header;
	ViewPanel pinArea;
	boolean showHorzScrollbar = true;
	boolean showVertScrollbar = true;

	// Rows
	int numRows = 0;
	int numVisibleRows = 1;
	Vector[] rows;

	// Column info
	int numColumns = 0;
	ColumnData[] colInfo = new ColumnData[9];

	// Drawing info
	int firstrow = 0;
	int firstcol = 0;
	int scrollx;
	int rowh;
	int baseline;
	int hmargin = 10;
	int vmargin = 0;
	int hgap = 3;
	int vgap = 1;
	Vector pinned;
	Image viewBuffer= null;
	Image headerBuffer;
	Image pinBuffer;
	boolean showColDivs = false;
	boolean showRowDivs = false;
	//Font font = Context.getFontProperty("plainFont");
	//Font headerFont = Context.getFontProperty("labelFont");
	String fontName = "Dialog";
	Font font = new Font(fontName, Font.PLAIN, 12);
	Font headerFont = new Font(fontName, Font.BOLD, 12);

	Color divColor = Color.lightGray;

	// Selection info
	boolean multipleSelections = false;
	int selected[] = new int[0];

	// Column resizing info
	boolean columnsResizable = true;
	boolean resizing = false;
	int resizeColumn = 0;
	int startx = 0;
	int endx = 0;
	int z = 0;

	// Pool of column components
	TextField textField[] = new TextField[20];

	// Buffer for character navigation
	StringBuffer searchPattern = new StringBuffer();

	private long _lastTime  = 0;
	private long _lastIndex = 0;
	private char refChar    = 'A';

	//private String javaVersion = "1.0";
	private String javaVersion = "1.1";

	/**
	 * Constructs a <CODE>Table</CODE> with no visible rows.
	 */
	public Table() { this(0); }

	/**
	 * Constructs a <CODE>Table</CODE> with the specified number of visible rows.
	 * @param <VAR>visibleRows</VAR> The number of rows visible.
	 */
	public Table(int visibleRows) {
		add("Header", header = new ViewPanel());
		header.setVisible(false);
		rows = new Vector[40];
		numVisibleRows = visibleRows;
		vmargin = showRowDivs ? 1 : 0;
		pinned = new Vector();
		header.setForeground(Color.white);
		header.setBackground(new Color(70, 70, 170));
		viewport.setForeground(Color.black);
		viewport.setBackground(Color.white);

/*
		Color c;
		if ((c = Context.getColorProperty("Table.viewportBackground")) != null)
			viewport.setBackground(c);
		if ((c = Context.getColorProperty("Table.viewportForeground")) != null)
			viewport.setForeground(c);
		if ((c = Context.getColorProperty("Table.headerBackground")) != null)
			header.setBackground(c);
		if ((c = Context.getColorProperty("Table.headerForeground")) != null)
			header.setForeground(c);

		String s;
		if ((s = (String)Context.getProperty("javaVersion")) != null)
			javaVersion = s;
*/
	}

	public void addNotify() {
		super.addNotify();

		FontMetrics fm = getFontMetrics(font);
		if (fm != null) {
			baseline = fm.getAscent();
			rowh = fm.getHeight() + 2 * vmargin;
		}
		fm = getFontMetrics(headerFont);
		if (fm != null) {
			header.setSize(100, fm.getHeight());
		}
	}

	/**
	 * Returns the viewport area.
	 */
	public Component viewportArea() {
		return viewport;
	}

	/**
	 * Returns the column header area.
	 */
	public Component headerArea() {
		return header;
	}

	/**
	 * Scrolls the <CODE>Table</CODE> to row <VAR>x</VAR> and column <VAR>y</VAR>.
	 */
	public void scrollTo(int x, int y) {
		int vis = getVisibleColumns(firstcol, true);
		if ((x < firstcol) ||
		    (x > firstcol+vis)) {
			firstcol = Math.max(x, 0);
			vis = getVisibleColumns(numColumns, false);
			firstcol = Math.min(numColumns - vis, firstcol);
			hbar.setValue(firstcol);
			repaint();
		}
		vis = getVisibleRows();
		if ((y < firstrow) ||
		    (y > firstrow+vis)) {
			firstrow = Math.max(y - (vis / 2), 0);
			firstrow = Math.min(numRows - vis, firstrow);
			vbar.setValue(firstrow);
			repaint();
		}
	}
	/**
	 * Returns the <CODE>Table</CODE>'s scrollable area.
	 */
	public Dimension getScrollAreaSize() {
		int width = hmargin;
		for (int i=0; i<numColumns; i++) {
			width += colInfo[i].width + hmargin;
		}
		int height = numRows * (rowh+vmargin);
		return new Dimension(width, height);
	}
	/**
	 * Manages the <CODE>Table</CODE>'s scrollbars.
	 */
	public void manageScrollbars() {
		boolean visibilityChanged;

		manageVbar();
		visibilityChanged = manageHbar();
		if (visibilityChanged) {
			manageVbar();
		}
	}
	private boolean manageHbar() {
		int val, vis, min = 0, max;
		boolean changed = false;

		if (showHorzScrollbar) {
			int numcols = getNumColumns();
			int viscols = getVisibleColumns(0, true);
			if ((numcols > 1) && ((numcols - viscols) > 0)) {
				if (!hbar.isVisible()) {
					hbar.setVisible(true);
					changed = true;
				}
				Dimension vdim = getSize();
				if ((vbar != null) && vbar.isVisible()) {
					Dimension sdim = vbar.getPreferredSize();
					vdim.width -= sdim.width;
				}
				if ((pinArea != null) &&
				    pinArea.isVisible()) {
					Dimension pdim =
						pinArea.getPreferredSize();
					vdim.width -= pdim.width;
				}
				vis = getVisibleColumns(numcols, false);
				max = numcols - vis;
				if (!javaVersion.equals("1.0"))
					max++;
				val = Math.min(hbar.getValue(), max);
				hbar.setValues(val, 1, min, max);
				hbar.setUnitIncrement(1);
				hbar.setBlockIncrement(1);
				setLeftColumn(val);
//System.out.println("manageHbar: " + "val="+val + ",vis="+1 +
//                   ",min="+min + ",max="+max + ",pinc="+1);
			}
			else if (hbar.isVisible()) {
				hbar.setVisible(false);
				hbar.setValue(0);
				setLeftColumn(0);
				changed = true;
			}
		}

		return changed;
	}
	private boolean manageVbar() {
		int val, vis, min = 0, max;
		boolean changed = false;

		if (showVertScrollbar) {
			int numrows = getNumRows();
			int visrows = getVisibleRows();
			if ((numrows - visrows) > 0) {
				if (!vbar.isVisible()) {
					vbar.setVisible(true);
					changed = true;
				}
				val = vbar.getValue();
				vis = Math.max(visrows, 1);
				if (javaVersion.equals("1.0"))
					max = numrows - visrows;
				else
					max = numrows;
				vbar.setValues(val, vis, min, max);
				vbar.setUnitIncrement(1);
				vbar.setBlockIncrement(vis);
				setTopRow(val);
//System.out.println("manageVbar: " + "val="+val + ",vis="+vis +
//                   ",min="+min + ",max="+numrows + ",pinc="+vis);
			}
			else if (vbar.isVisible()) {
				vbar.setVisible(false);
				vbar.setValue(0);
				setTopRow(0);
				changed = true;
			}
		}

		return changed;
	}

	/**
	 * Returns the preferred size of the table with the specified
	 * number of rows.
	 * @param <VAR>visibleRows</VAR> The number of visible rows.
	 */
	public Dimension preferredSize(int visibleRows) {
		Dimension p = getScrollAreaSize();
		p.height    = visibleRows * (rowh + vmargin);
		if (header != null) {
			Dimension d = header.getSize();
			p.height += d.height;
		}
		return p;
	}
	/**
	 * Returns the preferred size of the table.
	 */
	public Dimension getPreferredSize() {
		return (numVisibleRows > 0) ? preferredSize(numVisibleRows)
		                            : super.getPreferredSize();
	}

	/**
	 * Adds a new row to the <CODE>Table</CODE>.
	 */
	public synchronized void addRow(Vector row) { addRow(row, -1); }

	/**
	 * Adds a new row at the specified location.
	 */
	public synchronized void addRow(Vector row, int index) {
		int i = 0;
		Vector newrow = new Vector();
		Enumeration e = row.elements();
		while (e.hasMoreElements()) {
			Object o = e.nextElement();
			if (o instanceof String) {
			}
			else if (o instanceof Image) {
				rowh = Math.max(rowh, ((Image)o).getHeight(this));
			}
			newrow.addElement(o);
			i++;
		}
		if (numColumns == 0)
			numColumns = i;

		if (numRows == rows.length) {
			Vector newrows[] = new Vector[rows.length + 100];
			System.arraycopy(rows, 0, newrows, 0, rows.length);
			rows = newrows;
		}

		if (index < -1 || index >= numRows)
			index = -1;
		if (index == -1) {
			rows[numRows++] = newrow;
		}
		else {
			for (i = numRows; i > index; i--) {
				rows[i] = rows[i-1];
			}
			rows[index] = newrow;
			numRows++;
		}

		manageScrollbars();
		repaint();
	}

	/**
	 * Sets all rows in the <CODE>Table</CODE>.
	 */
	public synchronized void setRows(Vector[] newrows) {
		rows    = newrows;
		numRows = newrows.length;
		selected = new int[0];
		manageScrollbars();
		repaint();
	}

	/**
	 * Sets the number of visible rows.
	 */
	public synchronized void setVisibleRows(int visibleRows) {
		numVisibleRows = visibleRows;
		invalidate();
	}

	/**
	 * Replaces the specified row.
	 */
	public synchronized void replaceRow(Vector newRow, int index) {
		if ((index < 0) || (index >= numRows))
			return;

		rows[index] = newRow;
		repaint();
	}

	/**
	 * Deletes the specified row.
	 */
	public synchronized void deleteRow(int index) {
		if ((index < 0) || (index >= numRows))
			return;

		// remove from row list
		for (int i=index; i<numRows-1; i++) {
			rows[i] = rows[i+1];
		}
		numRows--;

		// remove from selected array
		if (isSelected(index)) {
			int newsel[] = new int[selected.length - 1];
			if (newsel.length > 0) {
				System.arraycopy(selected, 0, newsel, 0, index);
				System.arraycopy(selected, index+1,
				                 newsel, index,
			                         selected.length-(index+1));
			}
			selected = newsel;
		}

		// fix up selected indexes
		for (int i = 0; i < selected.length; i++) {
			if (index < selected[i]) {
				selected[i]--;
			}
		}

		manageScrollbars();
		repaint();
	}

	/**
	 * Deletes all rows in the <CODE>Table</CODE>.
	 */
	public synchronized void clear() {
		rows = new Vector[100];
		numRows = 0;
		selected = new int[0];
		manageScrollbars();
		repaint();
	}

	/**
	 * Gets the item at the specified row and column.
	 */
	public Object getItem(int row, int col) {
		return rows[row].elementAt(col);
	}
	public Font     getFont()               { return font; }
	public Font     getHeaderFont()         { return headerFont; }
	public Vector   getRow(int row)         { return rows[row]; }
	public Vector[] getRows()         	{ return rows; }
	public int      getNumColumns()         { return numColumns; };
	public int      getNumRows()            { return numRows; };

	/**
	 * Gets the width of a column in pixels.
	 */
	public int      getColumnWidth(int col) { return colInfo[col].width; }
	public int      getColumnWidthInChars(int col) {
		FontMetrics fm = getToolkit().getFontMetrics(font);
		return colInfo[col].width / fm.charWidth(refChar);
	}
	//public int      getColumnType(int col)  { return colInfo[col].type; };
	public String   getColumnLabel(int col) { return colInfo[col].label; }
	public boolean  getColumnsResizable()   { return columnsResizable; }

	/**
	 * Gets the number of rows currently visible in the <CODE>Table</CODE>.
	 */
	public int getVisibleRows() {
		Dimension vdim = getSize();
		if ((header != null) && header.isVisible()) {
			Dimension hdim = header.getPreferredSize();
			vdim.height -= hdim.height;
		}
		if ((hbar != null) && hbar.isVisible()) {
			Dimension sdim = hbar.getPreferredSize();
			vdim.height -= sdim.height;
		}
		int visrows = 0;
		if (rowh > 0)
			visrows = (vdim.height-4) / (rowh + vmargin);
		return Math.max(visrows, 0);
	}

	public synchronized void setFont(Font f) {
		font = f;
		FontMetrics fm = getFontMetrics(f);
		if (fm != null) {
			baseline = fm.getAscent();
			rowh = fm.getHeight() + 2 * vmargin;
		}
		repaint();
	}
	public synchronized void setHeaderFont(Font f) {
		headerFont = f;
		FontMetrics fm = getFontMetrics(f);
		if (fm != null) {
			header.setSize(100, fm.getHeight());
		}
		if (getParent() != null) getParent().validate();
		repaint();
	}

	/**
	 * Sets the item at the specified row and column.
	 */
	public synchronized void setItem(int row, int col, Object item) {
		if ((row < 0) || (row >= numRows))
			return;

		rows[row].setElementAt(item, col);
		repaint();
	}
	/**
	 * Sets the label of the specified column.  The column width
	 * defaults to the width of the label string.
	 * @param <VAR>col</VAR> The column.
	 * @param <VAR>label</VAR> The label string.
	 */
	public synchronized void setColumnLabel(int col, String label) {
		if ((col < 0) || (col > numColumns - 1)) {
			throw new IllegalArgumentException(
			   "Column number out of range: " + col);
		}

		FontMetrics fm = getFontMetrics(headerFont);

		colInfo[col].label = label;
		colInfo[col].width = fm.stringWidth(label) + 5;
		colInfo[col].align = ALIGNMENT.LEFT;
		//colInfo[col].type = READ_ONLY;
		header.setVisible(true);
	}
	/**
	 * Sets a range of column labels (starting at zero).  Column widths
	 * default to the widths of the corresponding label strings.
	 * @param <VAR>labels</VAR> A vector of <CODE>Strings</CODE>.
	 */
	public synchronized void setColumnLabels(Vector labels) {
		FontMetrics fm = getToolkit().getFontMetrics(headerFont);
		String lab;

		setNumColumns(labels.size());
		for (int i = 0; i < labels.size(); i++) {
			lab = (String)labels.elementAt(i);
			colInfo[i].label = lab;
			colInfo[i].width = fm.stringWidth(lab) + 5;
			colInfo[i].align = ALIGNMENT.LEFT;
			//colInfo[i].type = READ_ONLY;
		}

		header.setVisible(true);
	}
	/**
	 * Sets the number of columns in the <CODE>Table</CODE>.
	 * @param <VAR>num</VAR> The number of columns.
	 */
	public synchronized void setNumColumns(int num) {
		if (num < 1) {
			throw new IllegalArgumentException(
				"Number of columns less than one");
		}

		ColumnData[] tmp = new ColumnData[num];
		int count = Math.min(num, numColumns);
		for (int i = 0; i < count; i++) {
			tmp[i] = colInfo[i];
		}
		for (int i = numColumns; i < num; i++) {
			tmp[i] = new ColumnData();
		}
		numColumns = num;
		colInfo    = tmp;
	}
	/**
	 * Sets the width of a column in pixels.
	 * @param <VAR>col</VAR> The column.
	 * @param <VAR>width</VAR> The width in pixels.
	 */
	public synchronized void setColumnWidth(int col, int width) {
		if ((col < 0) || (col > numColumns - 1)) {
			throw new IllegalArgumentException(
			   "Column number out of range: " + col);
		}

		colInfo[col].width = width;
	}
	/**
	 * Sets the width of a column in pixels with the specified alignment.
	 * @param <VAR>col</VAR> The column.
	 * @param <VAR>width</VAR> The width in pixels.
	 * @param <VAR>align</VAR> The alignment.
	 * @see sunw.admin.avm.base.ALIGNMENT
	 */
	public synchronized void setColumnWidth(int col, int width, ALIGNMENT align) {
		if ((col < 0) || (col > numColumns - 1)) {
			throw new IllegalArgumentException(
			   "Column number out of range: " + col);
		}

		colInfo[col].width = width;
		colInfo[col].align = align;
	}
	public synchronized void setColumnWidthInChars(int col, int width) {
		if ((col < 0) || (col > numColumns - 1)) {
			throw new IllegalArgumentException(
			   "Column number out of range: " + col);
		}

		FontMetrics fm = getToolkit().getFontMetrics(font);
		colInfo[col].width = width * fm.charWidth(refChar);
	}
	public synchronized void setColumnWidthInChars(int col, int width, ALIGNMENT alignment) {
		this.setColumnWidthInChars(col, width);
		colInfo[col].align = alignment;
	}
	/*
	public synchronized void setColumnType(int col, int type) {
		if ((col < 0) || (col > numColumns - 1)) {
			throw new IllegalArgumentException(
			   "Column number out of range: " + col);
		}
		colInfo[col].type = type;
		switch (type) {
		  case READ_ONLY:
		    FontMetrics fm = getToolkit().getFontMetrics(font);
		    rowh = fm.getHeight() + 2 * vmargin;
		    break;
		  case EDITABLE:
		    //Dimension d = textField[0].getPreferredSize();
		    //rowh = d.height + 2*vmargin;
		    rowh = 30;
			for (int i=0; i<20; i++) {
				textField[i] = new TextField();
				view.add(textField[i]);
			}
		    break;
		  case CHOICE:
		    break;
		}
		repaint();
	}
	*/

	/**
	 * Sets whether or not to allow interactive resizing of columns.
	 */
	public synchronized void setColumnsResizable(boolean allow) {
		Frame frame;
		columnsResizable = allow;
		if (!columnsResizable && (frame = findFrame(this)) != null)
			//frame.setCursor(Frame.DEFAULT_CURSOR);
			frame.setCursor(
				Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	/**
	 * Sets whether or not to show column dividers.
	 */
	public void showColumnDividers(boolean show) {
		if (show != showColDivs) {
			showColDivs = show;
			repaint();
		}
	}
	/**
	 * Sets whether or not to show row dividers.
	 */
	public void showRowDividers(boolean show) {
		if (show != showRowDivs) {
			showRowDivs = show;
			vmargin = showRowDivs ? 1 : 0;
			repaint();
		}
	}
	/**
	 * Sets whether or not to show horizontal scrollbar (when necessary).
	 */
	public void showHorizontalScrollbar(boolean show) {
		showHorzScrollbar = show;
		if (show) hbar.setVisible(true);
		else      hbar.setVisible(false);
	}
	/**
	 * Sets whether or not to show vertical scrollbar (when necessary).
	 */
	public void showVerticalScrollbar(boolean show) {
		showVertScrollbar = show;
		if (show) vbar.setVisible(true);
		else      vbar.setVisible(false);
	}

	// Row Selection methods
	public synchronized int getSelectedIndex() {
		int sel[] = getSelectedIndexes();
		return (sel.length == 1) ? sel[0] : -1;
	}
	public synchronized int[] getSelectedIndexes() {
		return selected;
	}
	public synchronized Vector[] getSelectedRows() {
		int sel[] = getSelectedIndexes();
		Vector vec[] = new Vector[sel.length];
		for (int i = 0; i < sel.length; i++) {
			vec[i] = rows[sel[i]];
		}
		return vec;
	}
	/**
	 * Selects the specified row, and sends a selection event.
	 */
	public synchronized void select(int index) {
		select(index, true);
	}
	/**
	 * Selects the specified row, and optionally sends a selection event.
	 */
	public void select(int index, boolean sendEvent) {
		Graphics vgc = viewport.getGraphics();
		Graphics pgc = null;

		if ((index < 0) || (index >= numRows))
			return;

		for (int i = 0; i < selected.length; i++) {
			if (selected[i] == index)
				return;
		}

		if (pinArea != null)
			pgc = pinArea.getGraphics();

		if (!multipleSelections) {
			if (selected.length > 0)
				highlight(selected[0], false, vgc, pgc);
			selected = new int[1];
			selected[0] = index;
		}
		else {
			int newsel[] = new int[selected.length + 1];
			System.arraycopy(selected, 0, newsel, 0, selected.length);
			newsel[selected.length] = index;
			selected = newsel;
		}
		highlight(index, true, vgc, pgc);
		drawDividers(vgc);

		if (sendEvent) {
			Event evt = new Event(this,
			    System.currentTimeMillis(), Event.LIST_SELECT,
			    0, 0, 0, 0, new Integer(index));
			deliverEvent(evt);
		}
	}
	/**
	 * Deselects the specified row.
	 */
	public synchronized void deselect(int index) {
		for (int i = 0; i < selected.length; i++) {
			if (selected[i] == index) {
				Graphics vgc = viewport.getGraphics();
				Graphics pgc = null;
				if (pinArea != null)
					pgc = pinArea.getGraphics();

				highlight(index, false, vgc, pgc);
				drawDividers(vgc);

				int newsel[] = new int[selected.length - 1];
				System.arraycopy(selected, 0, newsel, 0, i);
				System.arraycopy(selected, i+1, newsel, i,
					selected.length - (i+1));
				selected = newsel;

				Event evt = new Event(this,
					System.currentTimeMillis(),
					Event.LIST_DESELECT, 0, 0, 0, 0,
					new Integer(index));
				deliverEvent(evt);

				return;
			}
		}
	}
	public synchronized void deselectAll() {
		selected = new int[0];
		repaint();
	}
	public synchronized boolean isSelected(int index) {
		int sel[] = getSelectedIndexes();
		for (int i = 0; i < sel.length; i++) {
			if (sel[i] == index)
				return true;
		}
		return false;
	}
	public boolean allowsMultipleSelections() {
		return multipleSelections;
	}
	public synchronized void setMultipleSelections(boolean v) {
		if (v != multipleSelections) {
			multipleSelections = v;
			selected = new int[0];
			repaint();
		}
	}

	/**
	 * Ensures the specified row is in the visible part of the <CODE>Table</CODE>.
	 */
	public void makeRowVisible(int row) {
		scrollTo(firstcol, row);
	}

	/**
	 * Pins the specified column to the left side of the table.
	 * Pinned columns scroll vertically, but not horizontally.
	 */
	public void pinColumn(int col) {
		if (col < 0 || col >= numColumns || isPinned(col))
			return;

		pinned.addElement(new Integer(col));
		if (pinArea == null)
			add("Pin", pinArea = new ViewPanel());
		int width = 0;
		for (int i = 0; i < pinned.size(); i++) {
			int j = ((Integer)pinned.elementAt(i)).intValue();
			width += colInfo[j].width + hmargin;
		}
		pinArea.setSize(width + hmargin, 100);
		pinArea.setVisible(true);
		validate();

		Dimension d = getSize();
		Graphics gc = getGraphics();
		gc.clearRect(0, 0, d.width, d.height);

		pinBuffer = null;
	}
	/**
	 * Unpins the specified column.
	 */
	public void unpinColumn(int col) {
		if (col < 0 || col >= numColumns || !isPinned(col))
			return;

		pinned.removeElement(new Integer(col));
		if (pinned.isEmpty()) {
			remove(pinArea);
			pinArea   = null;
			pinBuffer = null;
		}
		else {
			int width = 0;
			for (int i = 0; i < pinned.size(); i++) {
				int j = ((Integer)pinned.elementAt(i)).intValue();
				width += colInfo[j].width + hmargin;
			}
			pinArea.setSize(width + hmargin, 100);
		}
		validate();
	}
	public synchronized boolean isPinned(int index) {
		return pinned.contains(new Integer(index));
	}

	public void paint(Graphics g) {
		Dimension d;
		Graphics vgc;
		Graphics pgc = null;

		// Draw border rectangle
		Rectangle v = viewport.getBounds();
		g.setColor(getBackground());
		drawBevelRect(g, v.x-2, v.y-2, v.width+4, v.height+4, 2, false);

		// Create offscreen image buffers, if necessary
		if (viewBuffer == null) {
			d = viewport.getSize();
                        if ((d.width<=0) || (d.height<=0)) {
                           return;
                        }
			viewBuffer = createImage(d.width, d.height);
			headerBuffer = createImage(d.width, rowh+vmargin);
		}
		if (pinArea != null && pinBuffer == null) {
			d = pinArea.getSize();
			pinBuffer = createImage(d.width, d.height);
		}

		drawHeader(headerBuffer.getGraphics());

		// Initialize view buffer
		d = viewport.getSize();
		vgc = viewBuffer.getGraphics();
		vgc.setColor(viewport.getBackground());
		vgc.fillRect(0, 0, d.width, d.height);
		vgc.translate(scrollx, 0);
		if (pinArea != null) {
			d = pinArea.getSize();
			pgc = pinBuffer.getGraphics();
			pgc.setColor(getBackground());
			pgc.fillRect(0, 0, d.width, d.height);
		}

		drawList(vgc, pgc);

		// Draw selection rectangle(s)
		for (int i = 0; i < selected.length; i++) {
			highlight(selected[i], true, vgc, pgc);
		}

		drawDividers(vgc);

		// Copy offscreen buffers to screen
		Graphics hgc = header.getGraphics();
		hgc.drawImage(headerBuffer, 0, 0, this);

		if (pinArea != null) {
			pgc = pinArea.getGraphics();
			pgc.drawImage(pinBuffer, 0, 0, this);
		}

		vgc = viewport.getGraphics();
		vgc.drawImage(viewBuffer, 0, 0, this);

	}

	public void update(Graphics g) {
		paint(g);
	}

	public void setBounds(int x, int y, int width, int height) {
		super.setBounds(x, y, width, height);
		headerBuffer = null;
		viewBuffer = null;
		pinBuffer = null;
	}

	public boolean handleEvent(Event evt) {
		Point origin = viewport.getLocation();
		int orientation = 0, vis = 0;
		int linc = 0, pinc = 0;
		int min = 0, max = 0;
		int x = 0, y = 0;

		if ((evt.id == Event.SCROLL_LINE_UP)   ||
		    (evt.id == Event.SCROLL_LINE_DOWN) ||
		    (evt.id == Event.SCROLL_PAGE_UP)   ||
		    (evt.id == Event.SCROLL_PAGE_DOWN) ||
		    (evt.id == Event.SCROLL_ABSOLUTE)) {
			orientation = ((Scrollbar)evt.target).getOrientation();
			min = ((Scrollbar)evt.target).getMinimum();
			max = ((Scrollbar)evt.target).getMaximum();
			vis = ((Scrollbar)evt.target).getVisibleAmount();
			linc = ((Scrollbar)evt.target).getUnitIncrement();
			pinc = ((Scrollbar)evt.target).getBlockIncrement();
		}

		switch (evt.id) {
		    case Event.MOUSE_EXIT:
			return pointerExit(evt, evt.x, evt.y);
		    case Event.MOUSE_MOVE:
			return pointerMove(evt, evt.x, evt.y);
		    case Event.MOUSE_DOWN:
			return pointerDown(evt, evt.x, evt.y);
		    case Event.MOUSE_DRAG:
			return pointerDrag(evt, evt.x, evt.y);
		    case Event.MOUSE_UP:
			return pointerUp(evt, evt.x, evt.y);

		    case Event.KEY_PRESS:
		    case Event.KEY_ACTION:
			return keyPress(evt, evt.key);

		    case Event.SCROLL_LINE_UP:
			if (orientation == Scrollbar.VERTICAL) {
				if (firstrow > 0) {
					firstrow -= linc;
					scrollRows(-linc);
				}
			}
			else {
				if (firstcol > 0) {
					firstcol -= linc;
					while (isPinned(firstcol))
						firstcol--;
					repaint();
				}
			}
			break;
		    case Event.SCROLL_LINE_DOWN:
			if (orientation == Scrollbar.VERTICAL) {
				if (firstrow < max) {
					firstrow += linc;
					scrollRows(linc);
				}
			}
			else {
				if (firstcol < max) {
					firstcol += linc;
					while (isPinned(firstcol))
						firstcol++;
					repaint();
				}
			}
			break;
		    case Event.SCROLL_PAGE_UP:
			if (orientation == Scrollbar.VERTICAL)
				firstrow = Math.max(firstrow - pinc, min);
			else {
				int vc = getVisibleColumns(firstcol-1, false);
				int col = Math.max(firstcol - vc, min);
				while (col > min && isPinned(col))
					col--;
				setLeftColumn(col);
			}
			repaint();
			break;
		    case Event.SCROLL_PAGE_DOWN:
			if (orientation == Scrollbar.VERTICAL) {
				if (javaVersion.equals("1.0"))
				    firstrow = Math.min(firstrow+pinc, max);
				else
				    firstrow = Math.min(firstrow+pinc, max-pinc);
			}
			else {
				int vc = getVisibleColumns(firstcol, true);
				int col;
				if (javaVersion.equals("1.0"))
				    col = Math.min(firstcol + vc, max);
				else
				    col = Math.min(firstcol + vc, max-1);
				while (col < max-1 && isPinned(col))
					col++;
				setLeftColumn(col);
			}
			repaint();
			break;
		    case Event.SCROLL_ABSOLUTE:
 		        //fix a problem whereby netscape scrollbars do not put
 		        //their new value in the arg field of a SCROLL_ABSOLUTE
 		        //event by querying the scrollbar itself
 		        int val;
 		        if(evt.target instanceof Scrollbar)
 			        val = ((Scrollbar)evt.target).getValue();
 		        else
 			        val = ((Integer)evt.arg).intValue();
			if (orientation == Scrollbar.VERTICAL) {
				int scrollval = val - firstrow;
				firstrow = val;
				if (Math.abs(scrollval) < getVisibleRows())
					scrollRows(scrollval);
				else
					repaint();
			}
			else {
				// find corresponding non-pinned column
				int count = 0;
				firstcol = -1;
				while (count <= val) {
					firstcol++;
					if (!isPinned(firstcol))
						count++;
				}
				repaint();
			}
			break;

		    default:
			return super.handleEvent(evt);
		}

		return false;
	}
	private void scrollRows(int nrows) {
		if (null == viewBuffer)
			return;	// make sure panel is initialized
		Dimension vd = viewport.getSize();
		Graphics vgc = viewport.getGraphics();
		Dimension pd = null;
		Graphics pgc = null;
		int next, start;
		Rectangle r;
		int visrows = getVisibleRows();

		if (pinArea != null) {
			pd  = pinArea.getSize();
			pgc = pinArea.getGraphics();
		}

		// copy still-visible rows to new position
		int dy = -nrows*(rowh+vmargin);
		int h  = (visrows-Math.abs(nrows))*(rowh+vmargin);
		if (nrows < 0) {
			next = Math.max(firstrow, 0);
			start = 0;
			vgc.copyArea(0, 0, vd.width, h, 0, dy);
			if (pgc != null)
				pgc.copyArea(0, 0, pd.width, h, 0, dy);
		}
		else {
			next = Math.min(firstrow-nrows+visrows, numRows-1);
			start = visrows - nrows;
			int y = nrows*(rowh+vmargin);
			vgc.copyArea(0, -dy, vd.width, h, 0, dy);
			if (pgc != null)
				pgc.copyArea(0, -dy, pd.width, h, 0, dy);
		}

		// clear new row drawing area
		Graphics vbgc = viewBuffer.getGraphics();
		vbgc.setColor(viewport.getBackground());
		vbgc.fillRect(0, start*(rowh+vmargin),
		             vd.width, Math.abs(nrows)*(rowh+vmargin));
		Graphics pbgc = null;
		if (pgc != null) {
			pbgc = pinBuffer.getGraphics();
			pbgc.setColor(getBackground());
			pbgc.fillRect(0, start*(rowh+vmargin),
				pd.width, Math.abs(nrows)*(rowh+vmargin));
		}

		// draw newly-visible rows
		vbgc.setFont(font);
		vbgc.setColor(viewport.getForeground());
		if (pbgc != null) {
			pbgc.setFont(font);
			pbgc.setColor(viewport.getForeground());
		}
		int row = start;
		int last = Math.min(next + Math.abs(nrows), numRows);
		for (int i = next; i < last; i++) {
			drawRow(rows[i], (rowh+vmargin)*row++, vbgc, pbgc);
		}

		// Draw selection rectangle(s) for new rows
		for (int i = 0; i < selected.length; i++) {
			if ((selected[i] >= next) && (selected[i] < last))
				highlight(selected[i], true, vbgc, pbgc);
		}

		drawDividers(vbgc);

		// Copy offscreen buffer to clipped window area
		vgc.clipRect(0, start*(rowh+vmargin),
		            vd.width, Math.abs(nrows)*(rowh+vmargin));
		vgc.drawImage(viewBuffer, 0, 0, this);
		if (pgc != null) {
			pgc.clipRect(0, start*(rowh+vmargin),
				pd.width, Math.abs(nrows)*(rowh+vmargin));
			pgc.drawImage(pinBuffer, 0, 0, this);
		}
	}
	private boolean pointerExit(Event e, int x, int y) {
		if ((e.target == viewport) && (resizing)) {
			Frame frame = findFrame(this);
			if (frame != null)
				//frame.setCursor(Frame.DEFAULT_CURSOR);
				frame.setCursor(
					Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			resizing = false;
		}
		return true;
	}
	private boolean pointerMove(Event e, int x, int y) {
		Frame frame;

		if (!columnsResizable)
			return true;

		if (inResizeArea(x, y)) {
			if (!resizing) {
				if ((frame = findFrame(this)) != null)
					//frame.setCursor(Frame.E_RESIZE_CURSOR);
					frame.setCursor(
						Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
				resizing = true;
			}
		}
		else {
			if (resizing) {
				if ((frame = findFrame(this)) != null)
					//frame.setCursor(Frame.DEFAULT_CURSOR);
					frame.setCursor(
						Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				resizing = false;
			}
		}

		return true;
	}
	private boolean pointerDrag(Event e, int x, int y) {
		if ((resizing) && (x > startx)) {
			Graphics gc = viewport.getGraphics();
			Rectangle v = viewport.getBounds();
			gc.setXORMode(Color.blue);
			gc.drawLine(endx, 0, endx, v.height);
			endx = x - v.x;
			gc.drawLine(endx, 0, endx, v.height);
		}

		return true;
	}
	private boolean pointerDown(Event e, int x, int y) {
		Rectangle sel;
		Rectangle v = viewport.getBounds();
		Rectangle p = new Rectangle();
		Graphics gc = viewport.getGraphics();

		if (pinArea != null)
			p = pinArea.getBounds();

		if ((!v.contains(x,y) && !p.contains(x,y)) ||
		    ((firstrow + (y - v.y) / (rowh + vmargin)) >
		    		numRows-1))
			return true;

		if (columnsResizable && inResizeArea(x, y)) {
			resizing = true;
			gc.setXORMode(Color.blue);
			gc.drawLine(endx, 0, endx, v.height);
		}
		else {
			int index = firstrow + (y - v.y) / (rowh+vmargin);
			select(index, false);
			Event evt = new Event(this, e.when, Event.LIST_SELECT,
				e.x, e.y, e.key, e.modifiers,
				new Integer(index));
			evt.clickCount = e.clickCount;
			// The default click count is too short, figure out
			// our own based on the timestamp
			if ((e.when - _lastTime) < 500 && (index == _lastIndex))
				evt.clickCount = 2;
			_lastTime  = e.when;
			_lastIndex = index;
			deliverEvent(evt);
		}

		return true;
	}
	private boolean pointerUp(Event e, int x, int y) {
		if (resizing) {
			Graphics gc = viewport.getGraphics();
			Dimension d = viewport.getSize();
			gc.setXORMode(Color.blue);
			gc.drawLine(endx, 0, endx, d.height);
			setColumnWidth(resizeColumn, endx-startx-(hmargin/2));

			if (!inResizeArea(x, y)) {
				Frame frame;
				if ((frame = findFrame(this)) != null)
					//frame.setCursor(Frame.DEFAULT_CURSOR);
					frame.setCursor(
						Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
			resizing = false;
			manageScrollbars();
			repaint();
		}

		return true;
	}
	private boolean keyPress(Event e, int key) {
/*
		int col = -1;
		for (int i=0; i<numColumns; i++) {
			if (rows[0].elementAt(i) instanceof String) {
				col = i;
				break;
			}
		}
		if (col == -1)
			return true;

		int found;
		searchPattern.setLength(0);
		searchPattern.append((char)key);
		if ((found = findItem(col, new String(searchPattern))) > -1) {
			select(found);
			makeRowVisible(found);
		}
*/

		return false;
	}

	private synchronized void setTopRow(int row) {
		if ((row >=0) && (row < numRows)) {
			firstrow = row;
			vbar.setValue(row);
			repaint();
		}
	}
	private synchronized void setLeftColumn(int col) {
		if ((col >= 0) && (col < numColumns)) {
			firstcol = col;
			hbar.setValue(col);
			repaint();
		}
	}
	private int getVisibleColumns(int col, boolean forward) {
		Dimension vdim = getSize();
		if ((vbar != null) && vbar.isVisible()) {
			Dimension sdim = vbar.getPreferredSize();
			vdim.width -= sdim.width;
		}
		if ((pinArea != null) && pinArea.isVisible()) {
			Dimension pdim = pinArea.getPreferredSize();
			vdim.width -= pdim.width;
		}

		if (col < 0)
			col = 0;
		if (col > numColumns-1)
			col = numColumns-1;

		boolean done = false;
		int i = col, colw = 0, cnt = 0, inc = forward ? 1 : -1;
		while (!done) {
			if (!isPinned(i)) {
				colw += hmargin + colInfo[i].width;
				if (colw > vdim.width-4) {
					break;
				}
				cnt++;
			}
			i += inc;
			done = forward ? i > numColumns-1 : i < 0;
		}

		return cnt;
	}

	private void highlight(int index, boolean on,
	                       Graphics vgc, Graphics pgc) {
		Dimension dim = viewport.getSize();
		Color f, b;

		if ((index < 0) || (index > firstrow+getVisibleRows()))
			return;

		if (on) {
			f = viewport.getForeground();
			b = viewport.getBackground();
		}
		else {
			f = viewport.getBackground();
			b = viewport.getForeground();
		}

		vgc.translate(0, 0);
		Rectangle sel = new Rectangle(
			0, (index - firstrow) * (rowh + vmargin),
			dim.width, rowh);

		vgc.setColor(f);
		vgc.fillRect(sel.x, sel.y, sel.width, sel.height);
		vgc.setColor(b);
		vgc.translate(scrollx, 0);
		vgc.setFont(font);

		if (pgc != null) {
			dim = pinArea.getSize();
			pgc.setColor(f);
			pgc.fillRect(sel.x, sel.y, dim.width, sel.height);
			pgc.setColor(b);
			pgc.setFont(font);
		}

		drawRow(rows[index], sel.y, vgc, pgc);
	}

	private void drawRow(Vector v, int yoffset,
	                     Graphics vgc, Graphics pgc) {
		FontMetrics fm = getToolkit().getFontMetrics(font);
		Graphics tgc = null;
		int strh = fm.getHeight();
		int vcolx = hmargin;
		int pcolx = hmargin;
		int off = 0;

		for (int j = 0; j < numColumns; j++) {
			int valw;
			int valh;
			Object o;

			try { o = v.elementAt(j); }
			catch (ArrayIndexOutOfBoundsException e) { break; }

			if (isPinned(j)) {
				tgc = pgc.create();
				tgc.translate(pcolx, yoffset);
			}
			else if (j >= firstcol) {
				tgc = vgc.create();
				tgc.translate(vcolx, yoffset);
			}
			else {
				continue;
			}
			tgc.clipRect(0, 0, colInfo[j].width, rowh);

			if (o instanceof Image) {
				valw = ((Image)o).getWidth(this);
				valh = ((Image)o).getHeight(this);
				if      (colInfo[j].align == ALIGNMENT.LEFT)
					off = 0;
				else if (colInfo[j].align == ALIGNMENT.CENTER)
					off = (colInfo[j].width-valw)/2;
				else if (colInfo[j].align == ALIGNMENT.RIGHT)
					off = colInfo[j].width-valw;
				tgc.drawImage((Image)o, off, (rowh-valh)/2, this);
			}
                        else {
                                String str;
                                if (o instanceof String) {
                                        str = (String)o;
                                }
                                else {
                                        str = o.toString();
                                }

                                valw = fm.stringWidth(str);
                                if      (colInfo[j].align == ALIGNMENT.LEFT)
                                        off = 0;
                                else if (colInfo[j].align == ALIGNMENT.CENTER)
                                        off = (colInfo[j].width-valw)/2;
                                else if (colInfo[j].align == ALIGNMENT.RIGHT)
                                        off = colInfo[j].width-valw;
                                /*
                                else if (colInfo[j].type == EDITABLE) {
                                        textField[z].setBounds(
                                                colx+scrollx, yoffset+1,
                                                colInfo[j].width,
                                                rowh-vmargin-1);
                                        textField[z++].setText(str);
                                }
                                */
                                tgc.drawString(str, off, (rowh-strh)/2 + baseline);
                        }
			if (isPinned(j))
				pcolx += colInfo[j].width + hmargin;
			else
				vcolx += colInfo[j].width + hmargin;
		}
	}

	private void drawList(Graphics vgc, Graphics pgc) {
		vgc.setFont(font);
		vgc.setColor(viewport.getForeground());
		if (pgc != null) {
			pgc.setFont(font);
			pgc.setColor(viewport.getForeground());
		}
		int k = 0;
		int last = Math.min(firstrow+getVisibleRows(), numRows);
		for (int i = firstrow; i < last; i++) {
			drawRow(rows[i], (rowh + vmargin)*k++, vgc, pgc);
		}
	}

	private void drawHeader(Graphics gc) {
		FontMetrics fm = getToolkit().getFontMetrics(headerFont);
		int off = 0;
		int vcolx = hmargin + 2;
		int pcolx = hmargin + 2;

		if ((header != null) && (header.isVisible())) {
			Rectangle v = viewport.getBounds();
			Rectangle h = header.getBounds();
			gc.setColor(header.getBackground());
			gc.fillRect(0, 0, h.width, h.height);
			gc.setFont(headerFont);
			int baseln = fm.getAscent();
			for (int i = 0; i < numColumns; i++) {
				int labwidth = fm.stringWidth(colInfo[i].label);
				if      (colInfo[i].align == ALIGNMENT.LEFT)
					off = 0;
				else if (colInfo[i].align == ALIGNMENT.CENTER)
					off = (colInfo[i].width - labwidth) / 2;
				else if (colInfo[i].align == ALIGNMENT.RIGHT)
					off = colInfo[i].width - labwidth;
				gc.setColor(header.getForeground());
				Graphics tgc = gc.create();
				if (isPinned(i))
					tgc.translate(pcolx, 0);
				else if (i >= firstcol)
					tgc.translate(v.x+scrollx+vcolx, 0);
				else continue;
				tgc.clipRect(0, 0, colInfo[i].width, rowh);
				tgc.drawString(colInfo[i].label, off, baseln);
				if (isPinned(i)) {
					pcolx += colInfo[i].width + hmargin;
				}
				else {
					if (showColDivs) {
						int x = vcolx +
						   colInfo[i].width+(hmargin/2);
						gc.setColor(divColor);
						gc.drawLine(x, 0, x, h.height);
					}
					vcolx += colInfo[i].width + hmargin;
				}
			}
		}
	}

	private void drawBevelRect(Graphics g, int x, int y, int width,
                              int height, int linewidth, boolean raised) {
		Color c = g.getColor();
		Color brighter = c.brighter();
		Color darker = c.darker();

		g.setColor(raised ? brighter : darker);
		for (int i = 0; i < linewidth; i++) {
			g.drawLine(x+i, y+i, x+i, y+height-i);
			g.drawLine(x+i+1, y+i, x+width-i-1, y+i);
		}
		g.setColor(raised ? darker : brighter);
		for (int i = 0; i < linewidth; i++) {
			g.drawLine(x+i+1, y+height-i, x+width-i, y+height-i);
			g.drawLine(x+width-i, y+i, x+width-i, y+height-i-1);
		}

		g.setColor(c);
	}

	private void drawDividers(Graphics g) {
		int i, colx, rowy, off;
		Dimension v = viewport.getSize();
		g.setColor(divColor);

		// Draw row dividers
		if (showRowDivs) {
			rowy = 0;
			int vis = getVisibleRows();
			for (i = 0; i < vis; i++) {
				rowy += rowh;
				off = rowy + (vmargin/2);
				g.drawLine(0, off, v.width, off);
				rowy += vmargin;
			}
		}

		// Draw column dividers
		if (showColDivs) {
			colx = hmargin;
			for (i = firstcol;
			     i < Math.min(firstcol+numColumns-1, numColumns-1);
			     i++) {
				if (!isPinned(i)) {
					colx += colInfo[i].width;
					off = colx + (hmargin/2);
					g.drawLine(off, 0, off, v.height);
					colx += hmargin;
				}
			}
		}
	}

	private boolean inResizeArea(int x, int y) {
		Rectangle v = viewport.getBounds();
		if (!v.contains(x, y))
			return false;

		x -= v.x;
		int colx = hmargin;
		for (int i = 0; i < numColumns; i++) {
			if (!isPinned(i) && i >= firstcol) {
				colx += colInfo[i].width;
				if ((x > colx) && (x < colx + hmargin)) {
					resizeColumn = i;
					startx = colx - colInfo[i].width;
					endx = colx + (hmargin/2);
					return true;
				}
				colx += hmargin;
			}
		}

		return false;
	}

	/**
	 * Return class version string.
	 */
	public String getClassVersion() { return (sccs_id); }

	/**
	 * Returns the parameter <CODE>String</CODE> of this <CODE>Table</CODE>.
	 */
	protected String paramString() {
		return super.paramString() + ",numRows=" + numRows +
			",numColumns=" + numColumns;
	}

	/**
	 * Traverses the widget hierarchy to locate ancestor <CODE>Frame</CODE>.
	 * @param <VAR>component</VAR>	<CODE>Component</CODE> descending
	 * from a <CODE>Frame</CODE>.
	 * @return	The ancestor <CODE>Frame</CODE> or null if one was not found.
	 * @see java.awt.Frame
	*/
	private static Frame findFrame(Component component) {
		while ( component != null && !(component instanceof Frame))
			component = component.getParent();
		return (Frame) component;
	}

}

class ColumnData {
	String		label;
	int		width;
	ALIGNMENT	align;
	int		type;

	public ColumnData() {
		label = "";
		width = 0;
		align = ALIGNMENT.LEFT;
		type  = 0;
	}
}
