/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.display;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.opensourcephysics.tools.DataToolTab;
/**
* DataTable displays multiple TableModels in a table. The first TableModel
* usually contains the independent variable for the other TableModel so that
* the visibility of column[0] can be set to false for subsequent TableModels.
*
* @author Joshua Gould
* @author Wolfgang Christian
* @created February 21, 2002
* @version 1.0
*/
public class DataTable extends JTable implements ActionListener {
static final Color PANEL_BACKGROUND = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$
final static Color LIGHT_BLUE = new Color(204, 204, 255);
static final String NO_PATTERN
= DisplayRes.getString("DataTable.FormatDialog.NoFormat"); //$NON-NLS-1$
public static String rowName = DisplayRes.getString("DataTable.Header.Row"); //$NON-NLS-1$
private final SortDecorator decorator;
protected HashMap<String, PrecisionRenderer> precisionRenderersByColumnName
= new HashMap<String, PrecisionRenderer>();
protected HashMap<String, UnitRenderer> unitRenderersByColumnName
= new HashMap<String, UnitRenderer>();
DataTableModel dataTableModel;
protected RowNumberRenderer rowNumberRenderer;
int maximumFractionDigits = 3;
int refreshDelay = 0; // time in ms to delay refresh events
javax.swing.Timer refreshTimer = new javax.swing.Timer(refreshDelay, this); // delay for refreshTable
protected int labelColumnWidth=40, minimumDataColumnWidth=24;
protected NumberFormatDialog formatDialog;
protected int clickCountToSort = 1;
protected int sortedColumn;
/**
* Constructs a DatTable with a default data model
*/
public DataTable() {
this(new DefaultDataTableModel());
}
/**
* Constructs a DatTable with the specified data model
*
* @param model data model
*/
public DataTable(DataTableModel model) {
super();
refreshTimer.setRepeats(false);
refreshTimer.setCoalesce(true);
setModel(model);
setColumnSelectionAllowed(true);
setGridColor(Color.blue);
setSelectionBackground(LIGHT_BLUE);
JTableHeader header = getTableHeader();
header.setForeground(Color.blue); // set text color
TableCellRenderer headerRenderer = new HeaderRenderer(getTableHeader().getDefaultRenderer());
getTableHeader().setDefaultRenderer(headerRenderer);
setSelectionForeground(Color.red); // foreground color for selected cells
setColumnModel(new DataTableColumnModel());
setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
setColumnSelectionAllowed(true);
// add column sorting using a SortDecorator
decorator = new SortDecorator(getModel());
setModel(decorator);
header.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if(!OSPRuntime.isPopupTrigger(e)
&& !e.isControlDown()
&& !e.isShiftDown()
&& e.getClickCount()==clickCountToSort) {
TableColumnModel tcm = getColumnModel();
int vc = tcm.getColumnIndexAtX(e.getX());
int mc = convertColumnIndexToModel(vc);
if (sortedColumn!=mc) {
decorator.sort(mc);
sortedColumn = mc;
}
}
}
});
}
/**
* Sets the maximum number of fraction digits to display in a named column
*
* @param maximumFractionDigits maximum number of fraction digits to display
* @param columnName name of the column
*/
public void setMaximumFractionDigits(String columnName, int maximumFractionDigits) {
precisionRenderersByColumnName.put(columnName, new PrecisionRenderer(maximumFractionDigits));
}
/**
* Sets the formatting pattern for a named column
*
* @param pattern the pattern
* @param columnName name of the column
*/
public void setFormatPattern(String columnName, String pattern) {
if((pattern==null)||pattern.equals("")) { //$NON-NLS-1$
precisionRenderersByColumnName.remove(columnName);
} else {
precisionRenderersByColumnName.put(columnName, new PrecisionRenderer(pattern));
}
firePropertyChange("format", null, columnName); //$NON-NLS-1$
}
/**
* Sets the units and tooltip for a named column.
*
* @param columnName name of the column
* @param units the units string (may be null)
* @param tootip the tooltip (may be null)
*/
public void setUnits(String columnName, String units, String tooltip) {
if(units==null) {
unitRenderersByColumnName.remove(columnName);
}
else {
TableCellRenderer renderer = getDefaultRenderer(Double.class);
for (String next: precisionRenderersByColumnName.keySet()) {
if(next.equals(columnName)) {
renderer = precisionRenderersByColumnName.get(columnName);
}
}
UnitRenderer unitRenderer = new UnitRenderer(renderer, units, tooltip);
unitRenderersByColumnName.put(columnName, unitRenderer);
}
}
/**
* Gets the formatting pattern for a named column
*
* @param columnName name of the column
* @return the pattern
*/
public String getFormatPattern(String columnName) {
PrecisionRenderer r = precisionRenderersByColumnName.get(columnName);
return (r==null) ? "" : r.pattern; //$NON-NLS-1$
}
/**
* Gets the names of formatted columns
* Added by D Brown 24 Apr 2011
*
* @return array of names of columns with non-null formats
*/
public String[] getFormattedColumnNames() {
return precisionRenderersByColumnName.keySet().toArray(new String[0]);
}
/**
* Gets the formatted value at a given row and column.
* Added by D Brown 6 Oct 2010
*
* @param row the row number
* @param col the column number
* @return the value formatted as displayed in the table
*/
public Object getFormattedValueAt(int row, int col) {
Object value = getValueAt(row, col);
if (value==null)
return null;
TableCellRenderer renderer = getCellRenderer(row, col);
Component c = renderer.getTableCellRendererComponent(DataTable.this, value, false, false, 0, 0);
if (c instanceof JLabel) {
String s = ((JLabel)c).getText().trim();
// strip units, if any
if (renderer instanceof UnitRenderer) {
String units = ((UnitRenderer)renderer).units;
if (!"".equals(units)) { //$NON-NLS-1$
int n = s.lastIndexOf(units);
if (n>-1)
s = s.substring(0, n);
}
}
return s;
}
return value;
}
/**
* Gets the format setter dialog.
*
* @param names the column name choices
* @param selected the initially selected names
* @return the format setter dialog
*/
public NumberFormatDialog getFormatDialog(String[] names, String[] selected) {
if(formatDialog==null) {
formatDialog = new NumberFormatDialog();
// center on screen
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width - formatDialog.getBounds().width) / 2;
int y = (dim.height - formatDialog.getBounds().height) / 2;
formatDialog.setLocation(x, y);
}
formatDialog.setColumns(names, selected);
return formatDialog;
}
/**
* Sorts the table using the given column.
* @param col int
*/
public void sort(int col) {
decorator.sort(col);
sortedColumn = col;
}
/**
* Gets the sorted column. Added by D Brown 2010-10-24.
* @return the
*/
public int getSortedColumn() {
return decorator.getSortedColumn();
}
/**
* Sets the maximum number of fraction digits to display for cells that have
* type Double
*
* @param maximumFractionDigits - maximum number of fraction digits to display
*/
public void setMaximumFractionDigits(int maximumFractionDigits) {
this.maximumFractionDigits = maximumFractionDigits;
setDefaultRenderer(Double.class, new PrecisionRenderer(maximumFractionDigits));
}
/**
* Gets the maximum number of digits in the table.
* @return int
*/
public int getMaximumFractionDigits() {
return maximumFractionDigits;
}
/**
* Sets the display row number flag. Table displays row number.
*
* @param _rowNumberVisible <code>true<\code> if table display row number
*/
public void setRowNumberVisible(boolean _rowNumberVisible) {
if(dataTableModel.isRowNumberVisible()!=_rowNumberVisible) {
if(_rowNumberVisible&&(rowNumberRenderer==null)) {
rowNumberRenderer = new RowNumberRenderer(this);
}
dataTableModel.setRowNumberVisible(_rowNumberVisible);
}
}
/**
* Sets the model for this data table;
*
* @param _model
*/
public void setModel(DataTableModel _model) {
super.setModel(_model);
dataTableModel = _model;
}
/**
* Sets the stride of a TableModel in the DataTable.
*
* @param tableModel
* @param stride
*/
public void setStride(TableModel tableModel, int stride) {
dataTableModel.setStride(tableModel, stride);
}
/**
* Sets the visibility of a column of a TableModel in the DataTable.
*
* @param tableModel
* @param columnIndex
* @param b
*/
public void setColumnVisible(TableModel tableModel, int columnIndex, boolean b) {
dataTableModel.setColumnVisible(tableModel, columnIndex, b);
}
/**
* Gets the display row number flag.
*
* @return The rowNumberVisible value
*/
public boolean isRowNumberVisible() {
return dataTableModel.isRowNumberVisible();
}
/**
* Returns an appropriate renderer for the cell specified by this row and
* column. If the <code>TableColumn</code> for this column has a non-null
* renderer, returns that. If the <code>TableColumn</code> for this column has
* the same name as a name specified in the setMaximumFractionDigits method,
* returns the appropriate renderer. If not, finds the class of the data in
* this column (using <code>getColumnClass</code>) and returns the default
* renderer for this type of data.
*
* @param row Description of Parameter
* @param column Description of Parameter
* @return The cellRenderer value
*/
public TableCellRenderer getCellRenderer(int row, int column) {
int i = convertColumnIndexToModel(column);
if((i==0)&&dataTableModel.isRowNumberVisible()) {
return rowNumberRenderer;
}
UnitRenderer unitRenderer = null;
TableCellRenderer baseRenderer = null;
try {
// find units renderer
TableColumn tableColumn = getColumnModel().getColumn(column);
Iterator<String> keys = unitRenderersByColumnName.keySet().iterator();
while(keys.hasNext()) {
String columnName = keys.next();
if(tableColumn.getHeaderValue().equals(columnName)) {
unitRenderer = unitRenderersByColumnName.get(columnName);
break;
}
}
// find base renderer
baseRenderer = tableColumn.getCellRenderer();
if (baseRenderer==null) {
keys = precisionRenderersByColumnName.keySet().iterator();
while(keys.hasNext()) {
String columnName = keys.next();
if(tableColumn.getHeaderValue().equals(columnName)) {
baseRenderer = precisionRenderersByColumnName.get(columnName);
break;
}
else if(tableColumn.getHeaderValue().equals(columnName+DataToolTab.SHIFTED)) {
baseRenderer = precisionRenderersByColumnName.get(columnName);
break;
}
}
}
} catch(Exception ex) {}
// if no precision base renderer, use default
if (baseRenderer==null)
baseRenderer = getDefaultRenderer(getColumnClass(column));
// return unit renderer if defined
if (unitRenderer!=null) {
unitRenderer.setBaseRenderer(baseRenderer);
return unitRenderer;
}
return baseRenderer;
}
/**
* Gets the precision renderer, if any, for a given columnn name.
* Added by D Brown Dec 2010
*
* @param columnName the name
* @return the PrecisionRenderer, or null if none
*/
public TableCellRenderer getPrecisionRenderer(String columnName) {
return precisionRenderersByColumnName.get(columnName);
}
/**
* Sets the delay time for table refresh timer.
*
* @param delay the delay in millisecond
*/
public void setRefreshDelay(int delay) {
if(delay>0) {
refreshTimer.setDelay(delay);
refreshTimer.setInitialDelay(delay);
} else if(delay<=0) {
refreshTimer.stop();
}
refreshDelay = delay;
}
/**
* Refresh the data in the DataTable, as well as other changes to the table,
* such as row number visibility. Changes to the TableModels displayed in the
* table will not be visible until this method is called.
*/
public void refreshTable() {
if(refreshDelay>0) {
refreshTimer.start();
} else {
Runnable doRefreshTable = new Runnable() {
public synchronized void run() {
actionPerformed(null);
}
};
if(SwingUtilities.isEventDispatchThread()) {
doRefreshTable.run();
} else {
SwingUtilities.invokeLater(doRefreshTable);
}
}
}
/**
* Performs the action for the refresh timer and refreshTable() method
* by refreshing the data in the DataTable.
*
* @param evt
*/
public void actionPerformed(ActionEvent evt) {
// code added by D Brown to maintain column order and widths (Mar 2014)
TableColumnModel model = this.getColumnModel();
int colCount = model.getColumnCount();
int[] modelIndexes = new int[colCount];
int[] columnWidths = new int[colCount];
ArrayList<Object> columnNames = new ArrayList<Object>();
// save current order, widths and column names
for (int i=0; i<colCount; i++) {
TableColumn column = model.getColumn(i);
modelIndexes[i] = column.getModelIndex();
columnWidths[i] = column.getWidth();
columnNames.add(column.getHeaderValue());
}
// refresh table--this lays out columns in default order and widths
tableChanged(new TableModelEvent(dataTableModel, TableModelEvent.HEADER_ROW));
// deal with added and/or removed columns
int newCount = model.getColumnCount();
// create list of new column names
ArrayList<Object> newColumnNames = new ArrayList<Object>();
for (int i=0; i<newCount; i++) {
TableColumn column = model.getColumn(i);
newColumnNames.add(column.getHeaderValue());
}
// determine which column(s) were removed
TreeSet<Integer> removedIndexes = new TreeSet<Integer>();
for (int i=0; i<colCount; i++) {
if (!newColumnNames.contains(columnNames.get(i))) {
removedIndexes.add(modelIndexes[i]);
}
}
// determine which column(s) were added
TreeSet<Integer> addedIndexes = new TreeSet<Integer>();
for (int i=0; i<newCount; i++) {
if (!columnNames.contains(newColumnNames.get(i))) {
addedIndexes.add(i);
}
}
// rebuild modelIndex and columnWidth arrays
while (!removedIndexes.isEmpty()) {
int n = removedIndexes.last();
removedIndexes.remove(n);
int[] newModelIndexes = new int[colCount-1];
int[] newColumnWidths = new int[colCount-1];
int k = 0;
for (int i=0; i<colCount; i++) {
if (modelIndexes[i]==n) continue;
if (modelIndexes[i]>n) {
newModelIndexes[k] = modelIndexes[i]-1;
}
else {
newModelIndexes[k] = modelIndexes[i];
}
newColumnWidths[k] = columnWidths[i];
k++;
}
modelIndexes = newModelIndexes;
columnWidths = newColumnWidths;
colCount = modelIndexes.length;
}
while (!addedIndexes.isEmpty()) {
int n = addedIndexes.first();
addedIndexes.remove(n);
int[] newModelIndexes = new int[colCount+1];
int[] newColumnWidths = new int[colCount+1];
for (int i=0; i<colCount; i++) {
if (modelIndexes[i]>=n) {
newModelIndexes[i] = modelIndexes[i]+1;
}
else {
newModelIndexes[i] = modelIndexes[i];
}
newColumnWidths[i] = columnWidths[i];
}
// add new columns at end and assign them the default width
newModelIndexes[colCount] = n;
newColumnWidths[colCount] = model.getColumn(n).getWidth();
modelIndexes = newModelIndexes;
columnWidths = newColumnWidths;
colCount = modelIndexes.length;
}
// restore column order
outer: for (int targetIndex=0; targetIndex<colCount; targetIndex++) {
// find column with modelIndex and move to targetIndex
for (int i=0; i<colCount; i++) {
if (model.getColumn(i).getModelIndex()==modelIndexes[targetIndex]) {
model.moveColumn(i, targetIndex);
continue outer;
}
}
}
// restore column widths
for (int i=0; i<columnWidths.length; i++) {
model.getColumn(i).setPreferredWidth(columnWidths[i]);
model.getColumn(i).setWidth(columnWidths[i]);
}
}
/**
* Add a TableModel object to the table model list.
*
* @param tableModel
*/
public void add(TableModel tableModel) {
dataTableModel.add(tableModel);
}
/**
* Remove a TableModel object from the table model list.
*
* @param tableModel
*/
public void remove(TableModel tableModel) {
dataTableModel.remove(tableModel);
}
/**
* Remove all TableModels from the table model list.
*/
public void clear() {
dataTableModel.clear();
}
private static class DataTableElement {
TableModel tableModel;
boolean columnVisibilities[]; // boolean values indicating if a column is visible
int stride = 1; // data stride in the DataTable view
/**
* Constructor DataTableElement
*
* @param t
*/
public DataTableElement(TableModel t) {
tableModel = t;
}
/**
* Method setStride
*
* @param _stride
*/
public void setStride(int _stride) {
stride = _stride;
}
/**
* Method setColumnVisible
*
* @param columnIndex
* @param visible
*/
public void setColumnVisible(int columnIndex, boolean visible) {
ensureCapacity(columnIndex+1);
columnVisibilities[columnIndex] = visible;
}
/**
* Method getStride
*
* @return
*/
public int getStride() {
return stride;
}
/**
* Method getColumnVisibilities
*
* @return
*/
public boolean[] getColumnVisibilities() {
return columnVisibilities;
}
/**
* Method getColumnCount
*
* @return
*/
public int getColumnCount() {
int count = 0;
int numberOfColumns = tableModel.getColumnCount();
ensureCapacity(numberOfColumns);
for(int i = 0; i<numberOfColumns; i++) {
boolean visible = columnVisibilities[i];
if(visible) {
count++;
}
}
return count;
}
/**
* Method getValueAt
*
* @param rowIndex
* @param columnIndex
* @return
*/
public Object getValueAt(int rowIndex, int columnIndex) {
return tableModel.getValueAt(rowIndex, columnIndex);
}
/**
* Method getColumnName
*
* @param columnIndex
* @return
*/
public String getColumnName(int columnIndex) {
return tableModel.getColumnName(columnIndex);
}
/**
* Method getColumnClass
*
* @param columnIndex
* @return
*/
public Class<?> getColumnClass(int columnIndex) {
return tableModel.getColumnClass(columnIndex);
}
/**
* Method getRowCount
*
* @return
*/
public int getRowCount() {
return tableModel.getRowCount();
}
private void ensureCapacity(int minimumCapacity) {
if(columnVisibilities==null) {
columnVisibilities = new boolean[(minimumCapacity*3)/2+1];
Arrays.fill(columnVisibilities, true);
} else if(columnVisibilities.length<minimumCapacity) {
boolean[] temp = columnVisibilities;
columnVisibilities = new boolean[(minimumCapacity*3)/2+1];
System.arraycopy(temp, 0, columnVisibilities, 0, temp.length);
Arrays.fill(columnVisibilities, temp.length, columnVisibilities.length, true);
}
}
}
/*
* DefaultDataTableModel acts on behalf of the TableModels that the DataTable contains. It combines
* data from these multiple sources and allows the DataTable to display data
* is if the data were from a single source.
*
* @author jgould
* @created February 21, 2002
*/
protected static class DefaultDataTableModel implements DataTableModel {
ArrayList<DataTableElement> dataTableElements = new ArrayList<DataTableElement>();
boolean rowNumberVisible = false;
/**
* Method setColumnVisible
*
* @param tableModel
* @param columnIndex
* @param b
*/
public void setColumnVisible(TableModel tableModel, int columnIndex, boolean b) {
DataTableElement dte = findElementContaining(tableModel);
dte.setColumnVisible(columnIndex, b);
}
/**
* Method setStride
*
* @param tableModel
* @param stride
*/
public void setStride(TableModel tableModel, int stride) {
DataTableElement dte = findElementContaining(tableModel);
dte.setStride(stride);
}
/**
* Method setRowNumberVisible
*
* @param _rowNumberVisible
*/
public void setRowNumberVisible(boolean _rowNumberVisible) {
rowNumberVisible = _rowNumberVisible;
}
/**
* Method setValueAt modified by Doug Brown 12/19/2013
*
* @param value
* @param rowIndex
* @param columnIndex
*/
public void setValueAt(Object value, int rowIndex, int columnIndex) {
if(dataTableElements.size()==0) {
return;
}
if(rowNumberVisible) {
if(columnIndex==0) {
return;
}
}
ModelFilterResult mfr = ModelFilterResult.find(rowNumberVisible, dataTableElements, columnIndex);
DataTableElement dte = mfr.tableElement;
int stride = dte.getStride();
rowIndex = rowIndex*stride;
if(rowIndex>=dte.getRowCount()) {
return;
}
dte.tableModel.setValueAt(value, rowIndex, mfr.column);
}
/**
* Method isRowNumberVisible
*
* @return
*/
public boolean isRowNumberVisible() {
return rowNumberVisible;
}
/**
* Method getColumnName
*
* @param columnIndex
* @return the name
*/
public String getColumnName(int columnIndex) {
if((dataTableElements.size()==0)&&!rowNumberVisible) {
return null;
}
if(rowNumberVisible) {
if(columnIndex==0) {
return rowName;
}
}
ModelFilterResult mfr = ModelFilterResult.find(rowNumberVisible, dataTableElements, columnIndex);
DataTableElement dte = mfr.tableElement;
String name = dte.getColumnName(mfr.column);
return name;
}
/**
* Method getRowCount
*
* @return
*/
public int getRowCount() {
int rowCount = 0;
for(int i = 0; i<dataTableElements.size(); i++) {
DataTableElement dte = dataTableElements.get(i);
int stride = dte.getStride();
rowCount = Math.max(rowCount, (dte.getRowCount()+stride-1)/stride);
}
return rowCount;
}
/**
* Method getColumnCount
*
* @return
*/
public int getColumnCount() {
int columnCount = 0;
for(int i = 0; i<dataTableElements.size(); i++) {
DataTableElement dte = dataTableElements.get(i);
columnCount += dte.getColumnCount();
}
if(rowNumberVisible) {
columnCount++;
}
return columnCount;
}
/**
* Method getValueAt
*
* @param rowIndex
* @param columnIndex
* @return
*/
public Object getValueAt(int rowIndex, int columnIndex) {
if(dataTableElements.size()==0) {
return null;
}
if(rowNumberVisible) {
if(columnIndex==0) {
return new Integer(rowIndex);
}
}
ModelFilterResult mfr = ModelFilterResult.find(rowNumberVisible, dataTableElements, columnIndex);
DataTableElement dte = mfr.tableElement;
int stride = dte.getStride();
rowIndex = rowIndex*stride;
if(rowIndex>=dte.getRowCount()) {
return null;
}
return dte.getValueAt(rowIndex, mfr.column);
}
/**
* Method getColumnClass
*
* @param columnIndex
* @return
*/
public Class<?> getColumnClass(int columnIndex) {
if(rowNumberVisible) {
if(columnIndex==0) {
return Integer.class;
}
}
if((columnIndex==0)&&rowNumberVisible) {
columnIndex--;
}
ModelFilterResult mfr = ModelFilterResult.find(rowNumberVisible, dataTableElements, columnIndex);
DataTableElement dte = mfr.tableElement;
return dte.getColumnClass(mfr.column);
}
/**
* Method isCellEditable
*
* @param rowIndex
* @param columnIndex
* @return true if editable
*/
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
/**
* Method remove
*
* @param tableModel
*/
public void remove(TableModel tableModel) {
DataTableElement dte = findElementContaining(tableModel);
dataTableElements.remove(dte);
}
/**
* Method clear
*/
public void clear() {
dataTableElements.clear();
}
/**
* Method add
*
* @param tableModel
*/
public void add(TableModel tableModel) {
dataTableElements.add(new DataTableElement(tableModel));
}
/**
* Method addTableModelListener
*
* @param l
*/
public void addTableModelListener(TableModelListener l) {}
/**
* Method removeTableModelListener
*
* @param l
*/
public void removeTableModelListener(TableModelListener l) {}
/**
* returns the DataTableElement that contains the specified TableModel
*
* @param tableModel
* @return Description of the Returned Value
*/
private DataTableElement findElementContaining(TableModel tableModel) {
for(int i = 0; i<dataTableElements.size(); i++) {
DataTableElement dte = dataTableElements.get(i);
if(dte.tableModel==tableModel) {
return dte;
}
}
return null;
}
}
private static class ModelFilterResult {
DataTableElement tableElement;
int column;
/**
* Constructor ModelFilterResult
*
* @param _tableElement
* @param _column
*/
public ModelFilterResult(DataTableElement _tableElement, int _column) {
tableElement = _tableElement;
column = _column;
}
/**
* Method find
*
* @param rowNumberVisible
* @param dataTableElements
* @param tableColumnIndex
* @return
*/
public static ModelFilterResult find(boolean rowNumberVisible, ArrayList<DataTableElement> dataTableElements, int tableColumnIndex) {
if(rowNumberVisible) {
tableColumnIndex--;
}
int totalColumns = 0;
for(int i = 0; i<dataTableElements.size(); i++) {
DataTableElement dte = dataTableElements.get(i);
dte.ensureCapacity(tableColumnIndex);
int columnCount = dte.getColumnCount();
totalColumns += columnCount;
if(totalColumns>tableColumnIndex) {
// int columnIndex = Math.abs(totalColumns - columnCount - tableColumnIndex);
int columnIndex = (columnCount+tableColumnIndex)-totalColumns;
boolean visible[] = dte.getColumnVisibilities();
for(int j = 0; j<tableColumnIndex; j++) {
if(!visible[j]) {
columnIndex++;
}
}
return new ModelFilterResult(dte, columnIndex);
}
}
return null; // this shouldn't happen
}
}
private class DataTableColumnModel extends DefaultTableColumnModel {
/**
* Method getColumn
*
* @param columnIndex
* @return
*/
public TableColumn getColumn(int columnIndex) {
TableColumn tableColumn;
try {
tableColumn = super.getColumn(columnIndex);
} catch(Exception ex) { // return an empty column if the columnIndex is not valid.
return new TableColumn();
}
String headerValue = (String) tableColumn.getHeaderValue();
if(headerValue==null) {
return tableColumn;
}
else if(headerValue.equals(rowName)&&(tableColumn.getModelIndex()==0)) {
tableColumn.setMaxWidth(labelColumnWidth);
tableColumn.setMinWidth(labelColumnWidth);
tableColumn.setResizable(false);
}
else {
tableColumn.setMinWidth(minimumDataColumnWidth);
}
return tableColumn;
}
}
protected static class PrecisionRenderer extends DefaultTableCellRenderer {
NumberFormat numberFormat;
String pattern;
/**
* PrecisionRenderer constructor
*
* @param precision - maximum number of fraction digits to display
*/
public PrecisionRenderer(int precision) {
super();
numberFormat = NumberFormat.getInstance();
numberFormat.setMaximumFractionDigits(precision);
setHorizontalAlignment(SwingConstants.RIGHT);
setBackground(Color.WHITE);
}
/**
* PrecisionRenderer constructor
*
* @param pattern a formatting pattern
*/
public PrecisionRenderer(String pattern) {
super();
numberFormat = NumberFormat.getInstance();
if(numberFormat instanceof DecimalFormat) {
((DecimalFormat) numberFormat).applyPattern(pattern);
this.pattern = pattern;
}
setHorizontalAlignment(SwingConstants.RIGHT);
}
/**
* Sets the string for the cell being rendered to value.
*
* @param value - the string value for this cell; if value is null it sets
* the text value to an empty string
*/
public void setValue(Object value) {
setText((value==null) ? "" : numberFormat.format(value)); //$NON-NLS-1$
}
/**
* Sets the maximum number of fraction digits to display
*
* @param precision - maximum number of fraction digits to display
*/
public void setPrecision(int precision) {
numberFormat.setMaximumFractionDigits(precision);
}
}
protected static class RowNumberRenderer extends JLabel implements TableCellRenderer {
JTable table;
/**
* RowNumberRenderer constructor
*
* @param _table Description of Parameter
*/
public RowNumberRenderer(JTable _table) {
super();
table = _table;
setHorizontalAlignment(SwingConstants.RIGHT);
setOpaque(true); // make background visible.
setForeground(Color.black);
setBackground(PANEL_BACKGROUND);
}
/**
* returns a JLabel that is highlighted if the row is selected.
*
* @param table
* @param value
* @param isSelected
* @param hasFocus
* @param row
* @param column
* @return
*/
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(table.isRowSelected(row)) {
int[] i = table.getSelectedColumns();
if((i.length==1)&&(table.convertColumnIndexToModel(i[0])==0)) {
setBackground(PANEL_BACKGROUND);
} else {
setBackground(Color.gray);
}
} else {
setBackground(PANEL_BACKGROUND);
}
setText(value.toString());
return this;
}
}
/**
* A cell renderer that adds units to displayed values.
* Added by D Brown Dec 2010
*/
protected static class UnitRenderer implements TableCellRenderer {
TableCellRenderer baseRenderer;
String units;
String tooltip;
/**
* UnitRenderer constructor
*
* @param renderer a TableCellRenderer
* @param factor a conversion factor
*/
public UnitRenderer(TableCellRenderer renderer, String units,
String tooltip) {
super();
this.units = units;
this.tooltip = tooltip;
setBaseRenderer(renderer);
}
/**
* Sets the base renderer.
*
* @param renderer the base renderer
*/
public void setBaseRenderer(TableCellRenderer renderer) {
this.baseRenderer = renderer;
}
/**
* Returns the rendered component.
*/
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = baseRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (c instanceof JLabel && units!=null) {
JLabel label = (JLabel)c;
if (label.getText()!=null && !label.getText().equals("")) //$NON-NLS-1$
label.setText(label.getText()+units);
label.setToolTipText(tooltip);
}
return c;
}
}
public class NumberFormatDialog extends JDialog {
JButton closeButton, cancelButton, helpButton, applyButton;
JLabel patternLabel, sampleLabel;
JTextField patternField, sampleField;
java.text.DecimalFormat sampleFormat;
String[] displayedNames;
Map<String, String> realNames = new HashMap<String, String>();
Map<String, String> prevPatterns = new HashMap<String, String>();
JList columnList;
JScrollPane columnScroller;
protected NumberFormatDialog() {
super(JOptionPane.getFrameForComponent(DataTable.this), true);
setLayout(new BorderLayout());
setTitle(DisplayRes.getString("DataTable.NumberFormat.Dialog.Title")); //$NON-NLS-1$
// create sample format
sampleFormat = (java.text.DecimalFormat) java.text.NumberFormat.getNumberInstance();
// create buttons
closeButton = new JButton(DisplayRes.getString("Dialog.Button.Close.Text")); //$NON-NLS-1$
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
applyButton = new JButton(DisplayRes.getString("Dialog.Button.Apply.Text")); //$NON-NLS-1$
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
patternField.getAction().actionPerformed(e);
}
});
cancelButton = new JButton(DisplayRes.getString("GUIUtils.Cancel")); //$NON-NLS-1$
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for(String displayedName : displayedNames) {
String name = realNames.get(displayedName);
setFormatPattern(name, prevPatterns.get(name));
}
refreshTable();
setVisible(false);
}
});
helpButton = new JButton(DisplayRes.getString("GUIUtils.Help")); //$NON-NLS-1$
helpButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String tab = " "; //$NON-NLS-1$
String nl = System.getProperty("line.separator", "/n"); //$NON-NLS-1$ //$NON-NLS-2$
JOptionPane.showMessageDialog(formatDialog, DisplayRes.getString("DataTable.NumberFormat.Help.Message1")+nl+nl+ //$NON-NLS-1$
tab+DisplayRes.getString("DataTable.NumberFormat.Help.Message2")+nl+ //$NON-NLS-1$
tab+DisplayRes.getString("DataTable.NumberFormat.Help.Message3")+nl+ //$NON-NLS-1$
tab+DisplayRes.getString("DataTable.NumberFormat.Help.Message4")+nl+ //$NON-NLS-1$
tab+DisplayRes.getString("DataTable.NumberFormat.Help.Message5")+nl+nl+ //$NON-NLS-1$
DisplayRes.getString("DataTable.NumberFormat.Help.Message6")+" PI.", //$NON-NLS-1$ //$NON-NLS-2$
DisplayRes.getString("DataTable.NumberFormat.Help.Title"), //$NON-NLS-1$
JOptionPane.INFORMATION_MESSAGE);
}
});
// create labels and text fields
patternLabel = new JLabel(DisplayRes.getString("DataTable.NumberFormat.Dialog.Label.Format")); //$NON-NLS-1$
sampleLabel = new JLabel(DisplayRes.getString("DataTable.NumberFormat.Dialog.Label.Sample")); //$NON-NLS-1$
patternField = new JTextField(6);
patternField.setAction(new AbstractAction() {
@SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
String pattern = patternField.getText();
if (pattern.indexOf(NO_PATTERN)>-1)
pattern = ""; //$NON-NLS-1$
// substitute 0 for other digits
for (int i = 1; i< 10; i++) {
pattern = pattern.replaceAll(String.valueOf(i), "0"); //$NON-NLS-1$
}
int i = pattern.indexOf("0e0"); //$NON-NLS-1$
if(i>-1) {
pattern = pattern.substring(0, i)+"0E0"+pattern.substring(i+3); //$NON-NLS-1$
}
try {
showNumberFormatAndSample(pattern);
// apply pattern to all selected columns
Object[] selectedColumns = columnList.getSelectedValues();
for(Object displayedName : selectedColumns) {
String name = realNames.get(displayedName.toString());
setFormatPattern(name, pattern);
}
refreshTable();
} catch(RuntimeException ex) {
patternField.setBackground(new Color(255, 153, 153));
patternField.setText(pattern);
return;
}
}
});
patternField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
patternField.setBackground(Color.white);
} else {
patternField.setBackground(Color.yellow);
// refresh sample format after text changes
Runnable runner = new Runnable() {
public void run() {
String pattern = patternField.getText();
if (pattern.indexOf(NO_PATTERN)>-1)
pattern = ""; //$NON-NLS-1$
// substitute 0 for other digits
for (int i = 1; i< 10; i++) {
pattern = pattern.replaceAll(String.valueOf(i), "0"); //$NON-NLS-1$
}
int i = pattern.indexOf("0e0"); //$NON-NLS-1$
if(i>-1) {
pattern = pattern.substring(0, i)+"0E0"+pattern.substring(i+3); //$NON-NLS-1$
}
if(pattern.equals("") || pattern.equals(NO_PATTERN)) { //$NON-NLS-1$
TableCellRenderer renderer = DataTable.this.getDefaultRenderer(Double.class);
Component c = renderer.getTableCellRendererComponent(DataTable.this, Math.PI, false, false, 0, 0);
if(c instanceof JLabel) {
String text = ((JLabel) c).getText();
sampleField.setText(text);
}
} else {
try {
sampleFormat.applyPattern(pattern);
sampleField.setText(sampleFormat.format(Math.PI));
} catch (Exception e) {
}
}
}
};
SwingUtilities.invokeLater(runner);
}
}
});
patternField.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
patternField.setBackground(Color.white);
patternField.getAction().actionPerformed(null);
}
});
sampleField = new JTextField(6);
sampleField.setEditable(false);
// column scroller (list is instantiated in setColumns() method)
columnScroller = new JScrollPane();
columnScroller.setPreferredSize(new Dimension(160, 120));
// assemble dialog
JPanel formatPanel = new JPanel(new GridLayout());
JPanel patternPanel = new JPanel();
patternPanel.add(patternLabel);
patternPanel.add(patternField);
formatPanel.add(patternPanel);
JPanel samplePanel = new JPanel();
samplePanel.add(sampleLabel);
samplePanel.add(sampleField);
formatPanel.add(samplePanel);
add(formatPanel, BorderLayout.NORTH);
JPanel columnPanel = new JPanel(new BorderLayout());
columnPanel.setBorder(BorderFactory.createTitledBorder(
DisplayRes.getString("DataTable.FormatDialog.ApplyTo.Title"))); //$NON-NLS-1$
columnPanel.add(columnScroller, BorderLayout.CENTER);
add(columnPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.add(helpButton);
buttonPanel.add(applyButton);
buttonPanel.add(closeButton);
buttonPanel.add(cancelButton);
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
private void showNumberFormatAndSample(int[] selectedIndices) {
if (selectedIndices==null || selectedIndices.length==0) {
showNumberFormatAndSample(""); //$NON-NLS-1$
}
else if (selectedIndices.length==1) {
String name = realNames.get(displayedNames[selectedIndices[0]]);
String pattern = getFormatPattern(name);
showNumberFormatAndSample(pattern);
}
else {
// do all selected indices have same pattern?
String name = realNames.get(displayedNames[selectedIndices[0]]);
String pattern = getFormatPattern(name);
for (int i=1; i<selectedIndices.length; i++) {
name = realNames.get(displayedNames[selectedIndices[i]]);
if (!pattern.equals(getFormatPattern(name))) {
pattern = null;
break;
}
}
showNumberFormatAndSample(pattern);
}
}
private void showNumberFormatAndSample(String pattern) {
if (pattern==null) {
sampleField.setText(""); //$NON-NLS-1$
patternField.setText(""); //$NON-NLS-1$
return;
}
if(pattern.equals("") || pattern.equals(NO_PATTERN)) { //$NON-NLS-1$
TableCellRenderer renderer = DataTable.this.getDefaultRenderer(Double.class);
Component c = renderer.getTableCellRendererComponent(DataTable.this, Math.PI, false, false, 0, 0);
if(c instanceof JLabel) {
String text = ((JLabel) c).getText();
sampleField.setText(text);
}
patternField.setText(NO_PATTERN);
} else {
sampleFormat.applyPattern(pattern);
sampleField.setText(sampleFormat.format(Math.PI));
patternField.setText(pattern);
}
}
void setColumns(String[] names, String[] selected) {
displayedNames = new String[names.length];
realNames.clear();
for (int i=0; i<names.length; i++) {
String s = TeXParser.removeSubscripting(names[i]);
// add white space for better look
displayedNames[i] = " "+s+" "; //$NON-NLS-1$ //$NON-NLS-2$
realNames.put(displayedNames[i], names[i]);
if (selected!=null) {
for (int j=0; j<selected.length; j++) {
if (selected[j]!=null && selected[j].equals(names[i])) {
selected[j] = displayedNames[i];
}
}
}
}
prevPatterns.clear();
for(String name : names) {
prevPatterns.put(name, getFormatPattern(name));
}
// create column list and add to scroller
columnList = new JList(displayedNames);
columnList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
columnList.setVisibleRowCount(-1);
columnList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
showNumberFormatAndSample(columnList.getSelectedIndices());
}
});
columnScroller.setViewportView(columnList);
pack();
int[] indices = null;
if (selected!=null) {
// select requested names
indices = new int[selected.length];
for (int j=0; j<indices.length; j++) {
inner:
for (int i = 0; i< displayedNames.length; i++) {
if (displayedNames[i].equals(selected[j])) {
indices[j] = i;
break inner;
}
}
}
columnList.setSelectedIndices(indices);
}
else
showNumberFormatAndSample(indices);
}
}
/**
* A header cell renderer that identifies sorted columns.
* Added by D Brown 2010-10-24
*/
class HeaderRenderer implements TableCellRenderer {
TableCellRenderer renderer;
DrawingPanel panel = new DrawingPanel();
DrawableTextLine textLine = new DrawableTextLine("", 0, -6); //$NON-NLS-1$
/**
* Constructor HeaderRenderer
* @param renderer
*/
public HeaderRenderer(TableCellRenderer renderer) {
this.renderer = renderer;
textLine.setJustification(TextLine.CENTER);
panel.addDrawable(textLine);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
// value is column name
String name = (value==null) ? "" : value.toString(); //$NON-NLS-1$
if (OSPRuntime.isMac()) {
name = TeXParser.removeSubscripting(name);
}
Component c = renderer.getTableCellRendererComponent(table, name, isSelected, hasFocus, row, col);
if (!(c instanceof JComponent)) {
return c;
}
JComponent comp = (JComponent) c;
int sortCol = decorator.getSortedColumn();
Font font = comp.getFont();
if (OSPRuntime.isMac()) {
// textline doesn't work on OSX
comp.setFont((sortCol!=convertColumnIndexToModel(col))?
font.deriveFont(Font.PLAIN) :
font.deriveFont(Font.BOLD));
if (comp instanceof JLabel) {
((JLabel)comp).setHorizontalAlignment(SwingConstants.CENTER);
}
return comp;
}
textLine.setText(name);
java.awt.Dimension dim = comp.getPreferredSize();
dim.height += 1;
panel.setPreferredSize(dim);
javax.swing.border.Border border = comp.getBorder();
if (border instanceof javax.swing.border.EmptyBorder) {
border = BorderFactory.createLineBorder(Color.LIGHT_GRAY);
}
panel.setBorder(border);
// set font: bold if sorted column
textLine.setFont((sortCol!=convertColumnIndexToModel(col)) ? font : font.deriveFont(Font.BOLD));
textLine.setColor(comp.getForeground());
textLine.setBackground(comp.getBackground());
panel.setBackground(comp.getBackground());
return panel;
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
*
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/