gt:       refreshTable(); // refresh if there has been a change
context: /*
   * 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:
   * <https://www.compadre.org/osp/>
   */
  
  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)) {
  