package org.jcon.ui.table;

import org.jcon.ui.VisualLib;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.*;

/**
 * A better JTable wrapper. The "B" stands for better. This
 * class is merely a starting point. Currently we offer 
 * these features: <p> <pre>
 * - The table is in a scroll pane
 * - Column widths are automatically set
 * - The table's preferred width is its columns total preferred width
 * - JTable.AUTO_RESIZE_OFF is in effect
 * - A single row at a time is selectable
 * - A single event contains all events of interest </pre>
 * <p>
 * Due to poor awt design, the client of this class MUST
 * call window.pack() or whatever before calling init. If
 * this is not done, header height is zero.
 *
 * @author Jack Harich
 */
public class BTable implements
    MouseListener, ListSelectionListener {

//---------- Private Fields ------------------------------
private BTableModel  model;
private JTable       table;
private JScrollPane  scrollPane;
private Vector       listeners = new Vector();

private int          preferredWidth;
private int          preferredHeight;
private int          numberRows;
private int          previousRowIndex = -1;

//---------- Initialization ------------------------------
public BTable() {
    clear();
}
/**
 * Clears all configured state, returning the instance to
 * its post constructor state. This allows an instance to
 * be reused, for example by displaying a completely
 * different set of columns and data.
 */
public void clear() {
    table = new JTable();
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    table.addMouseListener(this);
    table.getSelectionModel().addListSelectionListener(this);

    //scrollPane = JTable.createScrollPaneForTable(table);
    scrollPane = new JScrollPane(table);
    scrollPane.setMinimumSize(new Dimension(0, 0));
    scrollPane.setDoubleBuffered(true);

    model = null;
    // ***scrollPane = null;
    listeners.removeAllElements();

    preferredWidth = -1; // Use columns preferred width
    preferredHeight = -1;
    numberRows = 5;
    previousRowIndex = -1;
}
//---------- MouseListener Implementation ----------------
// *** Unreliable in receiving event - Swing bug
public void mouseClicked(MouseEvent evt) {
    if (evt.getSource() == table) {
        int rowIndex = getSelectedRow();
        if (rowIndex < 0) return;

        // x and y check for whitespace click - convoluted
        if (table.rowAtPoint(evt.getPoint()) < 0 ||
            table.columnAtPoint(evt.getPoint()) < 0) return;

        if (evt.getClickCount() == 1) {
            //print(".mouseClicked() single " + rowIndex);

        } else if (evt.getClickCount() == 2) {
            //print(".mouseClicked() double " + rowIndex);
            //print(".valueChanged() - Firing ROW_ACTIVATED, index = " + rowIndex);
            fireEvent(new BTableEvent(
                BTableEvent.ROW_ACTIVATED, rowIndex, this));
        }
    }
}
public void mousePressed(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }

//---------- ListSelectionListener Implementation --------
// Swing "feature" - The event is called for row that had
// the selection and the row that received the new selection.
// Thus it is called TWICE. We attempt a fix using previousRowIndex.

public void valueChanged(ListSelectionEvent evt) {
    int rowIndex = evt.getFirstIndex();
    //print(".valueChanged() - Firing ROW_SELECTED, index = " + rowIndex);
    if (rowIndex == previousRowIndex) return;
    
    fireEvent(new BTableEvent(
        BTableEvent.ROW_SELECTED, rowIndex, this));
    previousRowIndex = rowIndex;
}
//---------- Properties ----------------------------------
/**
 * Sets the number of rows that will be shown. This prevents
 * showing a portions of the bottom row, which happens if
 * the height is set with pixels. The default is 5.
 */
public void setNumberRows(int numberRows) {
    this.numberRows = numberRows;
    calcPreferredSize();
}
/**
 * Returns the component containing the table. Call this
 * AFTER configuring this class and calling init().
 */
public Component getComponent() {
    return scrollPane;
}
/**
 * Returns the currently selected row index or -1 if none.
 * This is for single selection mode.
 */
public int getSelectedRow() {
    if (getRowCount() == 0) return -1;

    int index = table.getSelectedRow();
    // Swing bug, index can be > items.size() - 1
    if (0 <= index && index <= getRowCount() - 1) {
        return index;
    } else {
        return -1; // Clicked on whitespace
    }
}
/**
 * Selects the row with the rowIndex and insures the row
 * is visible. We also fire ROW_SELECTED.
 */
public void setSelectedRow(int rowIndex) {
    // Not necessary: table.removeRowSelectionInterval(0, table.getRowCount() - 1);

    table.setRowSelectionInterval(rowIndex, rowIndex);
    // Show the selected row
    table.scrollRectToVisible(table.getCellRect(rowIndex,0,true));
    
    print(".setSelectedRow() - Firing ROW_SELECTED, index = " + rowIndex);
    fireEvent(new BTableEvent(
        BTableEvent.ROW_SELECTED, rowIndex, this));
    // Possibly set previousRowIndex ??? ***************
}
/**
 * Returns the number of rows or zero if no model.
 */
public int getRowCount() {
    if (model == null) {
        return 0;
    } else {
        return model.getRowCount();
    }
}
public int getColumnCount() {
    return table.getColumnCount();
}
//----- model
public void setModel(BTableModel model) {
    this.model = model;
    table.setModel(model);
}
public BTableModel getModel() {
    return model;
}
//----- preferredWidth
/**
 * Sets the preferred width. If this is not set then the
 * preferred width will be automatically calculated from
 * the total preferred width of all the columns.
 */
public void setPreferredWidth(int preferredWidth) {
    this.preferredWidth = preferredWidth;
}
public int getPreferredWidth() {
    return preferredWidth;
}
//----- preferredHeight
/**
 * Sets the preferred height, which will override
 * setNumberRows().
 */
public void setPreferredHeight(int preferredHeight) {
    this.preferredHeight = preferredHeight;
}
public int getPreferredHeight() {
    return preferredHeight;
}
//---------- Events --------------------------------------
public void addBTableListener(BTableListener listener) {
    listeners.addElement(listener);
}
public void removeBTableListener(BTableListener listener) {
    listeners.removeElement(listener);
}
//---------- Public Methods ------------------------------
/**
 * Adds one column with the specified index. It will be
 * sized to columnsWidth and have the header label.
 */
public void addColumn(int index, int columnWidth,
        String label) {
    int calcWidth = VisualLib.getColumnsWidth(columnWidth,
        (Component)table);
    TableColumn column = new TableColumn();
    column.setModelIndex(index);
    column.setHeaderValue(label);
    column.setWidth(calcWidth + 4);
    table.addColumn(column);
}
/**
 * Initializes the instance. Call this after clear(), setting
 * properties and then addColumn() as needed.
 */
public void init() {
    calcPreferredSize();
}
private void calcPreferredSize() {
    // Set preferred size
    int prefWidth = preferredWidth;
    if (preferredWidth < 0) {
        prefWidth = table.getPreferredSize().width;
    }
    // *** table.pack(); // To allow JTableHeader.getHeaderRect() to have correct height, not zero
    int prefHeight = calcPreferredHeight();
    scrollPane.setPreferredSize(new Dimension(
        prefWidth + 5 , prefHeight)); // Extra width is bug fix
    //print(".init() - prefWidth = " + prefWidth + ", prefHeight = " + prefHeight);
}
//----- Model events
public void fireTableDataChanged() {
    model.fireTableDataChanged();
}
public void fireTableRowsInserted(int firstRow, int lastRow) {
    model.fireTableRowsInserted(firstRow, lastRow);
}
public void fireTableRowsUpdated(int firstRow, int lastRow) {
    model.fireTableRowsUpdated(firstRow, lastRow);
}
public void fireTableRowsDeleted(int firstRow, int lastRow) {
    model.fireTableRowsDeleted(firstRow, lastRow);
}
//----- Other
public void fireEvent(BTableEvent evt) {
    Vector list;
    synchronized(this) {
        list = (Vector)listeners.clone();
    }
    for (int i = 0; i < list.size(); i++) {
        BTableListener listener = (BTableListener)list.elementAt(i);
        listener.processBTableEvent(evt);
    }
}
//---------- Private Methods -----------------------------
// preferredHeight overrides numberRows
public int calcPreferredHeight() {
    if (preferredHeight > 0) return preferredHeight;

    // Use header height + (numberRows * height per row)
    // Note columns have already been added
    Rectangle rec = table.getTableHeader().getHeaderRect(0);
    print(".calcPreferredHeight() - height = " + rec.height);

    Dimension headerSize = table.getTableHeader().getSize();
    //print(".calcPreferredHeight() - headerSize = " + headerSize);

    int height = rec.height +
        (numberRows * (table.getRowHeight() + 1) + 4);
    //print(".calcPreferredHeight() - height = " + height);

    return height;
}
//--- Std
private static void print(String text) {
    System.out.println("BTable" + text);
}
//========== Inner Classes ===============================
/** class Model extends AbstractTableModel {
    //----- Abstract implementation  -----
    // ColumnCount = 0 since we supply TableColumns above
    public int getColumnCount() { return 0; }
    public int getRowCount() {
        if (bTableModel == null) {
            return 0;
        } else {
            return bTableModel.getRowCount();
        }
    }
    public Object getValueAt(int row, int col) {
        return bTableModel.getValueAt(row, col);
    }
    //----- Superclass Overrides -----
    // Had to override since protected
    protected void fireTableDataChanged() {
        super.fireTableDataChanged();
    }
    protected void fireTableRowsInserted(int firstRow, int lastRow) {
        super.fireTableRowsInserted(firstRow, lastRow);
    }
    protected void fireTableRowsUpdated(int firstRow, int lastRow) {
        super.fireTableRowsUpdated(firstRow, lastRow);
    }
    protected void fireTableRowsDeleted(int firstRow, int lastRow) {
        super.fireTableRowsDeleted(firstRow, lastRow);
    }
} */ // End inner class

} // End class
