/*
 * 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.Dimension;
import java.awt.Font;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * A panel that displays an ArrayTable.
 *
 * @author Douglas Brown
 * @author Wolfgang Christian
 * @version 1.0
 */
public class ArrayPanel extends JPanel implements PropertyChangeListener, Data {
  JTabbedPane tabbedPane = new JTabbedPane();
  ArrayTable[] tables;
  JSpinner spinner;
  JScrollPane scrollpane;
  Object array;
  boolean changed;
  String format = null;
  int firstRowIndex = 0, firstColIndex = 0;
  boolean rowNumberVisible = true, editable = true;
  String[] colNames;
  private int ID = hashCode();

  /**
   * Constructor ArrayPanel
   */
  public ArrayPanel() {
    //System.out.println("creating array panel");
  }

  public static ArrayPanel getArrayPanel(Object arrayObj) {
    ArrayPanel arrayPanel = new ArrayPanel();
    arrayPanel.setArray(arrayObj);
    return arrayPanel;
  }

  /**
   * Gets an array panel for the specified array.
   *
   * @param arrayObj the array
   * @param name the display name for the array
   * @return the array panel
   */
  public void setArray(Object arrayObj) {
    if(!canDisplay(arrayObj)) {
      return;
    }
    if(arrayObj instanceof double[]) {
      setArray((double[]) arrayObj);
    } else if(arrayObj instanceof double[][]) {
      setArray((double[][]) arrayObj);
    } else if(arrayObj instanceof double[][][]) {
      setArray((double[][][]) arrayObj);
    } else if(arrayObj instanceof int[]) {
      setArray((int[]) arrayObj);
    } else if(arrayObj instanceof int[][]) {
      setArray((int[][]) arrayObj);
    } else if(arrayObj instanceof int[][][]) {
      setArray((int[][][]) arrayObj);
    } else if(arrayObj instanceof String[]) {
      setArray((String[]) arrayObj);
    } else if(arrayObj instanceof String[][]) {
      setArray((String[][]) arrayObj);
    } else if(arrayObj instanceof String[][][]) {
      setArray((String[][][]) arrayObj);
    } else if(arrayObj instanceof boolean[]) {
      setArray((boolean[]) arrayObj);
    } else if(arrayObj instanceof boolean[][]) {
      setArray((boolean[][]) arrayObj);
    } else if(arrayObj instanceof boolean[][][]) {
      setArray((boolean[][][]) arrayObj);
    }
    this.array = arrayObj;
  }

  /**
   * Determines if an object is an array that can be displayed.
   *
   * @param obj the object
   * @return true if it can be inspected
   */
  public static boolean canDisplay(Object obj) {
    if(obj==null) {
      return false;
    }
    if((obj instanceof double[])||(obj instanceof double[][])||(obj instanceof double[][][])||(obj instanceof int[])||(obj instanceof int[][])||(obj instanceof int[][][])||(obj instanceof boolean[])||(obj instanceof boolean[][])||(obj instanceof boolean[][][])||(obj instanceof String[])||(obj instanceof String[][])||(obj instanceof String[][][])) {
      return true;
    }
    return false;
  }

  /**
   * Gets the object being displayed.
   *
   * @return
   */
  public Object getArray() {
    return array;
  }

  /**
   * Listens for cell events (data changes) from ArrayTable.
   *
   * @param e the property change event
   */
  public void propertyChange(PropertyChangeEvent e) {
    // forward event to listeners
    changed = true;
    firePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
  }

  /**
   * Sets the same numeric display format for all columns
   *
   * @param _format String
   */
  public void setNumericFormat(String _format) {
    this.format = _format;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setNumericFormat(_format);
    }
  }

  /**
   * Sets the numeric display format for each column
   *
   * @param _format String[]
   */
  public void setNumericFormat(String[] _format) {
    this.format = null;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setNumericFormat(_format);
    }
  }

  /**
   * 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) {
    this.rowNumberVisible = vis;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setRowNumberVisible(vis); // refreshes the table
    }
  }

  /**
   * Sets the column names in the table models.
   *
   * @param names
   */
  public void setColumnNames(String[] names) {
    for(int i = 0; i<tables.length; i++) {
      tables[i].setColumnNames(names);
    }
  }

  /**
   * Sets the column names in each table model separately.
   *
   * @param names
   */
  public void setColumnNames(String[][] names) {
    int n = Math.min(tables.length, names.length); // ensure correct dimensions
    for(int i = 0; i<n; i++) {
      tables[i].setColumnNames(names[i]);
    }
  }

  // Preferred column width changes by Willy Gerber

  /**
   * Sets this column's preferred width of the given column.
   * The minimum width is set to zero and the maximum width is set to 300.
   *
   * @param ncol the column
   * @param nwidth the preferred width
   */
  public void setPreferredColumnWidth(int ncol, int nwidth) {
    for(int table = 0; table<tables.length; table++) {
      javax.swing.table.TableColumn column = tables[table].getColumnModel().getColumn(ncol);
      column.setMinWidth(0);
      column.setMaxWidth(300);
      column.setPreferredWidth(nwidth);
    }
  }

  /**
   * Sets this column's preferred width of the entire table.
   * The minimum width is set to zero and the maximum width is set to 300.
   *
   * @param nwidth the preferred width
   */
  public void setPreferredColumnWidth(int nwidth) {
    for(int table = 0; table<tables.length; table++) {
      for(int col = 0; col<tables[table].getColumnCount(); col++) {
        javax.swing.table.TableColumn column = tables[table].getColumnModel().getColumn(col);
        column.setMinWidth(0);
        column.setMaxWidth(300);
        column.setPreferredWidth(nwidth);
      }
    }
  }

  /**
   * Sets the alignment of the contents of the given column along the X axis.
   * The alignment constants are defined in the SwingConstants class.
   *
   * @param ncol the column
   * @param align  One of the following constants defined in <code>SwingConstants</code>:
   *           <code>LEFT</code>,
   *           <code>CENTER</code> (the default for image-only labels),
   *           <code>RIGHT</code>,
   *           <code>LEADING</code> (the default for text-only labels) or
   *           <code>TRAILING</code>.
   */
  public void setColumnAlignment(int ncol, int align) {
    for(int table = 0; table<tables.length; table++) {
      for(int row = 0; row<tables[table].getRowCount(); row++) {
        javax.swing.table.TableCellRenderer renderer = tables[table].getCellRenderer(row, ncol);
        ((JLabel) renderer).setHorizontalAlignment(align);
      }
    }
  }

  /**
   * Sets the alignment of the contents of all table columns along the X axis.
   * The alignment constants are defined in the SwingConstants class.
   *
   * @param align  One of the following constants defined in <code>SwingConstants</code>:
   *           <code>LEFT</code>,
   *           <code>CENTER</code> (the default for image-only labels),
   *           <code>RIGHT</code>,
   *           <code>LEADING</code> (the default for text-only labels) or
   *           <code>TRAILING</code>.
   */
  public void setColumnAlignment(int align) {
    for(int table = 0; table<tables.length; table++) {
      for(int row = 0; row<tables[table].getRowCount(); row++) {
        for(int col = 0; col<tables[table].getColumnCount(); col++) {
          javax.swing.table.TableCellRenderer renderer = tables[table].getCellRenderer(row, col);
          ((JLabel) renderer).setHorizontalAlignment(align);
        }
      }
    }
  }

  // End of changes by Willy Gerber
  public int getFirstRowIndex() {
    return this.firstRowIndex;
  }

  /**
   * Sets the first row's index.
   *
   * @param index
   */
  public void setFirstRowIndex(int index) {
    this.firstRowIndex = index;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setFirstRowIndex(index);
    }
  }

  /**
   * Sets the first column's index.
   *
   * @param index
   */
  public void setFirstColIndex(int index) {
    this.firstColIndex = index;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setFirstColIndex(index);
    }
  }

  /**
   * Sets the column's lock flag.
   *
   * @param column   int
   * @param locked   boolean
   */
  public void setColumnLock(int columnIndex, boolean locked) {
    for(int i = 0; i<tables.length; i++) {
      tables[i].setColumnLock(columnIndex, locked);
    }
  }

  /**
   * Sets the lock flag for multiple columns.  Previously set locks are cleared.
   *
   * @param locked   boolean array
   */
  public void setColumnLocks(boolean[] locked) {
    for(int i = 0; i<tables.length; i++) {
      tables[i].setColumnLocks(locked);
    }
  }

  /**
   * Sets the editable property for the entire panel.
   *
   * @param editable true to allow editing of the cell values
   */
  public void setEditable(boolean _editable) {
    this.editable = _editable;
    for(int i = 0; i<tables.length; i++) {
      tables[i].setEditable(_editable);
    }
  }

  /**
   * 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) {
    for(int i = 0; i<tables.length; i++) {
      tables[i].setTransposed(transposed);
    }
  }

  /**
   * 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 (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].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 fg  the desired foreground <code>Color</code> 
   * @see java.awt.Component#getForeground
   */
  public void setForeground(Color color){ // Added by Paco
    super.setForeground(color);
    if (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].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 fg  the desired background <code>Color</code> 
   * @see java.awt.Component#getBackground
   */
  public void setBackground(Color color){ // Added by Paco
    super.setBackground(color);
    if (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].setBackground(color);
  }

  /**
   * Sets the data foreground color of this component.  It is up to the
   * look and feel to honor this property, some may choose to ignore
   * it.
   *
   * @param fg  the desired foreground <code>Color</code> 
   */
  public void setDataForeground(Color color){ // Added by Paco
    if (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].setDataForeground(color);
    refreshTable();
  }

  /**
   * Sets the data background color of this component.  It is up to the
   * look and feel to honor this property, some may choose to ignore
   * it.
   *
   * @param fg  the desired background <code>Color</code> 
   * @see java.awt.Component#getBackground
   */
  public void setDataBackground(Color color){ // Added by Paco
    if (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].setDataBackground(color);
    refreshTable();
  }

  /**
   * Sets the table's auto resize mode when the table is resized.
   *
   * @param   mode One of 5 legal values:
   *                   AUTO_RESIZE_OFF,
   *                   AUTO_RESIZE_NEXT_COLUMN,
   *                   AUTO_RESIZE_SUBSEQUENT_COLUMNS,
   *                   AUTO_RESIZE_LAST_COLUMN,
   *                   AUTO_RESIZE_ALL_COLUMNS
   */
  public void setAutoResizeMode(int mode) {
    if (tables!=null) for(int i = 0; i<tables.length; i++) tables[i].setAutoResizeMode(mode);
    refreshTable();
  }

  // -------------------------------
  // Getters
  // -------------------------------
  public int getNumColumns() {
    return tables[0].getColumnCount();
  }

  //-------------------------------
  // Getters
  // -------------------------------

  /**
   * Refresh the data in all the tables.
   */
  public void refreshTable() {
    for(int i = 0; i<tables.length; i++) {
      tables[i].refreshTable();
    }
  }

  /**
   * 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) {
    for(int i = 0; i<tables.length; i++) {
      tables[i].setRefreshDelay(delay);
    }
  }

  /**
   * Creates the GUI.
   */
  protected void createGUI() {
    this.removeAll(); // remove old elements Paco: be careful with this. If you use the ArrayPanel as a normal JPanel
    // and have added another component, it will be lost!
    this.setPreferredSize(new Dimension(400, 300));
    this.setLayout(new BorderLayout());
    scrollpane = new JScrollPane(tables[0]);
    if(tables.length>1) {
      // create spinner
      SpinnerModel model = new SpinnerNumberModel(0, 0, tables.length-1, 1);
      spinner = new JSpinner(model);
      JSpinner.NumberEditor editor = new JSpinner.NumberEditor(spinner);
      editor.getTextField().setFont(tables[0].indexRenderer.getFont());
      spinner.setEditor(editor);
      spinner.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent e) {
          int i = ((Integer) spinner.getValue()).intValue();
          scrollpane.setViewportView(tables[i]);
        }

      });
      Dimension dim = spinner.getMinimumSize();
      spinner.setMaximumSize(dim);
      add(scrollpane, BorderLayout.CENTER);
      JToolBar toolbar = new JToolBar();
      toolbar.setFloatable(false);
      toolbar.add(new JLabel(" index ")); //$NON-NLS-1$
      toolbar.add(spinner);
      toolbar.add(Box.createHorizontalGlue());
      add(toolbar, BorderLayout.NORTH);
    } else {
      scrollpane.createHorizontalScrollBar();
      add(scrollpane, BorderLayout.CENTER);
    }
    this.validate();                      // refresh the display
  }
  
  //_____________________________private constructors___________________________
  private void setArray(int[] array) {
    if(this.array instanceof int[]) { // && ((int[])this.array).length==array.length) { // Paco
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(int[][] array) {
    if(this.array instanceof int[][]) { //&& ((int[][])this.array).length==array.length  && ((int[][])this.array)[0].length==array[0].length) { // Paco
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(int[][][] array) {
    /* Isn't this too much? Paco
    if (this.array instanceof int[][][] && ((int[][][])this.array).length==array.length ) {
      boolean quickChange = true;
      int[][][] thisArray = (int[][][]) this.array;
      for (int i=0; i<thisArray.length; i++) {
        if (thisArray[i].length!=array[i].length || thisArray[i][0].length!=array[i][0].length) {
          quickChange = false;
          break;
        }
      }
      if (quickChange) {
        for (int i=0; i<thisArray.length; i++) tables[i].tableModel.setArray(array[i]);
        return;
      }
    }
    */
    tables = new ArrayTable[array.length];
    for(int i = 0; i<tables.length; i++) {
      tables[i] = new ArrayTable(array[i]);
      tables[i].addPropertyChangeListener("cell", this); //$NON-NLS-1$
      tables[i].setFont(getFont());
      tables[i].setForeground(getForeground());
      tables[i].setBackground(getBackground());
    }
    createGUI();
  }

  private void setArray(double[] array) {
    if(this.array instanceof double[]) { // ((double[])this.array).length==array.length) { // Paco added this
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(double[][] array) {
    if(this.array instanceof double[][]) { //&& ((double[][])this.array).length==array.length && ((double[][])this.array)[0].length==array[0].length) { // Paco
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(double[][][] array) {
    tables = new ArrayTable[array.length];
    for(int i = 0; i<tables.length; i++) {
      tables[i] = new ArrayTable(array[i]);
      tables[i].addPropertyChangeListener("cell", this); //$NON-NLS-1$
      tables[i].setFont(getFont());
      tables[i].setForeground(getForeground());
      tables[i].setBackground(getBackground());
    }
    createGUI();
  }

  private void setArray(String[] array) {
    if(this.array instanceof String[]) { // && ((String[])this.array).length==array.length) { // Paco added this
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(String[][] array) {
    if(this.array instanceof String[][]) { //&& ((String[][])this.array).length==array.length && ((String[][])this.array)[0].length==array[0].length) { // Paco
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(String[][][] array) {
    tables = new ArrayTable[array.length];
    for(int i = 0; i<tables.length; i++) {
      tables[i] = new ArrayTable(array[i]);
      tables[i].addPropertyChangeListener("cell", this); //$NON-NLS-1$
      tables[i].setFont(getFont());
      tables[i].setForeground(getForeground());
      tables[i].setBackground(getBackground());
    }
    createGUI();
  }

  private void setArray(boolean[] array) {
    if(this.array instanceof boolean[]) { // && ((boolean[])this.array).length==array.length) { // Paco added this
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(boolean[][] array) {
    if(this.array instanceof boolean[][]) { //&& ((boolean[][])this.array).length==array.length && ((boolean[][])this.array)[0].length==array[0].length) { // Paco
      tables[0].tableModel.setArray(array);
      return;
    }
    tables = new ArrayTable[1];
    tables[0] = new ArrayTable(array);
    tables[0].addPropertyChangeListener("cell", this); //$NON-NLS-1$
    tables[0].setFont(getFont());
    tables[0].setForeground(getForeground());
    tables[0].setBackground(getBackground());
    createGUI();
  }

  private void setArray(boolean[][][] array) {
    tables = new ArrayTable[array.length];
    for(int i = 0; i<tables.length; i++) {
      tables[i] = new ArrayTable(array[i]);
      tables[i].addPropertyChangeListener("cell", this); //$NON-NLS-1$
      tables[i].setFont(getFont());
      tables[i].setForeground(getForeground());
      tables[i].setBackground(getBackground());
    }
    createGUI();
  }

  // The following methods were added to implement the Data interface for double[][] arrays.

  /**
   * Gets column names from Table Model.
   * Implementation of Data interface.
   */
  public String[] getColumnNames() {
    double[][] data = getData2D();
    if(data==null) {
      return null; // no data
    }
    int n = data.length;
    if((colNames==null)||(n!=colNames.length)) { // every array columns should have a name
      colNames = new String[n];
    }
    String[] modelNames = tables[0].tableModel.columnNames;
    int stop = (modelNames==null) ? 0 : Math.min(n, modelNames.length-1);
    for(int i = 0; i<stop; i++) {    // assign model names to Data columns
      colNames[i] = modelNames[i+1]; // skip zero column name because it is the row index
    }
    for(int i = stop; i<n; i++) { // assign default column names
      colNames[i] = "C"+(i+1);    //$NON-NLS-1$
    }
    return colNames;
  }

  /**
   * Gets double[][] data from the Table Model and transposes this array if necessary.
   * Implementation of Data interface.
   */
  public double[][] getData2D() {
    if((tables==null)||(tables[0]==null)) {
      return null;
    }
    boolean transposed = tables[0].tableModel.transposed;
    double[][] data = tables[0].tableModel.doubleArray2;
    if(!transposed&&(data!=null)) { // first index of the array is the DataPanel column
      int r = data.length;
      int c = 0;
      for(int i = 0; i<r; i++) {    // find the largest index to set the number of columns
        c = Math.max(c, data[i].length);
      }
      double[][] tdata = new double[c][r];
      for(int i = 0; i<r; i++) {
        int ci = data[i].length;
        for(int j = 0; j<ci; j++) {
          tdata[j][i] = data[i][j];
        }
        for(int j = ci; j<c; j++) {
          tdata[j][i] = Double.NaN;
        }
      }
      return tdata;
    }
    return data;
  }

  /**
   * Not used because double[][][] is not used in any OSP Tools.
   * Implementation of Data interface method.
   */
  public double[][][] getData3D() {
    return null;
  }

  /**
   * Not used because Data is stored in this object, not in a list of Data objects.
   * Implementation of Data interface.
   */
  public List<Data> getDataList() {
    return null;
  }

  /**
   * Not used Data because is stored in 2D arrays.
   * Implementation of Data interface.
   */
  public ArrayList<Dataset> getDatasets() {
    return null;
  }

  /**
   * Fill colors for columns are not specified. Client should assign colors.
   * Implementation of Data interface.
   */
  public Color[] getFillColors() {
    return null;
  }

  /**
   * Lines colors for columns are not specified.  Client should assign colors.
   * Implementation of Data interface.
   */
  public Color[] getLineColors() {
    return null;
  }

  /**
   * Gets the Data ID.
   */
  public int getID() {
    boolean transposed = tables[0].tableModel.transposed;
    if(transposed) {
      return ID^0xffff; // exclusive OR to reverse and produce new ID
    }
    return ID;
  }

  /**
   * Sets the Data ID.
   */
  public void setID(int id) {
    ID = id;
  }

}

/*
 * 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
 */
