/*
 * 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.controls;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Point;
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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JColorChooser;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import org.opensourcephysics.display.CellBorder;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.tools.ArrayInspector;

/**
 * This is a table view of an XML control and its property contents.
 *
 * @author Douglas Brown
 */
public class XMLTable extends JTable {
  final static Color LIGHT_BLUE = new Color(196, 196, 255);
  // instance fields

  XMLTableModel tableModel;
  XMLCellRenderer xmlRenderer = new XMLCellRenderer();
  XMLValueEditor valueEditor = new XMLValueEditor();
  Color defaultBackgroundColor = Color.white;
  Map<String, Color> cellColors = new HashMap<String, Color>();         // maps property to color of cell
  Map<String, Color> selectedCellColors = new HashMap<String, Color>(); // maps property to color of selected cell
  Map<String, Color> editingCellColors = new HashMap<String, Color>();  // maps property to color of editing cell
  Color defaultEditingColor;
  PropertyChangeListener comboListener;

  /**
   * Constructor for XMLControl.
   *
   * @param control the XMLcontrol
   */
  public XMLTable(XMLControl control) {
    tableModel = new XMLTableModel(control);
    init();
  }

  /**
   * Constructor for XMLTableModel.
   *
   * @param model the XMLTableModel
   */
  public XMLTable(XMLTableModel model) {
    tableModel = model;
    init();
  }

  /**
   * Gets the currently displayed XMLControl.
   *
   * @return the XML control
   */
  public XMLControl getControl() {
    return tableModel.control;
  }

  /**
   * Enables/disables editing for the entire table.
   * Overrides the editable property of individual parameters.
   *
   * @param editable true to enable editing
   */
  public void setEditable(boolean editable) {
    tableModel.editable = editable;
  }

  /**
   * Returns true if editing is enabled for the entire table.
   * If table is editiable, editing can still be disabled for individual parameters.
   *
   * @return true if editable
   * @see setEditable(String propName, boolean editable)
   */
  public boolean isEditable() {
    return tableModel.editable;
  }

  /**
   * Enables/disables editing for a specified property name.
   * Properties are editable by default.
   *
   * @param propName the property name
   * @param editable true to enable editing
   */
  public void setEditable(String propName, boolean editable) {
    // add to uneditablePropNames list if editable is false
    if(!editable) {
      tableModel.uneditablePropNames.add(propName);
    } else {
      tableModel.uneditablePropNames.remove(propName);
    }
  }

  /**
   * Returns true if editing is enabled for the specified property.
   *
   * @param propName the name of the property
   * @return true if editable
   */
  public boolean isEditable(String propName) {
    return !tableModel.uneditablePropNames.contains(propName);
  }

  /**
   * Determines whether the given cell is editable.
   *
   * @param row the row index
   * @param col the column index
   * @return true if editable
   */
  public boolean isCellEditable(int row, int col) {
    return tableModel.editable&&tableModel.isCellEditable(row, col);
  }

  /**
   * Sets the font. Overrides JTable method
   *
   * @param font the font
   */
  public void setFont(Font font) {
    super.setFont(font);
    if(xmlRenderer!=null) {
      xmlRenderer.setFont(font);
      valueEditor.field.setFont(font);
      // resize row heights after revalidation
      Runnable runner = new Runnable() {
        public void run() {
          if(getTableHeader().getHeight()>0) { // changed by W. Christian
            setRowHeight(getTableHeader().getHeight());
          }
        }

      };
      SwingUtilities.invokeLater(runner);
    }
  }

  /**
   * Sets the color of a selected cell for a specified property name.
   * May be set to null.
   *
   * @param propName the property name
   * @param color the color of the cell when selected
   */
  public void setSelectedColor(String propName, Color color) {
    selectedCellColors.put(propName, color);
  }

  /**
   * Gets the color of a selected cell for a specified property name.
   *
   * @param propName the property name
   * @return the color
   */
  public Color getSelectedColor(String propName) {
    Color color = selectedCellColors.get(propName);
    return(color==null) ? LIGHT_BLUE : color;
  }

  /**
   * Sets the background color of the value field for a specified property name.
   * May be set to null.
   *
   * @param propName the property name
   * @param color the color
   */
  public void setBackgroundColor(String propName, Color color) {
    cellColors.put(propName, color);
  }

  /**
   * Gets the background color for a specified property name.
   *
   * @param propName the property name
   * @return the color
   */
  public Color getBackgroundColor(String propName) {
    Color color = cellColors.get(propName);
    return(color==null) ? defaultBackgroundColor : color;
  }

  /**
   * Sets the default color of the editor for a specified property name.
   * May be set to null.
   *
   * @param propName the property name
   * @param color the color of the cell when being edited
   */
  public void setEditingColor(String propName, Color color) {
    editingCellColors.put(propName, color);
  }

  /**
   * Sets the color of the editor for a specified property name.
   *
   * @param propName the property name
   * @return the color
   */
  public Color getEditingColor(String propName) {
    Color color = editingCellColors.get(propName);
    return(color==null) ? defaultEditingColor : 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) {
    return xmlRenderer;
  }

  /**
   * Returns the editor for a cell specified by row and column.
   *
   * @param row the row number
   * @param column the column number
   * @return the cell editor
   */
  public TableCellEditor getCellEditor(int row, int column) {
    return valueEditor;
  }

  /**
   * A cell renderer for an xml table.
   */
  class XMLCellRenderer extends DefaultTableCellRenderer {
    Color lightGreen = new Color(204, 255, 204);                          // for double-clickable cells
    Color lightGray = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$
    Font font = new JTextField().getFont();

    // Constructor

    /**
     * Constructor XMLCellRenderer
     */
    public XMLCellRenderer() {
      super();
      setOpaque(true); // make background visible
      setForeground(Color.black);
      setFont(font);
    }

    // Returns a label for the specified cell.
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
      setForeground(Color.BLACK);
      if(value==null) {
        value = ""; // changed by W. Christian to trap for null values //$NON-NLS-1$
      }
      String propName = (String) tableModel.getValueAt(row, 0);
      Class<?> childClass = null;
      Object childObj = null;
      if(column==0) { // property name
        if(isSelected) {
          //setBackground(new Color(255, 192, 255));
          setBackground(LIGHT_BLUE);
        } else {
          setBackground(lightGray);
        }
        setHorizontalAlignment(SwingConstants.LEFT);
        String text = value.toString();
        if(OSPRuntime.getTranslator()!=null) {
          text = OSPRuntime.getTranslator().getProperty(XMLTable.this, text);
        }
        setText(text);
        //setBorder(BorderFactory.createLineBorder(new Color(224, 224, 224),1));
        setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 2));
        return this;
      }
      if(value instanceof XMLProperty) {                // object, array or collection type
        XMLProperty prop = (XMLProperty) value;
        XMLProperty parent = prop.getParentProperty();
        childClass = parent.getPropertyClass();
        String className = XML.getSimpleClassName(childClass);
        XMLControl control = (XMLControl) parent.getParentProperty();
        childObj = control.getObject(parent.getPropertyName());
        // array type
        if(parent.getPropertyType().equals("array")) {  //$NON-NLS-1$
          Object array = childObj;
          // determine if base component type is primitive and count array elements
          Class<?> baseType = array.getClass().getComponentType();
          int count = Array.getLength(array);
          int insert = className.indexOf("[]")+1;       //$NON-NLS-1$
          className = className.substring(0, insert)+count+className.substring(insert);
          while(baseType.getComponentType()!=null) {
            baseType = baseType.getComponentType();
            array = Array.get(array, 0);
            if(array==null) {
              break;
            }
            count = Array.getLength(array);
            insert = className.indexOf("[]", insert)+1; //$NON-NLS-1$
            className = className.substring(0, insert)+count+className.substring(insert);
          }
        }
        // general object types
        if((childClass!=OSPCombo.class                  // OSPCombo handled below
          )&&(childClass!=Boolean.class                 // Boolean handled below
            )&&(childClass!=Character.class)) {         // Char handled below
          setText(className);
          setBackground(isInspectable(parent) ? lightGreen : lightGray);
          //setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 2));
          //setBorder(BorderFactory.createLineBorder(new Color(240, 240, 240)));
          setBorder(new CellBorder(new Color(240, 240, 240)));
          setHorizontalAlignment(SwingConstants.CENTER);
          if(isSelected&&isInspectable(parent)) {
            setBackground(getSelectedColor(propName));
            setForeground(Color.RED);
          }
          return this;
        }
      }
      // int, double, boolean, string and special object types
      if(isSelected) {
        setBackground(getSelectedColor(propName));
        setForeground(Color.RED);
      } else {
        setBackground(getBackgroundColor(propName));
      }
      setHorizontalAlignment(SwingConstants.LEFT);
      if((childClass==OSPCombo.class)||(childClass==Boolean.class)||(childClass==Character.class)) {
        setText(childObj.toString());
      } else {
        setText(value.toString());
      }
      //setBorder(BorderFactory.createLineBorder(new Color(240, 240, 240)));
      setBorder(new CellBorder(new Color(240, 240, 240)));
      //setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 2));
      if(!tableModel.editable||tableModel.uneditablePropNames.contains(propName)) {
        setForeground(Color.GRAY); // override all other color settings
      }
      return this;
    }

  }

  /**
   * A cell editor for an xml table.
   */
  class XMLValueEditor extends AbstractCellEditor implements TableCellEditor {
    JPanel panel = new JPanel(new BorderLayout());
    JTextField field = new JTextField();
    int keepFocus = -2;
    OSPCombo combo; // may be null

    // Constructor.
    XMLValueEditor() {
      defaultEditingColor = field.getSelectionColor();
      panel.add(field, BorderLayout.CENTER);
      panel.setOpaque(false);
      field.setBorder(BorderFactory.createLineBorder(new Color(128, 128, 128), 1));
      //field.setBorder(BorderFactory.createEmptyBorder(0, 1, 1, 0));
      field.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          stopCellEditing();
          keepFocus = -2;
        }

      });
      field.addKeyListener(new KeyAdapter() {
        public void keyPressed(KeyEvent e) {
          if(e.getKeyCode()==KeyEvent.VK_ENTER) {
            stopCellEditing();
            keepFocus = -2;
          } else if(field.isEnabled()) {
            field.setBackground(Color.yellow);
          }
        }

      });
      field.addFocusListener(new FocusAdapter() {
        public void focusLost(FocusEvent e) {
          if(e.isTemporary()) {
            return;
          }
          if(field.getBackground()!=defaultBackgroundColor) {
            stopCellEditing();
          }
          int i = XMLTable.this.getSelectedRow();
          if(keepFocus==i) {
            keepFocus = -2;
          } else {
            XMLTable.this.requestFocusInWindow();
          }
        }

      });
      // OSPCombo case
      field.addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
          if(combo!=null) {
            if(combo.getPropertyChangeListeners("index").length>0) {      //$NON-NLS-1$
              combo.removePropertyChangeListener("index", comboListener); //$NON-NLS-1$
              combo.setVisible(false);
              stopCellEditing();
            } else {
              // remove and add combo listener
              combo.removePropertyChangeListener("index", comboListener); //$NON-NLS-1$
              combo.addPropertyChangeListener("index", comboListener);    //$NON-NLS-1$
              combo.showPopup(field);
            }
          }
        }

      });
    }

    // Gets the component to be displayed while editing.
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
      combo = null;
      String propName = (String) tableModel.getValueAt(row, 0);
      field.setBackground(defaultBackgroundColor);
      field.setSelectionColor(getEditingColor(propName));
      final int rowNumber = row;
      final int colNumber = column;
      if(value instanceof XMLControl) {                                           // object type
        final XMLControl childControl = (XMLControl) value;
        // Color case
        if(childControl.getObjectClass()==Color.class) {
          Color color = (Color) childControl.loadObject(null);
          String title = ControlsRes.getString("XMLTable.ColorChooser.Title");    //$NON-NLS-1$
          Color newColor = JColorChooser.showDialog(null, title, color);
          if((newColor!=null)&&!color.equals(newColor)) {
            childControl.saveObject(newColor);
            tableModel.fireTableCellUpdated(row, column);
          }
          return null;
        }
        // Character case
        if(childControl.getObjectClass()==Character.class) {
          Character c = (Character) childControl.loadObject(null);
          field.setEditable(true);
          field.setText(c.toString());
          return panel;
        }
        // OSPCombo case
        if(childControl.getObjectClass()==OSPCombo.class) {
          combo = (OSPCombo) childControl.loadObject(null);
          combo.row = row;
          combo.column = column;
          field.setText(combo.toString());
          field.setEditable(false);
          return panel;
        }
        // Boolean case
        if(childControl.getObjectClass()==Boolean.class) {
          Boolean bool = (Boolean) childControl.loadObject(null);
          int n = bool.booleanValue() ? 0 : 1;
          combo = new OSPCombo(new String[] {"true", "false"}, n);                //$NON-NLS-1$//$NON-NLS-2$
          combo.row = row;
          combo.column = column;
          field.setText(bool.toString());
          field.setEditable(false);
          combo.addPropertyChangeListener("value", new PropertyChangeListener() { //$NON-NLS-1$
            public void propertyChange(PropertyChangeEvent e) {
              OSPCombo combo = (OSPCombo) e.getSource();
              Boolean bool = new Boolean(combo.getSelectedIndex()==0);
              childControl.saveObject(bool);
            }

          });
          return panel;
        }
        XMLTableInspector inspector = new XMLTableInspector(childControl, isEditable());
        // listen for "xmlData" changes in inspector
        inspector.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent e) {
            // signal listeners when inspector closes and xml data is changed
            if(e.getPropertyName().equals("xmlData")) { //$NON-NLS-1$
              tableModel.fireTableCellUpdated(rowNumber, colNumber);
            }
          }

        });
        // offset new inspector relative to parent container
        Container cont = XMLTable.this.getTopLevelAncestor();
        Point p = cont.getLocationOnScreen();
        inspector.setLocation(p.x+30, p.y+30);
        inspector.setVisible(true);
        return null;
      } else if(value instanceof XMLProperty) {                                              // collection or array type
        XMLProperty prop = (XMLProperty) value;
        XMLProperty parent = prop.getParentProperty();
        if(parent.getPropertyType().equals("collection")) {                                  //$NON-NLS-1$
          String name = parent.getPropertyName();
          parent = parent.getParentProperty();
          if(parent instanceof XMLControl) {
            XMLControl cControl = new XMLControlElement();
            Collection<?> c = (Collection<?>) ((XMLControl) parent).getObject(name);
            Iterator<?> it = c.iterator();
            int i = 0;
            while(it.hasNext()) {
              Object next = it.next();
              cControl.setValue("item_"+i, next);                                            //$NON-NLS-1$
              i++;
            }
            XMLTableInspector inspector = new XMLTableInspector(cControl);
            inspector.setTitle(ControlsRes.getString("XMLTable.Inspector.Title")+name+"\""); //$NON-NLS-1$//$NON-NLS-2$
            // offset new inspector relative to parent container
            Container cont = XMLTable.this.getTopLevelAncestor();
            Point p = cont.getLocationOnScreen();
            inspector.setLocation(p.x+30, p.y+30);
            inspector.setVisible(true);
            cont.transferFocus();
          }
        }
        // display an array inspector if available
        XMLProperty arrayProp = prop.getParentProperty();
        ArrayInspector arrayInspector = ArrayInspector.getInspector(arrayProp);
        if(arrayInspector!=null) {
          String name = arrayProp.getPropertyName();
          parent = arrayProp.getParentProperty();
          while(!(parent instanceof XMLControl)) {
            name = parent.getPropertyName();
            arrayProp = parent;
            parent = parent.getParentProperty();
          }
          final XMLControl arrayControl = (XMLControl) parent;
          final String arrayName = name;
          final Object arrayObj = arrayInspector.getArray();
          arrayInspector.setEditable(tableModel.editable);
          // listen for "cell" and "arrayData" changes in the array inspector
          arrayInspector.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
              if(e.getPropertyName().equals("cell")) {                                       //$NON-NLS-1$
                // set new value in array control
                arrayControl.setValue(arrayName, arrayObj);
              }
              // signal listeners when inspector closes and array data is changed
              else if(e.getPropertyName().equals("arrayData")) {                             //$NON-NLS-1$
                tableModel.fireTableCellUpdated(rowNumber, colNumber);
              }
            }

          });
          // offset new arrayInspector relative to parent container
          Container cont = XMLTable.this.getTopLevelAncestor();
          Point p = cont.getLocationOnScreen();
          arrayInspector.setLocation(p.x+30, p.y+30);
          arrayInspector.setVisible(true);
          cont.transferFocus();
        }
        return null;
      } // end XMLProperty case
      // value is string
      field.setEditable(true);
      if(value!=null) {
        field.setText(value.toString());
      }
      return panel;
    }

    // Determines when editing starts.
    public boolean isCellEditable(EventObject e) {
      if(e instanceof MouseEvent) {
        MouseEvent me = (MouseEvent) e;
        XMLTable table = (XMLTable) me.getSource();
        int row = table.rowAtPoint(me.getPoint());
        keepFocus = row;
        Object value = tableModel.getValueAt(row, 1);
        // handle special object cases
        if(value instanceof XMLControl) {
          XMLControl childControl = (XMLControl) value;
          if((childControl.getObjectClass()==OSPCombo.class)||(childControl.getObjectClass()==Boolean.class)||(childControl.getObjectClass()==Character.class)) {
            return true;
          }
        }
        if((value instanceof String)||(me.getClickCount()==2)) {
          return true;
        }
      } else if(e instanceof ActionEvent) {
        keepFocus = -2;
        return true;
      }
      return false;
    }

    // Called when editing is completed.
    public Object getCellEditorValue() {
      XMLTable.this.requestFocusInWindow();
      if(field.getBackground()!=defaultBackgroundColor) {
        field.setBackground(defaultBackgroundColor);
        return field.getText();
      }
      return null;
    }

  }

  // refreshes the table
  public void refresh() {
    Runnable runner = new Runnable() {
      public synchronized void run() {
        tableChanged(new TableModelEvent(tableModel, TableModelEvent.HEADER_ROW));
      }

    };
    if(SwingUtilities.isEventDispatchThread()) {
      runner.run();
    } else {
      SwingUtilities.invokeLater(runner);
    }
  }

  public void tableChanged(TableModelEvent e) {
    // pass the tablemodel event to property change listeners
    firePropertyChange("tableData", null, e); //$NON-NLS-1$
    super.tableChanged(e);
  }

  /**
   *    Adds a listener that invokes the given method in the given object when the xml data changes.
   *    The method in the target is invoked with the table's variable name passed as a
   *    parameter.  The method will be invoked for all parameter changes.
   *
   *   @param  methodName   the name of the method
   *   @param  target       the target for the method
   */
  public void addControlListener(String methodName, final Object target) {
    addControlListener(null, methodName, target);
  }

  /**
   *  Adds a listener that invokes the given method in the given object when the xml data changes.
   *  The method in the target is invoked with the table's variable name passed as a
   *  parameter. The method will be invoked for all parameter changes if the parameter name is null.
   *
   * @param  parameterName the name of the parameter that will invoke the method
   * @param  methodName    the name of the method that will be invoked
   * @param  target        the target class for the method
   */
  public void addControlListener(final String parameterName, String methodName, final Object target) {
    Class<?>[] parameters = new Class[] {String.class};
    try {
      final java.lang.reflect.Method m = target.getClass().getMethod(methodName, parameters);
      tableModel.addTableModelListener(new TableModelListener() {
        final String par = parameterName;
        public void tableChanged(TableModelEvent e) {
          if((e.getType()!=TableModelEvent.UPDATE)||(e.getColumn()!=1)||(e.getFirstRow()<0)) {
            return;
          }
          String name = getValueAt(e.getFirstRow(), 0).toString();
          if((par==null)||par.equals(name)) { // invoke the method for all parameters if the parameter name is null
            Object[] args = {name};
            try {
              m.invoke(target, args);
            } catch(Exception ex) {
              ex.printStackTrace();
            }
          }
        }

      });
    } catch(NoSuchMethodException nsme) {
      System.err.println(ControlsRes.getString("XMLTable.ErrorMessage.NoMethod") //$NON-NLS-1$
                         +" "+methodName+"()");                                  //$NON-NLS-1$//$NON-NLS-2$
    }
  }

  // initializes the table
  private void init() {
    setModel(tableModel);
    JTableHeader header = getTableHeader();
    header.setReorderingAllowed(false);
    header.setForeground(Color.BLACK); // set header text color
    setGridColor(Color.BLACK);
    // Override the default tab behaviour
    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 JPanel) {
          JPanel panel = (JPanel) comp;
          comp = panel.getComponent(0);
          if(comp instanceof JTextField) {
            JTextField field = (JTextField) comp;
            OSPCombo combo = null;
            Object value = tableModel.getValueAt(row, column);
            if(value instanceof XMLControl) {
              final XMLControl control = (XMLControl) value;
              if(control.getObjectClass()==OSPCombo.class) {
                combo = (OSPCombo) control.loadObject(null);
              } else if(control.getObjectClass()==Boolean.class) {
                Boolean bool = (Boolean) control.loadObject(null);
                int n = bool.booleanValue() ? 0 : 1;
                combo = new OSPCombo(new String[] {"true", "false"}, n);                //$NON-NLS-1$//$NON-NLS-2$
                combo.addPropertyChangeListener("value", new PropertyChangeListener() { //$NON-NLS-1$
                  public void propertyChange(PropertyChangeEvent e) {
                    OSPCombo combo = (OSPCombo) e.getSource();
                    Boolean bool = new Boolean(combo.getSelectedIndex()==0);
                    control.saveObject(bool);
                  }

                });
              }
            }
            if(combo==null) {
              field.requestFocus();
              field.selectAll();
            } else {
              // add combo listener and show popup
              combo.row = row;
              combo.column = column;
              combo.removePropertyChangeListener("index", comboListener);               //$NON-NLS-1$
              combo.addPropertyChangeListener("index", comboListener);                  //$NON-NLS-1$
              combo.showPopup(field);
            }
          }
        }
      }

    };
    KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
    getActionMap().put(im.get(enter), enterAction);
    // create OSPCombo listener
    comboListener = new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent e) {
        OSPCombo combo = (OSPCombo) e.getSource();
        int n = ((Integer) e.getOldValue()).intValue();
        if(n!=combo.getSelectedIndex()) {
          combo.firePropertyChange("value", n, combo.getSelectedIndex()); //$NON-NLS-1$
          tableModel.fireTableCellUpdated(combo.row, combo.column);
        }
        combo.removePropertyChangeListener("index", this);                //$NON-NLS-1$
        valueEditor.stopCellEditing();
      }

    };
  }

  // determines whether the specified property is inspectable
  private boolean isInspectable(XMLProperty prop) {
    if(prop.getPropertyType().equals("object")) { //$NON-NLS-1$
      return true;
    }
    if(prop.getPropertyType().equals("array")) { //$NON-NLS-1$
      return ArrayInspector.canInspect(prop);
    }
    if(prop.getPropertyType().equals("collection")) { //$NON-NLS-1$
      return true;
    }
    return false;
  }

}

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