/* CPSTable.java - created: Jan 30, 2008
* Copyright (C) 2008 Clayton Carter
*
* This file is part of the project "Crop Planning Software". For more
* information:
* website: https://github.com/claytonrcarter/cropplanning
* email: cropplanning@gmail.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package CPS.UI.Swing;
import CPS.Data.CPSDateValidator;
import CPS.Data.CPSRecord;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Date;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.jdesktop.swingx.autocomplete.ComboBoxCellEditor;
public class CPSTable extends JTable {
private CPSDateValidator dateValidator;
private static final Color ROW_SHADE_GRAY_5 = new Color( 242, 242, 242 );
private static final Color ROW_SHADE_GRAY_10 = new Color( 229, 229, 229 );
private static final Color ROW_SHADE_GRAY = ROW_SHADE_GRAY_10;
public CPSTable() {
super();
init();
}
public CPSTable( TableModel tm ) {
super( tm );
init();
}
private void init() {
/* setup selection parameters */
// enable row selection, disable column selection (default)
this.setColumnSelectionAllowed( false );
this.setRowSelectionAllowed( true );
// allow multiple rows to be selected
this.getTableHeader().setReorderingAllowed(false);
dateValidator = new CPSDateValidator();
dateValidator.addFormat( CPSDateValidator.DATE_FORMAT_SQL );
setAutoResizeMode(AUTO_RESIZE_SUBSEQUENT_COLUMNS);
/* reset column widths for certain column types */
for ( int colIndex = 0; colIndex < getColumnCount(); colIndex++ ) {
Class c = getColumnClass(colIndex);
// Boolean
if ( c.getName().equals( Boolean.class.getName() ) ) {
getColumnModel().getColumn( colIndex ).setMaxWidth( 20 );
getColumnModel().getColumn( colIndex ).setPreferredWidth( 20 );
}
else if ( c.getName().equals( Date.class.getName() ))
getColumnModel().getColumn( colIndex ).setPreferredWidth( 50 );
else if ( c.getName().equals( Integer.class.getName() ))
getColumnModel().getColumn( colIndex ).setPreferredWidth( 40 );
else if ( c.getName().equals( Double.class.getName() ) )
getColumnModel().getColumn( colIndex ).setPreferredWidth( 50 );
else if ( c.getName().equals( Float.class.getName() ) )
getColumnModel().getColumn( colIndex ).setPreferredWidth( 50 );
}
// install custom table renderes and editors
for ( int i = 0 ; i < getColumnModel().getColumnCount() ; i++ ) {
// install date renderers and editors on all Date columns
if ( getColumnClass( i ).equals( new Date().getClass() ) ) {
getColumnModel().getColumn( i ).setCellRenderer( new DateCellRenderer() );
getColumnModel().getColumn( i ).setCellEditor( new DateCellEditor() );
}
// floating point columns
else if ( getColumnClass( i ).equals( Float.class ) ||
getColumnClass( i ).equals( Double.class ) ||
getColumnClass( i ).equals( Integer.class ) ) {
getColumnModel().getColumn( i ).setCellRenderer( new NumberCellRenderer() );
}
}
}
@Override
public void setRowSelectionInterval( int arg0, int arg1 ) {
super.setRowSelectionInterval( arg0, arg1 );
ensureRowIsVisible(arg0);
}
// Assumes table is contained in a JScrollPane. Scrolls the
// cell (rowIndex, vColIndex) so that it is visible within the viewport.
public void ensureRowIsVisible( int rowIndex ) {
if ( !( this.getParent() instanceof JViewport ) ) {
return;
}
JViewport viewport = (JViewport) this.getParent();
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = this.getCellRect( rowIndex, 1, true );
// The location of the viewport relative to the table
Point pt = viewport.getViewPosition();
// Translate the cell location so that it is relative
// to the view, assuming the northwest corner of the
// view is (0,0)
rect.setLocation( rect.x - pt.x, rect.y - pt.y );
// Scroll the area into view
viewport.scrollRectToVisible( rect );
}
public void setColumnNamesAndToolTips( List<String[]> prettyNames ) {
ColumnHeaderToolTips tips = new ColumnHeaderToolTips();
int COLNAME = 0;
int PRETTYNAME = 1;
int EDITABLE = 2;
// Assign a tooltip for each of the columns
for ( int c = 0; c < getColumnCount(); c++ ) {
String colName = getColumnModel().getColumn( c ).getHeaderValue().toString().toLowerCase();
String s = null;
Boolean editable = Boolean.TRUE; // default to true
for ( int l = 0; l < prettyNames.size(); l++ )
if ( colName.equals( prettyNames.get(l)[COLNAME]) ) {
s = prettyNames.get(l)[PRETTYNAME];
editable = new Boolean( prettyNames.get(l)[EDITABLE] );
break;
}
// Change the name to the "pretty name"
if ( s != null )
getColumnModel().getColumn( c ).setHeaderValue( s );
// set the tool tip to the "pretty name"
tips.setToolTip( getColumnModel().getColumn( c ), s );
// HACK!
// if the column is labeled as uneditable, then install an "uneditable" cell editor
if ( ! editable.booleanValue() ) {
getColumnModel().getColumn( c ).setCellEditor( new UneditableCellEditor() );
}
}
getTableHeader().addMouseMotionListener( tips );
}
@Override
public Component prepareRenderer( TableCellRenderer renderer, int rowIndex, int vColIndex ) {
Component c = super.prepareRenderer(renderer, rowIndex, vColIndex);
shadeComponentInRow( c, rowIndex );
return c;
}
private boolean isRowShaded( int rowIndex ) {
return ( (int) rowIndex / 2 ) % 2 == 0;
}
private Component shadeComponentInRow( Component c, int rowIndex ) {
if ( isRowSelected( rowIndex ))
c.setBackground( getSelectionBackground() );
else if ( isRowShaded( rowIndex ) )
c.setBackground( ROW_SHADE_GRAY );
else
// If not shaded, match the table's background
c.setBackground( getBackground() );
return c;
}
private class InsetRenderer extends JLabel implements TableCellRenderer {
public InsetRenderer() {
super();
setOpaque(true);
setBorder( BorderFactory.createEmptyBorder( 1, 5, 1, 5 ));
}
public Component getTableCellRendererComponent( JTable table, Object value,
boolean isSelected, boolean hasFocus,
int rowIndex, int vColIndex ) {
setText( (String) value );
return this;
}
}
private class NumberCellRenderer extends InsetRenderer implements TableCellRenderer {
public NumberCellRenderer() {
super();
setHorizontalAlignment( JLabel.RIGHT );
}
// This method is called each time a cell in a column
// using this renderer needs to be rendered.
public Component getTableCellRendererComponent( JTable table, Object value,
boolean isSelected, boolean hasFocus,
int rowIndex, int vColIndex ) {
// 'value' is value contained in the cell located at (rowIndex, vColIndex)
// Configure the component with the specified value
// in case, we display a formated string
String s;
if ( value instanceof Float ) {
if ( ((Float) value).floatValue() == 0f )
s = "";
else
s = CPSRecord.formatFloat( ((Float) value).floatValue(), 3 );
}
else if ( value instanceof Integer ) {
if ( ((Integer) value).intValue() == 0 )
s = "";
else
s = CPSRecord.formatInt( (Integer) value );
}
else if ( value instanceof Double ) {
if ( ((Double) value).floatValue() == 0f )
s = "";
else
s = CPSRecord.formatFloat( ((Double) value).floatValue(), 3 );
}
else if ( value == null )
s = "";
else
s = value.toString();
setText(s);
return this;
}
}
private class DateCellRenderer extends InsetRenderer implements TableCellRenderer {
public DateCellRenderer() {
super();
setOpaque(true);
}
// This method is called each time a cell in a column
// using this renderer needs to be rendered.
public Component getTableCellRendererComponent( JTable table, Object value,
boolean isSelected, boolean hasFocus,
int rowIndex, int vColIndex ) {
// 'value' is value contained in the cell located at (rowIndex, vColIndex)
// Configure the component with the specified value
// in case, we display a formated string
setText( CPSDateValidator.format( (Date) value, CPSDateValidator.DATE_FORMAT_SHORT ));
setToolTipText( CPSDateValidator.format( (Date) value, CPSDateValidator.DATE_FORMAT_FULLYEAR_DAY_OF_WEEK ) );
return this;
}
// The following methods override the defaults for performance reasons
public void validate() {}
public void revalidate() {}
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {}
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
}
public static class CPSComboBoxCellEditor extends ComboBoxCellEditor implements TableCellEditor {
public CPSComboBoxCellEditor(final JComboBox comboBox) {
super( comboBox );
}
public boolean isCellEditable(EventObject evt) {
if (evt instanceof MouseEvent) {
// For double-click activation
return ((MouseEvent)evt).getClickCount() >= 2;
}
return true;
}
}
private class DateCellEditor extends AbstractCellEditor implements TableCellEditor {
// This is the component that will handle the editing of the cell value
JComponent component = new JTextField();
// This method is called when a cell value is edited by the user.
// 'value' is value contained in the cell located at (rowIndex, vColIndex)
public Component getTableCellEditorComponent( JTable table, Object value,
boolean isSelected, int rowIndex, int vColIndex ) {
// Configure the component with the specified value
// In ths case, we accept a Date and fill the text field with a formated String.
( (JTextField) component ).setText( dateValidator.format( (Date) value ));
return component;
}
// This method is called when editing is completed.
// It must return the new value to be stored in the cell.
// In this case, we return a Date
public Object getCellEditorValue() {
if ( ((JTextField) component ).getText().equals( "" ))
return null;
String dateText = ( (JTextField) component ).getText();
return dateValidator.parse( dateText.trim() );
}
public boolean isCellEditable(EventObject evt) {
if (evt instanceof MouseEvent) {
// For double-click activation
return ((MouseEvent)evt).getClickCount() >= 2;
}
return true;
}
}
private class UneditableCellEditor extends AbstractCellEditor implements TableCellEditor {
// This is the component that will handle the editing of the cell value
JComponent component = new InsetRenderer();
// This method is called when a cell value is edited by the user.
// 'value' is value contained in the cell located at (rowIndex, vColIndex)
public Component getTableCellEditorComponent( JTable table, Object value,
boolean isSelected, int rowIndex, int vColIndex ) {
// Configure the component with the specified value
// In ths case, we accept a Date and fill the text field with a formated String.
( (JLabel) component ).setText( CPSDateValidator.format( (Date) value ));
return component;
}
// This method is called when editing is completed.
// It must return the new value to be stored in the cell.
// In this case, we return a Date
public Object getCellEditorValue() {
if ( ((JLabel) component ).getText().equals( "" ))
return null;
String dateText = ( (JLabel) component ).getText();
return dateValidator.parse( dateText.trim() );
}
@Override
public boolean isCellEditable( EventObject evt ) {
return false;
}
}
private class ColumnHeaderToolTips extends MouseMotionAdapter {
// Current column whose tooltip is being displayed.
// This variable is used to minimize the calls to setToolTipText().
TableColumn curCol;
// Maps TableColumn objects to tooltips
Map tips = new HashMap();
// If tooltip is null, removes any tooltip text.
public void setToolTip(TableColumn col, String tooltip) {
if (tooltip == null) {
tips.remove(col);
} else {
tips.put(col, tooltip);
}
}
public void mouseMoved(MouseEvent evt) {
TableColumn col = null;
JTableHeader header = (JTableHeader)evt.getSource();
JTable table = header.getTable();
TableColumnModel colModel = table.getColumnModel();
int vColIndex = colModel.getColumnIndexAtX(evt.getX());
// Return if not clicked on any column header
if (vColIndex >= 0) {
col = colModel.getColumn(vColIndex);
}
if (col != curCol) {
header.setToolTipText((String)tips.get(col));
curCol = col;
}
}
}
}