/*
 * @(#)ColumnList.java	1.5 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media.ui;

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


public class ColumnList extends Canvas implements MouseListener, FocusListener, KeyListener, ComponentListener {

    public static final int       TYPE_INTEGER = 1;
    public static final int       TYPE_DOUBLE = 2;
    public static final int       TYPE_STRING = 3;
    public static final int       TYPE_DATE = 4;

    private static final int      MARGIN_VERT = 2;
    private static final int      MARGIN_HORZ = 6;

    private static final Color    COLOR_HEADER_BG = Color.lightGray;
    private static final Color    COLOR_HEADER_FG = Color.black;
    private static final Color    COLOR_SHADOW_TOP = Color.white;
    private static final Color    COLOR_SHADOW_BOTTOM = Color.darkGray;
//    private static final Color    COLOR_SEL_BG = new Color ( 0, 0, 128 );
//    private static final Color    COLOR_SEL_FG = Color.yellow;
    private static final Color    COLOR_SEL_BG = Color.white;
    private static final Color    COLOR_SEL_FG = Color.black;

    private Vector     vectorColumns = new Vector (); // array of ColumnData
    private Vector     vectorRows = new Vector (); // array of RowData

    private boolean    boolFocus = false;
    private boolean    boolSetColumnWidthAsPreferred = false;
    private int        nScrollPosHorz = 0;
    private int        nScrollPosVert = 0;
    private int        nCurrentIndex = 0;
    private int        nVisibleRows = 1;

    private Font       fontHeader = new Font ( "Dialog", Font.PLAIN, 12 );
    private Font       fontItem = new Font ( "Dialog", Font.PLAIN, 12 );
    private int        nHeightHeader;
    private int        nHeightRow;

    public ColumnList ( String arrColumnNames[] ) {
        super ();

        int           i;
        int           nCount;
        ColumnData    column;

        nCount = arrColumnNames.length;
        for ( i = 0;  i < nCount;  i++ ) {
            column = new ColumnData ( arrColumnNames[i], TYPE_STRING );
            vectorColumns.addElement ( column );
        }

        try {
            init();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void addRow ( Object arrValues[] ) {
        RowData   rowData;

        rowData = new RowData ( arrValues );
        vectorRows.addElement ( rowData );
        repaint ();
    }

    public void removeRow ( int nRowIndex ) {
        vectorRows.removeElementAt ( nRowIndex );
        repaint ();
    }

    public void setCellValue ( Object value, int nRowIndex, int nColumnIndex ) {
        RowData   rowData;

        rowData = (RowData) vectorRows.elementAt ( nRowIndex );
        rowData.setValue ( value, nColumnIndex );
        repaint ();
    }

    public void setColumnWidth ( int nWidth, int nColumnIndex ) {
        ColumnData   columnData;

        columnData = (ColumnData) vectorColumns.elementAt ( nColumnIndex );
        columnData.nWidth = nWidth;
        repaint ();
    }

    public void setColumnWidth ( int nWidth ) {
        int          i;
        int          nCount;
        ColumnData   columnData;


        nCount = vectorColumns.size ();
        for ( i = 0;  i < nCount;  i++ ) {
            columnData = (ColumnData) vectorColumns.elementAt ( i );
            columnData.nWidth = nWidth;
        }
        repaint ();
    }

    public void setColumnWidthAsPreferred ( int nColumnIndex ) {
        int          nWidth;
        ColumnData   columnData;

        columnData = (ColumnData) vectorColumns.elementAt ( nColumnIndex );
        nWidth = getPreferredColumnWidth ( nColumnIndex );
        columnData.nWidth = nWidth;
        repaint ();
    }

    public void setColumnWidthAsPreferred () {
        int          i;
        int          nCount;
        int          nWidth;
        int          nWidthTotal;
        ColumnData   columnData;
        Rectangle    rect;
        int          nWidthExtra;


        nCount = vectorColumns.size ();
        nWidthTotal = 0;
        for ( i = 0;  i < nCount;  i++ ) {
            columnData = (ColumnData) vectorColumns.elementAt ( i );
            nWidth = getPreferredColumnWidth ( i );
            columnData.nWidth = nWidth;
            nWidthTotal += nWidth;
        }
        rect = getBounds ();
        if ( rect.width < 1 )
            boolSetColumnWidthAsPreferred = true;
        rect.width -= 2;
        if ( rect.width > nWidthTotal ) {
            nWidthExtra = (rect.width - nWidthTotal) / nCount;
            nWidthTotal = rect.width;
            for ( i = 0;  i < nCount;  i++ ) {
                columnData = (ColumnData) vectorColumns.elementAt ( i );
                if ( i < nCount - 1 )
                    columnData.nWidth += nWidthExtra;
                else
                    columnData.nWidth = nWidthTotal;
                nWidthTotal -= columnData.nWidth;
            }
        }
        repaint ();
    }

    public Dimension getPreferredSize () {
        int          i;
        int          nCount;
        Dimension    dim;
        int          nWidth;
        int          nHeight;


        dim = new Dimension ();

        dim.height += this.nHeightHeader;
        nHeight = this.nHeightRow;
        nHeight *= vectorRows.size ();
        dim.height += nHeight; 

        // columns
        nCount = vectorColumns.size ();
        for ( i = 0;  i < nCount;  i++ ) {
            nWidth = this.getPreferredColumnWidth ( i );
            dim.width += nWidth;
        }

        dim.width += 3; // for shadow lines
        dim.height += 3; // for shadow lines
        return ( dim );
    }

    public boolean isFocusTraversable () {
        return ( true );
    }

    public void update ( Graphics g ) {
	Rectangle       rectClient;
        Image           image;
        Graphics        graphics;

	rectClient = this.getBounds ();
        image = createImage ( rectClient.width, rectClient.height );
        if ( image != null )
            graphics = image.getGraphics ();
        else
            graphics = g;

        paint ( graphics );

        if ( image != null )
            g.drawImage ( image, 0, 0, this );
    }

    public void paint ( Graphics graphics ) {
        int             i, j;
        int             nColCount;
        int             nRowCount;
	Rectangle       rect;
	Rectangle       rectClient;
        Font            font;
        FontMetrics     fontMetrics;
        ColumnData      columnData;
        RowData         rowData;
        int             nStartX;
        String          strValue;
        int             nX;
        int             nY;
        int             nWidth;
        int             nHeight;
        int             nLength;


	rectClient = this.getBounds ();
        rectClient.x = 0;
        rectClient.y = 0;
	rect = new Rectangle ( rectClient );

        super.paint ( graphics );

	graphics.setColor ( COLOR_SHADOW_BOTTOM );
        graphics.drawRect ( rect.x, rect.y, rect.width - 2, rect.height - 2 );
	graphics.setColor ( COLOR_SHADOW_TOP );
	graphics.drawLine ( rect.x + rect.width - 1, rect.y + 1, rect.x + rect.width - 1, rect.y + rect.height - 1 );
	graphics.drawLine ( rect.x + 1, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1 );

        nColCount = vectorColumns.size ();
        nRowCount = vectorRows.size ();

        rect.x += 1;
        rect.y += 1;
        nStartX = rect.x;

        // draw header
        fontMetrics = this.getFontMetrics ( fontHeader );
        graphics.setFont ( this.fontHeader );
        nHeight = fontMetrics.getHeight ();
        rect.height = nHeightHeader;
        for ( i = nScrollPosHorz;  i < nColCount;  i++ ) {
            columnData = (ColumnData) vectorColumns.elementAt ( i );
            rect.width = columnData.nWidth;
            if ( rect.x + rect.width > rectClient.x + rectClient.width - 1 )
                rect.width = rectClient.x + rectClient.width - 1 - rect.x;

            // draw header
            graphics.setColor ( COLOR_HEADER_BG );
            graphics.fillRect ( rect.x, rect.y, rect.width, rect.height );
            graphics.setColor ( COLOR_SHADOW_TOP );
            graphics.drawLine ( rect.x, rect.y, rect.x, rect.y + rect.height - 2 );
            graphics.drawLine ( rect.x, rect.y, rect.x + rect.width - 2, rect.y );
            graphics.setColor ( COLOR_SHADOW_BOTTOM );
            graphics.drawLine ( rect.x + rect.width - 1, rect.y + 1, rect.x + rect.width - 1, rect.y + rect.height - 1 );
            graphics.drawLine ( rect.x + 1, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1 );

            strValue = columnData.strName;
            nLength = strValue.length ();
            nWidth = fontMetrics.stringWidth ( strValue );
            while ( nWidth > rect.width - 2 * MARGIN_HORZ  &&  nLength > 0 ) {
                nLength--;
                strValue = strValue.substring ( 0, nLength ) + "...";
                nWidth = fontMetrics.stringWidth ( strValue );
            }
            nX = rect.x + (rect.width - nWidth) / 2;
            nY = rect.y + rect.height - (rect.height - nHeight) / 2 - fontMetrics.getMaxDescent();
            graphics.setColor ( this.getForeground() );
	    graphics.drawString ( strValue, nX, nY );

            rect.x += rect.width;
        }

        // draw rows
        font = this.getFont ();
        fontMetrics = this.getFontMetrics ( font );
        graphics.setFont ( font );
        nHeight = fontMetrics.getHeight ();

        rect.y += rect.height;
        rect.height = nHeightRow;
        for ( j = nScrollPosVert;  j < nRowCount;  j++ ) {
            rect.x = nStartX;
            if ( j == nCurrentIndex ) {
                rect.width = rectClient.width - 3;
                graphics.setColor ( COLOR_SEL_BG );
                graphics.fillRect ( rect.x, rect.y, rect.width, rect.height );
                graphics.setColor ( COLOR_SEL_FG );
                if ( boolFocus == true ) {
                    drawDottedLine ( graphics, rect.x, rect.y, rect.x + rect.width - 1, rect.y );
                    drawDottedLine ( graphics, rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1 );
                }
            }
            else
                graphics.setColor ( this.getForeground() );

            rowData = (RowData) vectorRows.elementAt ( j );
            for ( i = nScrollPosHorz;  i < nColCount;  i++ ) {
                columnData = (ColumnData) vectorColumns.elementAt ( i );
                rect.width = columnData.nWidth;
                if ( rect.x + rect.width > rectClient.x + rectClient.width - 1 )
                    rect.width = rectClient.x + rectClient.width - 1 - rect.x;
                strValue = rowData.getValue(i).toString();
                nLength = strValue.length ();
                nWidth = fontMetrics.stringWidth ( strValue );
                while ( nWidth > rect.width - 2 * MARGIN_HORZ  &&  nLength > 0 ) {
                    nLength--;
                    strValue = strValue.substring ( 0, nLength ) + "...";
                    nWidth = fontMetrics.stringWidth ( strValue );
                }
                nX = rect.x + MARGIN_HORZ;
                nY = rect.y + rect.height - (rect.height - nHeight) / 2 - fontMetrics.getMaxDescent();
                graphics.drawString ( strValue, nX, nY );
                rect.x += rect.width;
            }
            rect.y += rect.height;
        }

    }

    public void mouseClicked ( MouseEvent event ) {
    }

    public void mousePressed ( MouseEvent event ) {
        int             x, y;
        int             nIndex;

        x = event.getX ();
        y = event.getY ();
        y -= 1 + nHeightHeader;
        if ( y >= 0 ) {
            nIndex = y / nHeightRow;
            if ( nIndex >= 0  &&  nIndex < vectorRows.size() - nScrollPosVert )
                nCurrentIndex = nIndex + nScrollPosVert;
        }

        this.requestFocus ();
        repaint ();
    }

    public void mouseReleased ( MouseEvent event ) {
    }

    public void mouseEntered ( MouseEvent event ) {
    }

    public void mouseExited ( MouseEvent event ) {
    }

    public void keyTyped ( KeyEvent event ) {
    }

    public void keyPressed ( KeyEvent event ) {
        int       nKeyCode;
        int       nIndex;

        nKeyCode = event.getKeyCode ();
        nIndex = nCurrentIndex;
        if ( nKeyCode == KeyEvent.VK_DOWN )
            nIndex++;
        else if ( nKeyCode == KeyEvent.VK_UP )
            nIndex--;
        else if ( nKeyCode == KeyEvent.VK_HOME )
            nIndex = 0;
        else if ( nKeyCode == KeyEvent.VK_END )
            nIndex = vectorRows.size() - 1;
        else if ( nKeyCode == KeyEvent.VK_PAGE_UP )
            nIndex -= nVisibleRows;
        else if ( nKeyCode == KeyEvent.VK_PAGE_DOWN )
            nIndex += nVisibleRows;

        if ( nIndex > vectorRows.size() - 1 )
            nIndex = vectorRows.size() - 1;
        if ( nIndex < 0 )
            nIndex = 0;

        if ( nIndex != nCurrentIndex ) {
            nCurrentIndex = nIndex;
            if ( nScrollPosVert + nVisibleRows < nCurrentIndex )
                nScrollPosVert = nCurrentIndex - nVisibleRows + 1;
            if ( nScrollPosVert > nCurrentIndex )
                nScrollPosVert = nCurrentIndex;
            repaint ();
        }
    }

    public void keyReleased ( KeyEvent event ) {
    }

    public void focusGained ( FocusEvent event ) {
        if ( boolFocus == true )
            return;
        boolFocus = true;
        repaint ();
    }

    public void focusLost ( FocusEvent event ) {
        if ( boolFocus == false )
            return;
        boolFocus = false;
        repaint ();
    }

    public void componentResized ( ComponentEvent event ) {
        Rectangle    rect;

        if ( boolSetColumnWidthAsPreferred == true ) {
            boolSetColumnWidthAsPreferred = false;
            this.setColumnWidthAsPreferred ();
        }
        rect = getBounds ();
        rect.height -= 3 + nHeightHeader;
        nVisibleRows = rect.height / nHeightRow;
        if ( nVisibleRows < 1 )
            nVisibleRows = 1;
    }

    public void componentMoved ( ComponentEvent event ) {
    }

    public void componentShown ( ComponentEvent event ) {
    }

    public void componentHidden ( ComponentEvent event ) {
    }


    private void init () throws Exception {
        this.setFont ( fontItem );
        this.computeHeights ();
        this.setBackground ( Color.white );

        this.addMouseListener ( this );
        this.addKeyListener ( this );
        this.addFocusListener ( this );
        this.addComponentListener ( this );
    }

    private int getPreferredColumnWidth ( int nColumnIndex ) {
        int          i;
        int          nCount;
        int          nWidth;
        int          nWidthMax;
        Font         font;
        FontMetrics  fontMetrics;
        String       strValue;
        ColumnData   columnData;
        RowData      rowData;

        columnData = (ColumnData) vectorColumns.elementAt ( nColumnIndex );

        // header
        fontMetrics = this.getFontMetrics ( fontHeader );
        strValue = columnData.strName;
        nWidthMax = fontMetrics.stringWidth ( strValue ) + 2 * MARGIN_HORZ + 2;

        // rows
        font = this.getFont ();
        fontMetrics = this.getFontMetrics ( font );
        nCount = vectorRows.size ();
        for ( i = 0;  i < nCount;  i++ ) {
            rowData = (RowData) vectorRows.elementAt ( i );
            strValue = rowData.getValue(nColumnIndex).toString();
            nWidth = fontMetrics.stringWidth ( strValue ) + 2 * MARGIN_HORZ;
            nWidthMax = Math.max ( nWidthMax, nWidth );
        }
        
        return ( nWidthMax );
    }

    private void computeHeights () {
        Font            font;
        FontMetrics     fontMetrics;

        fontMetrics = this.getFontMetrics ( fontHeader );
        nHeightHeader = fontMetrics.getHeight ();
        nHeightHeader += 2;  // shadow lines
        nHeightHeader += 2 * MARGIN_VERT;

        font = this.getFont ();
        fontMetrics = this.getFontMetrics ( font );
        nHeightRow = fontMetrics.getHeight ();
        nHeightRow += 2 * MARGIN_VERT;
    }

    private void drawDottedLine ( Graphics graphics, int nX1, int nY1, int nX2, int nY2 ) {
	int	nX, nY;
	double	dDiv;

	if ( nX1 == nX2  &&  nY1 == nY2 ) {
	    drawDot ( graphics, nX1, nY1 );
            return;
	}
	if ( nX1 > nX2 ) {
	    nX = nX1;
	    nX1 = nX2;
	    nX2 = nX;
	}
	if ( nY1 > nY2 ) {
	    nY = nY1;
	    nY1 = nY2;
	    nY2 = nY;
	}
	if ( nX2 - nX1 > nY2 - nY1 ) {
	    dDiv = (double)(nY2 - nY1) / (nX2 - nX1);
	    for ( nX = nX1;   nX <= nX2;   nX++ ) {
	    	nY = (int)Math.rint ( nY1 + (nX - nX1) * dDiv );
	    	drawDot ( graphics, nX, nY );
	    }
	}
	else {
	    dDiv = (nX2 - nX1) / (nY2 - nY1);
	    for ( nY = nY1;   nY <= nY2;   nY++ ) {
	    	nX = (int)Math.rint ( nX1 + (nY - nY1) * dDiv );
	    	drawDot ( graphics, nX, nY );
	    }
	}
    }

    private void drawDot ( Graphics graphics, int nX, int nY )
    {
    	if ( (nX + nY) % 2 == 0 )
            graphics.drawLine ( nX, nY, nX, nY );
    }


}


class ColumnData {
    String   strName;
    int      nType;
    int      nWidth = 120;

    public ColumnData ( String strName, int nType ) {
        this.strName = strName;
        this.nType = nType;
    }

    public String toString () {
        return ( strName );
    }
}

class RowData {
    private Vector   vectorValues = new Vector ();

    public RowData ( Object arrValues[] ) {
        int       i;
        int       nCount;

        if ( arrValues != null ) {
            nCount = arrValues.length;
            for ( i = 0;  i < nCount;  i++ )
                vectorValues.addElement ( arrValues[i] );
        }
    }

    void setValue ( Object value, int nColumn ) {
        vectorValues.setElementAt ( value, nColumn );
    }

    Object getValue ( int nColumn ) {
        Object    value;

        value = vectorValues.elementAt ( nColumn );
        return ( value );
    }
}
