/*
* RapidMiner
*
* Copyright (C) 2001-2008 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.tools;
import java.awt.Color;
import java.awt.Point;
import java.awt.event.MouseEvent;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.report.Tableable;
import com.rapidminer.tools.Tools;
/**
* <p>This class extends a JTable in a way that editing is handled like it is expected, i.e.
* editing is properly stopped during focus losts, resizing, or column movement. The current
* value is then set to the model. The only way to abort the value change is by pressing
* the escape key.</p>
*
* <p>The extended table is sortable per default. Developers should note that this feature
* might lead to problems if the columns contain different class types end different editors.
* In this case one of the constructors should be used which set the sortable flag to false.
* </p>
*
* @author Ingo Mierswa
* @version $Id: ExtendedJTable.java,v 1.16 2008/08/25 08:10:36 ingomierswa Exp $
*/
public class ExtendedJTable extends JTable implements Tableable {
private static final long serialVersionUID = 4840252601155251257L;
private static final int DEFAULT_MAX_ROWS_FOR_SORTING = 100000;
public static final int NO_DATE_FORMAT = -1;
public static final int DATE_FORMAT = 0;
public static final int TIME_FORMAT = 1;
public static final int DATE_TIME_FORMAT = 2;
private boolean sortable = true;
private CellColorProvider cellColorProvider = new CellColorProviderAlternating();
private boolean useColoredCellRenderer = true;
private transient ColoredTableCellRenderer renderer = new ColoredTableCellRenderer();
private ExtendedTableSorterModel tableSorter = null;
public ExtendedJTable() {
this(null, true);
}
public ExtendedJTable(boolean sortable) {
this(null, sortable);
}
public ExtendedJTable(TableModel model, boolean sortable) {
this(model, sortable, true);
}
public ExtendedJTable(TableModel model, boolean sortable, boolean columnMovable) {
this(model, sortable, columnMovable, true);
}
public ExtendedJTable(boolean sortable, boolean columnMovable, boolean autoResize) {
this(null, sortable, columnMovable, autoResize);
}
public ExtendedJTable(TableModel model, boolean sortable, boolean columnMovable, boolean autoResize) {
this(model, sortable, columnMovable, autoResize, true);
}
public ExtendedJTable(TableModel model, boolean sortable, boolean columnMovable, boolean autoResize, boolean useColoredCellRenderer) {
super();
this.sortable = sortable;
this.useColoredCellRenderer = useColoredCellRenderer;
// allow all kinds of selection (e.g. for copy and paste)
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setColumnSelectionAllowed(true);
setRowSelectionAllowed(true);
setRowHeight(getRowHeight() + SwingTools.TABLE_ROW_EXTRA_HEIGHT);
getTableHeader().setReorderingAllowed(columnMovable);
// necessary in order to fix changes after focus was lost
putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
// auto resize?
if (!autoResize)
setAutoResizeMode(AUTO_RESIZE_OFF);
if (model != null) {
setModel(model);
}
}
protected Object readResolve() {
this.renderer = new ColoredTableCellRenderer();
return this;
}
protected ExtendedTableSorterModel getTableSorter() {
return this.tableSorter;
}
/** Subclasses might overwrite this method which by default simply returns NO_DATE.
* The returned format should be one out of NO_DATE_FORMAT, DATE_FORMAT, TIME_FORMAT,
* or DATE_TIME_FORMAT. This information will be used for the cell renderer. */
public int getDateFormat(int row, int column) {
return NO_DATE_FORMAT;
}
/** The given color provider will be used for the cell renderer.
* The default method implementation returns {@link SwingTools#LIGHTEST_BLUE} and white for
* alternating rows. If no colors should be used at all, set the cell color provider to
* null or to the default white color provider {@link CellColorProviderWhite}. */
public void setCellColorProvider(CellColorProvider cellColorProvider) {
this.cellColorProvider = cellColorProvider;
}
/** The returned color provider will be used for the cell renderer.
* The default method implementation returns {@link SwingTools#LIGHTEST_BLUE} and white for
* alternating rows. If no colors should be used at all, set the cell color provider to
* null or to the default white color provider {@link CellColorProviderWhite}. */
public CellColorProvider getCellColorProvider() {
return this.cellColorProvider;
}
public void setSortable(boolean sortable) {
this.sortable = sortable;
}
public boolean isSortable() {
return sortable;
}
public void setModel(TableModel model) {
boolean shouldSort = this.sortable && checkIfSortable(model);
if (shouldSort) {
this.tableSorter = new ExtendedTableSorterModel(model);
this.tableSorter.setTableHeader(getTableHeader());
super.setModel(this.tableSorter);
} else {
super.setModel(model);
this.tableSorter = null;
}
}
private boolean checkIfSortable(TableModel model) {
int maxSortableRows = DEFAULT_MAX_ROWS_FOR_SORTING;
String maxString = System.getProperty(RapidMinerGUI.PROPERTY_RAPIDMINER_GUI_MAX_SORTABLE_ROWS);
if (maxString != null) {
try {
maxSortableRows = Integer.parseInt(maxString);
} catch (NumberFormatException e) {
// do nothing
}
}
if (model.getRowCount() > maxSortableRows) {
return false;
} else {
return true;
}
}
/** Necessary to properly stopping the editing when a column is moved (dragged). */
public void columnMoved(TableColumnModelEvent e) {
if (isEditing()) {
cellEditor.stopCellEditing();
}
super.columnMoved(e);
}
/** Necessary to properly stopping the editing when a column is resized. */
public void columnMarginChanged(ChangeEvent e) {
if (isEditing()) {
cellEditor.stopCellEditing();
}
super.columnMarginChanged(e);
}
public boolean shouldUseColoredCellRenderer() {
return this.useColoredCellRenderer;
}
public TableCellRenderer getCellRenderer(int row, int col) {
if (useColoredCellRenderer) {
Color color = null;
CellColorProvider usedColorProvider = getCellColorProvider();
if (usedColorProvider != null) {
color = usedColorProvider.getCellColor(row, col);
}
if (color != null)
renderer.setColor(color);
renderer.setDateFormat(getDateFormat(row, col));
return renderer;
} else {
return super.getCellRenderer(row, col);
}
}
/** This method ensures that the correct tool tip for the current table cell is delivered. */
public String getToolTipText(MouseEvent e) {
Point p = e.getPoint();
int colIndex = columnAtPoint(p);
int realColumnIndex = convertColumnIndexToModel(colIndex);
int rowIndex = rowAtPoint(p);
Object value = getModel().getValueAt(rowIndex, realColumnIndex);
if (value != null)
return SwingTools.transformToolTipText(value.toString());
else
return super.getToolTipText();
}
public String getCell(int row, int column) {
String text = null;
if (getTableHeader() != null) {
if (row == 0) {
// titel row
return getTableHeader().getColumnModel().getColumn(column).getHeaderValue().toString();
} else {
row--;
}
}
// data area
Object value = getModel().getValueAt(row, column);
if (value instanceof Number) {
Number number = (Number)value;
double numberValue = number.doubleValue();
text = Tools.formatIntegerIfPossible(numberValue);
} else {
if (value != null)
text = value.toString();
else
text = "?";
}
return text;
}
public int getColumnNumber() {
return getColumnCount();
}
public int getRowNumber() {
if (getTableHeader() != null) {
return getRowCount() + 1;
} else {
return getRowCount();
}
}
public int getModelIndex(int rowIndex) {
return tableSorter.modelIndex(rowIndex);
}
}