package net.xoetrope.swing;
import java.awt.Color;
import java.awt.Component;
import java.util.Vector;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.TableModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import net.xoetrope.swing.table.GroupableTableHeader;
import net.xoetrope.xui.XAttributedComponent;
import net.xoetrope.xui.XModelHolder;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.XProjectManager;
import net.xoetrope.xui.data.XModel;
import net.xoetrope.xui.style.XStyleComponent;
import net.xoetrope.xui.style.XStyle;
import net.xoetrope.xui.data.XRowSelector;
import net.xoetrope.xui.events.XHandlerInvoker;
import net.xoetrope.xui.events.XListenerHelper;
import net.xoetrope.xui.style.XStyleManager;
import net.xoetrope.xui.data.XBaseModel;
import net.xoetrope.xui.data.table.XTableModel;
import net.xoetrope.xui.helper.XTranslator;
/**
* <p>Provides a wrapper for the JTable and offer some compatibility with the
* AWT version of the XTable.</p>
* <p>Copyright (c) Xoetrope Ltd., 1998-2004<br>
* License: see license.txt
* $Revision: 2.12 $
*/
public class XTable extends JTable implements XAttributedComponent, XModelHolder,
XStyleComponent, XRowSelector,
XListenerHelper, ListSelectionListener
{
protected XProject currentProject;
protected XModel model;
protected boolean updateModelSelection = false;
protected XHandlerInvoker invoker;
protected int oldRowSelection = -1;
protected boolean usesDatabase = false;
protected String headerStyle;
/**
* Create a new table
*/
public XTable()
{
super();
currentProject = XProjectManager.getCurrentProject();
setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
getTableHeader().setReorderingAllowed( false );
getSelectionModel().addListSelectionListener( this );
}
protected JTableHeader createDefaultTableHeader()
{
return new GroupableTableHeader( columnModel );
}
public void update()
{
if ( dataModel != null ) {
int selRowIdx = getSelectedRow();
updateTable();
int newRowIdx = getSelectedRow();
if (( newRowIdx < 0 ) && ( selRowIdx >= 0 ))
setSelectedRow( selRowIdx );
}
}
/**
* Force the scroll pane to set the component size if there is no data - otherwise the
* table would have no height
*/
public boolean getScrollableTracksViewportHeight()
{
if ( model == null ) {
if ( getParent() instanceof JViewport )
return true;
}
return super.getScrollableTracksViewportHeight();
}
/**
* Force the table to update itself
*/
public void updateTable()
{
tableChanged( new TableModelEvent( dataModel ));
}
/**
* Add an event handler response method to a component such that the page's
* response method is invoked when the event occurs
* @param page the page containing the method
* @param handlerType the type of event handler
* @param methodName the method to invoke
* @throws NoSuchMethodException cannot add the handler
*/
public void addHandler( Object page, String handlerType, String methodName ) throws NoSuchMethodException
{
invoker = new XHandlerInvoker( page, this, methodName );
}
/**
* The row selection has changes, update the model if required.
* @param le the list selection event
*/
public void valueChanged( ListSelectionEvent le )
{
super.valueChanged( le );
if ( model instanceof XRowSelector ) {
int selRowIdx = getSelectedRow();
if ( selRowIdx >= 0 ) {
if ( updateModelSelection )
((XRowSelector)model).setSelectedRow( selRowIdx );
if (( oldRowSelection != selRowIdx ) && ( invoker != null ))
invoker.invoke();
oldRowSelection = selRowIdx;
}
}
}
/**
* Set the XModel which we will be generating the table from
* @param xmodel The XModel of data
*/
public void setModel( XModel xmodel )
{
model = xmodel;
if ( model != null ) {
usesDatabase = ( model instanceof XTableModel );
model.get();
setModel( new XSwingTableModel( currentProject, model ));
}
}
/**
* Get the underlying model.
* @return the model
*/
public XModel getXModel()
{
return model;
}
/**
* Set the general style of the XTable
* @param style XStyle
*/
public void setStyle( String style )
{
XStyleManager styleMgr = currentProject.getStyleManager();
XStyle xstyle = styleMgr.getStyle( style );
setForeground( xstyle.getStyleAsColor( XStyle.COLOR_FORE ));
setBackground( xstyle.getStyleAsColor( XStyle.COLOR_BACK ));
setFont( styleMgr.getFont( xstyle ));
}
/**
* Set the background color of the table's viewport
* @param viewportBkColor the viewport background color
*/
public void setViewportBackground( Color viewportBkColor )
{
Component p = getParent();
if ( p instanceof JViewport )
p.setBackground( viewportBkColor );
}
/**
* Set the style of the header data
* @param style XStyle
*/
public void setHeaderStyle( String style )
{
if ( style != null ) {
headerStyle = style;
XStyleManager styleMgr = currentProject.getStyleManager();
XStyle xstyle = styleMgr.getStyle( style );
JTableHeader th = getTableHeader();
th.setForeground( xstyle.getStyleAsColor( XStyle.COLOR_FORE ));
th.setBackground( xstyle.getStyleAsColor( XStyle.COLOR_BACK ));
th.setFont( styleMgr.getFont( xstyle ));
}
}
/**
* Retrieve the name of the header style
* @return the header style name;
*/
public String getHeaderStyle()
{
return headerStyle;
}
/**
* Set the style of the selected row
* @param style XStyle
*/
public void setSelectedStyle( String style )
{
XStyle xstyle = currentProject.getStyleManager().getStyle( style );
Color c = xstyle.getStyleAsColor( XStyle.COLOR_FORE );
if ( c != null )
setSelectionForeground( c );
c = xstyle.getStyleAsColor( XStyle.COLOR_BACK );
if ( c != null )
setSelectionBackground( c );
}
/**
* Set the style of the border
* @param styleName the style name
*/
public void setBorderStyle( String styleName )
{
XStyle style = currentProject.getStyleManager().getStyle( styleName );
Color borderColor = style.getStyleAsColor( XStyle.COLOR_FORE );
setBorder( new LineBorder( borderColor ));
}
/**
* Check the if the table is interactive
* @return true if the table supports user interaction
* @deprecated
*/
public boolean isInteractiveTable()
{
// return content.isInteractiveTable();
return false;
}
/**
* Set the user interaction state
* @param state true for an user interactive table.
* @deprecated
*/
public void setInteractiveTable( boolean state )
{
// content.setInteractiveTable( state );
}
/**
* Sets the indexof the selected row
* @param idx the new selected row
*/
public void setSelectedRow( int idx )
{
setRowSelectionInterval( idx, idx );
}
/**
* Deselects all selected columns and rows.
*/
public void clearSelection()
{
oldRowSelection = -1;
super.clearSelection();
}
/**
* Set the translateable state of the model
* @param state 'true' for a transalted table
*/
public void setTranslated( boolean state )
{
((XSwingTableModel)getModel()).setTranslated( state );
}
/**
* Get the translateable state of the table
* @return true if the table is translateable
*/
public boolean getTranslated()
{
return ((XSwingTableModel)getModel()).getTranslated();
}
/**
* Move to the first row
*/
public void first()
{
if ( getRowCount() > 0 )
setSelectedRow( 0 );
}
/**
* Move to the previous row
*/
public void prev()
{
int selRow = getSelectedRow() - 1;
if ( selRow >= 0 )
setSelectedRow( selRow );
}
/**
* Move to the next row
*/
public void next()
{
int selRow = getSelectedRow() + 1;
if ( selRow <= ( getRowCount() -1 ))
setSelectedRow( selRow );
}
/**
* Move to the last row
*/
public void last()
{
setSelectedRow( model.getNumChildren()-1 );
}
/**
* Tie the model selection to this table's selection
* @param doUpdate true to tie the selections together, false to ignore
*/
public void setUpdateModelSelection( boolean doUpdate )
{
updateModelSelection = doUpdate;
}
/**
* Set the column width. Sets the maximum width as a workaround for a Swing problem
* @param columnIdx the column index
* @param width the new width
*/
public void setColWidth( int columnIdx, int width )
{
if ( isShowing() && isVisible()) {
DefaultTableColumnModel colModel = (DefaultTableColumnModel)getColumnModel();
TableColumn column = colModel.getColumn( columnIdx );
column.setMaxWidth( width );
}
else {
try {
final int w = width;
final int col = columnIdx;
SwingUtilities.invokeLater( new Runnable() {
public void run()
{
/**
* This is intended to be called after the component is displayed,
* so rthis delay may not work. Some die effects can be caused by
* the L&F delegate resetting properties and therefore whatever
* calls this probably shouldn't call this method till after
* addNotify is called.
*/
DefaultTableColumnModel colModel = (DefaultTableColumnModel)getColumnModel();
TableColumn column = colModel.getColumn( col );
column.setMaxWidth( w );
}
});
}
catch ( Exception e ) {}
}
}
/**
* Set one or more attributes of the component. Currently this handles the
* attributes
* <OL>
* <LI>headingStyle, value=the table header style</LI>
* <LI>selectionStyle, value=the selected row style</LI>
* <LI>interactive, value=true|false - no longer used</LI>
* <LI>updateModel, value=true|false update the underlying model selection (the selected row)</LI>
* <LI>border, 0 to turn off the border</LI>
* <LI>selectionmode, value=multiple for multiple selection ranges, single=a single selection range, singleSelection for a single item/row selection</LI>
* <LI>grid, value=true|false turn the grid on or off</LI>
* <LI>vertlines, value=true|false turn the vertical grid on or off</LI>
* <LI>horzlines, value=true|false turn the horizontal grid on or off</LI>
* </OL>
* @param attribName the attribute name
* @param attribValue the attribute value
* @return 0 for success, non zero for failure or to require some further action
*/
public int setAttribute( String attribName, Object attribValue )
{
String attribNameLwr = attribName.toLowerCase();
String attribValueStr = (String)attribValue;
// String attribValueLwr = null;
// if ( attribValue != null )
// attribValueLwr = attribValueStr.toLowerCase();
if ( attribNameLwr.equals( "headingstyle" ) || attribNameLwr.equals( "headerstyle" ))
setHeaderStyle( attribValueStr );
else if ( attribNameLwr.equals( "selectionstyle" ))
setSelectedStyle( attribValueStr );
else if ( attribNameLwr.equals( "borderstyle" ))
setBorderStyle( attribValueStr );
else if ( attribNameLwr.equals( "border" )) {
if ( "0".equals( attribValueStr ))
setBorder( new EmptyBorder( 0, 0, 0, 0 ));
}
else if ( attribNameLwr.equals( "interactive" ))
;//setInteractiveTable( attribValueStr.equals( "true" ));
else if ( attribNameLwr.equals( "updatemodel" ))
setUpdateModelSelection( attribValueStr.equals( "true" ));
else if ( attribNameLwr.equals( "selectionmode" ))
setSelectionMode( attribValueStr );
else if ( attribNameLwr.equals( "grid" ))
setShowGrid( attribValue.toString().equals( "true" ));
else if ( attribNameLwr.equals( "vertlines" ))
setShowVerticalLines( attribValue.toString().equals( "true" ));
else if ( attribNameLwr.equals( "horzlines" ))
setShowHorizontalLines( attribValue.toString().equals( "true" ));
else
return -1;
return 0;
}
/**
* Set the table selection mode
* @param value 'multiple' for ListSelectionModel.MULTIPLE_INTERVAL_SELECTION,
* other wise ListSelectionModel.SINGLE_INTERVAL_SELECTION
*/
public void setSelectionMode( String value )
{
if ( value.equals( "multiple" ) )
setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
else if ( value.equals( "single" ) )
setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );
else
setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
}
/**
* Gets a field value object from the currently selected row
* @param fieldIdx the field offset
* @return the value
*/
public Object getValue( int fieldIdx )
{
int row = getSelectedRow();
return getValue( row, fieldIdx );
}
/**
* Gets a field value object from the specified row
* @param rowIdx the row offset
* @param fieldIdx the field offset
* @return the value
*/
public Object getValue( int rowIdx, int fieldIdx )
{
XModel rowModel = model.get( rowIdx + 0/*content.getFirstRow()*/);
Object fieldModel = rowModel.get( fieldIdx );
if ( fieldModel instanceof XModel )
return ( ( XModel )fieldModel ).get();
return fieldModel;
}
/**
* Gets a field value as a string from the currently selected row
* @param fieldIdx the field offset
* @return the value
*/
public String getFieldValue( int fieldIdx )
{
return ( String )getValue( fieldIdx );
}
/**
* Gets a field value as a string from the specified row
* @param rowIdx the row offset
* @param fieldIdx the field offset
* @return the value
*/
public String getFieldValue( int rowIdx, int fieldIdx )
{
return ( String )getValue( rowIdx, fieldIdx );
}
/**
* Retrieves the TableCellEditor for a cell if one has been specified with the
* "editor" attribute
* @param row the row/record index (zero based)
* @param column the column/field index (zero based)
*/
public TableCellEditor getCellEditor( int row, int column )
{
Object obj = getValueAt( row, column );
if ( obj instanceof XModel ) {
XModel model = (XModel)obj;
String editor = model.getAttribValueAsString( model.getAttribute( "editor" ));
if ( editor == null )
return super.getCellEditor( row, column );
}
return null;
}
}
/**
* Create a new table model for use by the Table2 class
*/
class XSwingTableModel implements TableModel
{
/**
* The data model
*/
private XModel model;
/**
* the table listeners
*/
private Vector listeners;
/**
* true to display a header
*/
private boolean hasHeaderRow;
private boolean usesDatabase;
private boolean translateable;
private XTranslator translator;
/**
* Create a new table model
* @param currentProject the owner project
* @param newModel the data model source
*/
public XSwingTableModel( XProject currentProject, XModel newModel )
{
model = newModel;
usesDatabase = ( model instanceof XTableModel );
listeners = new Vector();
hasHeaderRow = false;
translateable = false;
translator = currentProject.getTranslator();
}
/**
* Set the translateable state of the model
* @param state 'true' for a transalted table
*/
public void setTranslated( boolean state )
{
translateable = state;
}
/**
* Get the translateable state of the table
* @return true if the table is translateable
*/
public boolean getTranslated()
{
return translateable;
}
/**
* Returns the number of rows in the model. A
* <code>JTable</code> uses this method to determine how many rows it
* should display. This method should be quick, as it
* is called frequently during rendering.
*
* @return the number of rows in the model
* @see #getColumnCount
*/
public int getRowCount()
{
if ( hasHeaderRow )
return model.getNumChildren() -1;
return model.getNumChildren();
}
/**
* Returns the number of columns in the model. A
* <code>JTable</code> uses this method to determine how many columns it
* should create and display by default.
*
* @return the number of columns in the model
* @see #getRowCount
*/
public int getColumnCount()
{
if ( model.getNumChildren() > 0 ) {
if ( usesDatabase )
return model.getNumAttributes();
else if ( model instanceof XBaseModel ) {
XBaseModel row0 = (XBaseModel)((XBaseModel)model).get( 0 );
if ( row0.getTagName().equals( "th" )) {
hasHeaderRow = true;
return row0.getNumChildren();
}
}
}
return model.getNumAttributes();
}
/**
* Returns the name of the column at <code>columnIndex</code>. This is used
* to initialize the table's column header name. Note: this name does
* not need to be unique; two columns in a table can have the same name.
*
* @param columnIndex the index of the column
* @return the name of the column
*/
public String getColumnName(int columnIndex)
{
if ( hasHeaderRow ) {
if ( model instanceof XBaseModel ) {//usesDatabase ) {
XBaseModel row0 = ( XBaseModel ) ( ( XBaseModel ) model ).get( 0 );
return row0.get( columnIndex ).getAttribValueAsString( XBaseModel.VALUE_ATTRIBUTE );
}
}
return model.getAttribName( columnIndex );
}
/**
* Returns the most specific superclass for all the cell values
* in the column. This is used by the <code>JTable</code> to set up a
* default renderer and editor for the column.
*
* @param columnIndex the index of the column
* @return the common ancestor class of the object values in the model.
*/
public Class getColumnClass(int columnIndex)
{
return String.class;
}
/**
* Returns true if the cell at <code>rowIndex</code> and
* <code>columnIndex</code>
* is editable. Otherwise, <code>setValueAt</code> on the cell will not
* change the value of that cell.
*
* @param rowIndex the row whose value to be queried
* @param columnIndex the column whose value to be queried
* @return true if the cell is editable
* @see #setValueAt
*/
public boolean isCellEditable( int rowIndex, int columnIndex )
{
return false;
}
/**
* Returns the value for the cell at <code>columnIndex</code> and
* <code>rowIndex</code>.
*
* @param rowIndex the row whose value is to be queried
* @param columnIndex the column whose value is to be queried
* @return the value Object at the specified cell
*/
public Object getValueAt( int rowIndex, int columnIndex )
{
Object obj = model.get( rowIndex + ( hasHeaderRow ? 1 : 0 )).get( columnIndex ).get();
if (( obj instanceof String ) && translateable )
return translator.translate( (String)obj );
return obj;
}
/**
* Sets the value in the cell at <code>columnIndex</code> and
* <code>rowIndex</code> to <code>aValue</code>.
*
* @param aValue the new value
* @param rowIndex the row whose value is to be changed
* @param columnIndex the column whose value is to be changed
* @see #getValueAt
* @see #isCellEditable
*/
public void setValueAt( Object aValue, int rowIndex, int columnIndex )
{
}
/**
* Adds a listener to the list that is notified each time a change
* to the data model occurs.
*
* @param l the TableModelListener
*/
public void addTableModelListener( TableModelListener l )
{
listeners.add( l );
}
/**
* Removes a listener from the list that is notified each time a
* change to the data model occurs.
*
* @param l the TableModelListener
*/
public void removeTableModelListener( TableModelListener l )
{
listeners.remove( l );
}
}