/* * 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.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.text.DecimalFormat; import java.util.EventObject; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; /** * A JTable to display int, double and String array values. * Version 1.1 uses a Timer to coalesce Refresh Table events * * @author Douglas Brown * @author Wolfgang Christian * @version 1.1 */ public class ArrayTable extends JTable implements ActionListener { int refreshDelay = 300; // time in ms to delay refresh events javax.swing.Timer refreshTimer = new javax.swing.Timer(refreshDelay, this); // delay for refreshTable ArrayTableModel tableModel; ArrayIndexRenderer indexRenderer = new ArrayIndexRenderer(); ArrayCellRenderer cellRenderer = new ArrayCellRenderer(); java.util.Dictionary<Integer, DecimalFormat> formatDictionary = new java.util.Hashtable<Integer, DecimalFormat>(); String formatPattern = "0.000"; //$NON-NLS-1$ DecimalFormat defaultFormat = new DecimalFormat(formatPattern); Object prevValue; /** * Constructor for 1D int array. * * @param array the array */ public ArrayTable(int[] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 2D int array. * * @param array the array */ public ArrayTable(int[][] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 1D double array. * * @param array the array */ public ArrayTable(double[] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 2D double array. * * @param array the array */ public ArrayTable(double[][] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 1D String array. * * @param array the array */ public ArrayTable(String[] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 2D String array. * * @param array the array */ public ArrayTable(String[][] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 1D boolean array. * * @param array the array */ public ArrayTable(boolean[] array) { tableModel = new ArrayTableModel(array); init(); } /** * Constructor for 2D boolean array. * * @param array the array */ public ArrayTable(boolean[][] array) { tableModel = new ArrayTableModel(array); init(); } /** * Initializes the table. */ protected void init() { refreshTimer.setRepeats(false); refreshTimer.setCoalesce(true); setModel(tableModel); tableModel.addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { int row = e.getFirstRow(); int col = tableModel.showRowNumber ? e.getColumn()+1 : e.getColumn(); Object value=getValueAt(row, col); if((value!=null)&&!value.equals(prevValue)) { // forward the table model event to property change listeners ArrayTable.this.firePropertyChange("cell", null, e); //$NON-NLS-1$ } } }); setDefaultRenderer(Object.class, cellRenderer); setColumnSelectionAllowed(true); getTableHeader().setReorderingAllowed(false); getTableHeader().setDefaultRenderer(indexRenderer); setAutoResizeMode(JTable.AUTO_RESIZE_OFF); setGridColor(Color.BLACK); int width = 24; String name; TableColumn column; if(getColumnCount()>0) { // set width of column 0 (row index) name = getColumnName(0); column = getColumn(name); column.setMinWidth(width); column.setMaxWidth(2*width); column.setWidth(width); } ((java.util.Hashtable<Integer, DecimalFormat>) formatDictionary).clear(); // empty dictionary during initialization // set width of other columns width = 60; for(int i = 1, n = getColumnCount(); i<n; i++) { name = getColumnName(i); column = getColumn(name); column.setMinWidth(width); column.setMaxWidth(3*width); column.setWidth(width); } // Override the default tab behavior InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); final Action prevTabAction = getActionMap().get(im.get(tab)); Action tabAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { // tab to the next editable cell prevTabAction.actionPerformed(e); JTable table = (JTable) e.getSource(); int rowCount = table.getRowCount(); int row = table.getSelectedRow(); int column = table.getSelectedColumn(); while(!table.isCellEditable(row, column)) { if(column==0) { column = 1; } else { row += 1; } if(row==rowCount) { row = 0; } if((row==table.getSelectedRow())&&(column==table.getSelectedColumn())) { break; } } table.changeSelection(row, column, false, false); } }; getActionMap().put(im.get(tab), tabAction); // enter key starts editing Action enterAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { // start editing JTable table = (JTable) e.getSource(); int row = table.getSelectedRow(); int column = table.getSelectedColumn(); table.editCellAt(row, column, e); Component comp = table.getEditorComponent(); if(comp instanceof JTextField) { JTextField field = (JTextField) comp; field.requestFocus(); field.selectAll(); } } }; KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); getActionMap().put(im.get(enter), enterAction); } /** * Starts editing the cell at <code>row</code> and <code>column</code>. * Overrides JTable method. * * @param row the row to be edited * @param column the column to be edited * @param e ignored * @return false if the cell cannot be edited or the indices are invalid */ public boolean editCellAt(int row, int column, EventObject e) { boolean editing = super.editCellAt(row, column, e); // save current value for comparison with value after editing if(editing) { prevValue = getValueAt(row, column); } return editing; } /* public void refreshTableOLD() { Runnable refresh = new Runnable() { public synchronized void run() { tableChanged(new TableModelEvent(tableModel, TableModelEvent.HEADER_ROW)); } }; SwingUtilities.invokeLater(refresh); } */ /** * Performs the action for the refresh timer by refreshing the data in the DataTable. * * @param evt */ public void actionPerformed(ActionEvent evt) { tableChanged(new TableModelEvent(tableModel, TableModelEvent.HEADER_ROW)); } /** * Sets the <code>Timer</code>'s initial time delay (in milliseconds) * to wait after the timer is started * before firing the first event. * @param delay */ public void setRefreshDelay(int delay) { refreshTimer.setInitialDelay(delay); } /** * Refresh the data in the table. */ public void refreshTable() { refreshTimer.start(); } /** * Sets the default numeric display format for all columns * @param defaultFormat */ public void setNumericFormat(String str) { if((str!=null)&&!str.equals(formatPattern)) { // format has changed formatPattern = str; defaultFormat = new DecimalFormat(str); refreshTable(); } } /** * Sets the numeric display format for each column * @param defaultFormat */ public void setNumericFormat(String[] str) { ((java.util.Hashtable<Integer, DecimalFormat>) formatDictionary).clear(); for(int i = 0, n = str.length; i<n; i++) { formatDictionary.put(i, new DecimalFormat(str[i])); } refreshTable(); } /** * Sets the first row's index. * * @param index */ public void setFirstRowIndex(int index) { if(index==tableModel.firstRow) { return; // nothing changed } tableModel.setFirstRowIndex(index); refreshTable(); } /** * Sets the first column's index. * * @param index */ public void setFirstColIndex(int index) { if(index==tableModel.firstCol) { return; // nothing changed } tableModel.setFirstColIndex(index); refreshTable(); } /** * Sets the display row number flag. Table displays row number. * * @param vis <code>true<\code> if table display row number */ public void setRowNumberVisible(boolean vis) { if(vis==tableModel.showRowNumber) { return; // nothing changed } tableModel.setRowNumberVisible(vis); refreshTable(); } /** * Sets the editable property. * * @param editable true allows editing of the cell values that are not locked. */ public void setEditable(boolean editable) { if(editable==tableModel.editable) { return; // nothing changed } tableModel.setEditable(editable); refreshTable(); } /** * Returns true of the table's row and column values are interchanged. * @return */ public boolean isTransposed() { return tableModel.isTransposed(); } /** * Sets the transposed property for the array. * A transposed array switches its row and column values in the display. * * @param transposed */ public void setTransposed(boolean transposed) { if(transposed==tableModel.transposed) { return; // nothing changed } tableModel.transposed = transposed; refreshTable(); } /** * Sets columns names. * @param names */ public void setColumnNames(String[] names) { if(tableModel.setColumnNames(names)) { refreshTable(); // refresh if there has been a change } } /** * Sets the column's locked flag. * * @param column int * @param locked boolean */ public void setColumnLock(int columnIndex, boolean locked) { if(tableModel.setColumnLock(columnIndex, locked)) { refreshTable(); // refresh if there has been a change } } /** * Sets the lock flag for multiple columns. Previously set locks are cleared. * * @param locked boolean array */ public void setColumnLocks(boolean[] locked) { if(tableModel.setColumnLocks(locked)) { refreshTable(); // refresh if there has been a change } } /** * Gets the default font of this component. * @return this component's font */ public Font getFont() { if(indexRenderer==null) { indexRenderer = new ArrayIndexRenderer(); } return indexRenderer.getFont(); } /** * Sets the font for this component. * * @param font the desired <code>Font</code> for this component * @see java.awt.Component#getFont */ public void setFont(Font font){ // Added by Paco super.setFont(font); if(indexRenderer==null) { indexRenderer = new ArrayIndexRenderer(); } if(cellRenderer==null) { cellRenderer = new ArrayCellRenderer(); } indexRenderer.setFont(font); cellRenderer.setFont(font); } /** * Sets the foreground color of this component. It is up to the * look and feel to honor this property, some may choose to ignore * it. * * @param color the desired foreground <code>Color</code> * @see java.awt.Component#getForeground */ public void setForeground(Color color){ super.setForeground(color); if(indexRenderer==null) { indexRenderer = new ArrayIndexRenderer(); } indexRenderer.setForeground(color); } /** * Sets the foreground color of the cell rendering component. It is up to the * look and feel to honor this property, some may choose to ignore * it. * * @param color the desired foreground <code>Color</code> */ public void setDataForeground(Color color){ if(cellRenderer==null) { cellRenderer = new ArrayCellRenderer(); } cellRenderer.setForeground(color); } /** * Sets the background color of this component. It is up to the * look and feel to honor this property, some may choose to ignore * it. * * @param color the desired background <code>Color</code> * @see java.awt.Component#getBackground */ public void setBackground(Color color){ super.setBackground(color); if(indexRenderer==null) { indexRenderer = new ArrayIndexRenderer(); } indexRenderer.setBackground(color); } /** * Sets the background color of the data cell rendering component. It is up to the * look and feel to honor this property, some may choose to ignore * it. * * @param color the desired background <code>Color</code> */ public void setDataBackground(Color color){ if(cellRenderer==null) { cellRenderer = new ArrayCellRenderer(); } cellRenderer.setBackground(color); } /** * Returns the renderer for a cell specified by row and column. * * @param row the row number * @param column the column number * @return the cell renderer */ public TableCellRenderer getCellRenderer(int row, int column) { int i = convertColumnIndexToModel(column); if((i==0)&&tableModel.showRowNumber) { return indexRenderer; } return getDefaultRenderer(getColumnClass(column)); } /** * A cell renderer for array cells. */ static class ArrayCellRenderer extends DefaultTableCellRenderer { /** * Constructor */ public ArrayCellRenderer() { super(); setForeground(Color.BLACK); setHorizontalAlignment(SwingConstants.RIGHT); setBackground(Color.WHITE); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); boolean editable = table.isCellEditable(row, column); setEnabled(editable); ArrayTable arrayTable = (ArrayTable) table; DecimalFormat cellFormat = arrayTable.formatDictionary.get(column); // does column have a special format? if(cellFormat==null) { cellFormat = arrayTable.defaultFormat; // use default format } if(value==null) { setText(""); //$NON-NLS-1$ } else if(cellFormat==null) { // default format not set setText(value.toString()); } else { try { setText(cellFormat.format(value)); } catch(IllegalArgumentException ex) { // convert to string if value cannot be formatted setText(value.toString()); } } setBorder(new CellBorder(new Color(224, 224, 224))); return this; } } /** * A cell renderer for array indices. */ static class ArrayIndexRenderer extends JLabel implements TableCellRenderer { /** * Constructor */ public ArrayIndexRenderer() { super(); setBorder(BorderFactory.createEtchedBorder()); setOpaque(true); // make background visible setForeground(Color.BLACK); setBackground(UIManager.getColor("Panel.background")); //$NON-NLS-1$ } /** * Returns a label for the specified cell. * * @param table ignored * @param value the row number to be displayed * @param isSelected ignored * @param hasFocus ignored * @param row ignored * @param column the column number * @return a label with the row number */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if(column==0) { setHorizontalAlignment(SwingConstants.RIGHT); } else { setHorizontalAlignment(SwingConstants.CENTER); } if(value==null) { setText(""); //$NON-NLS-1$ } else { setText(value.toString()); } setPreferredSize(new Dimension(20, 18)); return this; } } } /* * 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 */