/*
 * 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.ejs.control.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.util.ArrayList;
import javax.swing.JComponent;
import org.opensourcephysics.ejs.control.ControlElement;
import org.opensourcephysics.ejs.control.Utils;
import org.opensourcephysics.ejs.control.value.Value;

/**
 * <code>ControlSwingElement</code> is a base class for an object that
 * displays a visual java.awt.Component.
 * <p>
 * @see     java.awt.Component
 * @see     org.opensourcephysics.ejs.control.ControlElement
 */
public abstract class ControlSwingElement extends ControlElement {
  // Important: if you change the order of the properties you must up date
  // these constants accordingly!!!
  // These constants are her for use of any subclass that overrides the
  // setValue() method for any of these properties
  static public final int NAME = 0;                   // The name of the element // shadows superclass field
  static public final int POSITION = 1;               // The position in its parent
  static public final int PARENT = 2;                 // Its parent
  static public final int ENABLED = 3;                // Whetehr it is responsive or not
  static public final int VISIBLE = 4;                // The visibility
  static public final int SIZE = 5;                   // The size
  static public final int FOREGROUND = 6;             // The foreground color
  static public final int BACKGROUND = 7;             // The background color
  static public final int FONT = 8;                   // The font
  static public final int TOOLTIP = 9;                // The tooltip
  // Particular types of actions
  static public final int ACTION_PRESS = 10;
  static public final int ACTION_ON = 20;
  static public final int ACTION_OFF = 21;
  static private ArrayList<String> myInfoList = null; // The list of registered properties
  protected Component myVisual;                       // The visual element to display
  private Color myDefaultBkgd = null, myDefaultFrgd = null;
  private Font myDefaultFont = null;
  private Dimension mySize = null;

  // ------------------------------------------------
  // Static constants and constructor
  // ------------------------------------------------
  // public ControlSwingElement() { this(null); }

  /**
   * Instantiates an object that wrapps a Swing JComponent of this type.
   * If an object of an appropriate class is provided, it is used as
   * the visual.
   * @param _visual The javax.swing.JComponent to be wrapped
   */
  public ControlSwingElement(Object _visual) {
    super(_visual);
    myVisual = createVisual(_visual);
    super.myObject = myVisual;
    myDefaultFrgd = myVisual.getForeground();
    myDefaultBkgd = myVisual.getBackground();
    myDefaultFont = myVisual.getFont();
    if(myVisual instanceof JComponent) {
      mySize = ((JComponent) myVisual).getPreferredSize();
    }
  }

  // ------------------------------------------------
  // Visual components
  // ------------------------------------------------

  /**
   * Creates the visual component of this <code>ControlElement</code>,
   * the one you can configure graphically.
   * If an object of an appropriate class is provided, it is used as
   * the visual.
   * @param _visual The javax.swing.JComponent to be wrapped
   */
  abstract protected Component createVisual(Object _visual);

  /**
   * Returns the visual component of this <code>ControlElement</code>,
   * the one you can configure graphically.
   */
  final public Component getVisual() {
    return myVisual;
  }

  /**
   * Returns the component of this <code>ControlElement</code>,
   * the one that is added to a container.
   */
  public Component getComponent() {
    return myVisual;
  }
  // This one is not final becuase, although this is the usual behaviour,
  // there are exceptions. F. i., when embedding the visual into a JScrollPane

  // ------------------------------------------------
  // Definition of Properties
  // ------------------------------------------------

  /**
   * Returns the list of all properties that can be set for this
   * ControlElement.
   * Subclasses that add properties should extend this table.
   * Order is crucial here: Both for the presentation in an editor (f.i. ViewElement)
   * and for the setValue() method.
   */
  // Important: Order is crucial!!! if you change the order of the properties
  // you must up date the constants at the beginning of this file accordingly!!!
  public ArrayList<String> getPropertyList() {
    if(myInfoList==null) {
      myInfoList = new ArrayList<String>();
      myInfoList.add("name");       //$NON-NLS-1$
      myInfoList.add("position");   //$NON-NLS-1$
      myInfoList.add("parent");     //$NON-NLS-1$
      myInfoList.add("enabled");    //$NON-NLS-1$
      myInfoList.add("visible");    //$NON-NLS-1$
      myInfoList.add("size");       //$NON-NLS-1$
      myInfoList.add("foreground"); //$NON-NLS-1$
      myInfoList.add("background"); //$NON-NLS-1$
      myInfoList.add("font");       //$NON-NLS-1$
      myInfoList.add("tooltip");    //$NON-NLS-1$
    }
    return myInfoList;
  }

  /**
   * Returns information about a given property.
   * Subclasses that add properties should extend this table.
   * <ll>
   *   <li> The first keyword is ALWAYS the type.
   *   <li> The keyword <b>CONSTANT</b> applies to properties that can not be
   *     changed using the setValue() methods
   *   <li> The keyword <b>BASIC</b> is used by Ejs to group properties to the left
   *     hand side of the property editor
   *   <li> The keyword <b>HIDDEN</b> is used by Ejs so that it does not display
   *     an entry in the editor field
   *  </ll>
   */
  // Order in the implementation is irrelevant.
  public String getPropertyInfo(String _property) {
    if(_property.equals("name")) {             //$NON-NLS-1$
      return "String         CONSTANT HIDDEN"; //$NON-NLS-1$
    }
    if(_property.equals("position")) {                   //$NON-NLS-1$
      return "Position       CONSTANT PREVIOUS HIDDEN "; //$NON-NLS-1$
    }
    if(_property.equals("parent")) {           //$NON-NLS-1$
      return "ControlElement CONSTANT HIDDEN"; //$NON-NLS-1$
    }
    if(_property.equals("enabled")) {         //$NON-NLS-1$
      return "boolean          BASIC HIDDEN"; //$NON-NLS-1$
    }
    if(_property.equals("visible")) {         //$NON-NLS-1$
      return "boolean          BASIC HIDDEN"; //$NON-NLS-1$
    }
    if(_property.equals("size")) {     //$NON-NLS-1$
      return "Dimension|Object BASIC"; //$NON-NLS-1$
    }
    if(_property.equals("foreground")) { //$NON-NLS-1$
      return "Color|Object     BASIC";   //$NON-NLS-1$
    }
    if(_property.equals("background")) { //$NON-NLS-1$
      return "Color|Object     BASIC";   //$NON-NLS-1$
    }
    if(_property.equals("font")) {     //$NON-NLS-1$
      return "Font|Object      BASIC"; //$NON-NLS-1$
    }
    if(_property.equals("tooltip")) {               //$NON-NLS-1$
      return "String           BASIC TRANSLATABLE"; //$NON-NLS-1$
    }
    return null;
  }

  /**
   * Checks if a value can be considered a valid constant value for a property
   * If not, it returns null, meaning the value can be considered to be
   * a GroupVariable or a primitive constant.
   * This method implements more cases than really needed for the base class.
   * This is in order to save repetitions in swing subclasses.
   * @param     String _property The property name
   * @param     String _value The proposed value for the property
   */
  public Value parseConstant(String _propertyType, String _value) {
    if(_value==null) {
      return null;
    }
    Value constantValue;
    if(_propertyType.indexOf("Alignment")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.alignmentConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Dimension")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.dimensionConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Layout")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.layoutConstant(((ControlContainer) this).getContainer(), _value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Orientation")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.orientationConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Placement")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.placementConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    if(_propertyType.indexOf("Point")>=0) { //$NON-NLS-1$
      constantValue = ConstantParser.pointConstant(_value);
      if(constantValue!=null) {
        return constantValue;
      }
    }
    return super.parseConstant(_propertyType, _value);
  }

  // ------------------------------------------------
  // Set and Get the values of the properties
  // ------------------------------------------------

  /**
   * Sets the value of the registered variables.
   * Subclasses with internal values should extend this
   * @param _index   A keyword index that distinguishes among variables
   * @param _value The object holding the value for the variable.
   */
  public void setValue(int _index, Value _value) {
    // System.out.println ("Setting property #"+_index+" to "+_value.toString());
    switch(_index) {
       case NAME :
         super.setValue(ControlElement.NAME, _value);
         getComponent().setName(_value.toString());                                                              // Added for WC on 051209
         break;
       case POSITION : {
         ControlElement parent = myGroup.getElement(getProperty("parent"));                                      //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).remove(this);
         }
         myPropertiesTable.put("position", _value.toString());                                                   //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).add(this);
         }
       }
       break;
       case PARENT : {
         ControlElement parent = myGroup.getElement(getProperty("parent"));                                      //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).remove(this);
         }
         parent = myGroup.getElement(_value.toString());
         if(parent==null) {
           if(!(this instanceof ControlWindow)) {
             System.err.println(getClass().getName()+" : Error! Parent <"+_value+"> not found for "+toString()); //$NON-NLS-1$ //$NON-NLS-2$
           }
         } else {
           if(parent instanceof ControlContainer) {
             ((ControlContainer) parent).add(this);
           } else {
             System.err.println(getClass().getName()+" : Error! Parent <"+_value+"> is not a ControlContainer"); //$NON-NLS-1$ //$NON-NLS-2$
           }
         }
       }
       break;
       case ENABLED :
         getVisual().setEnabled(_value.getBoolean());
         break;                                                                      // enabled
       case VISIBLE :
         getVisual().setVisible(_value.getBoolean());
         break;                                                                      // visible
       case SIZE :                                                                   // Size (myVisual is necessarily a JComponent)
         if(getComponent() instanceof JComponent) {
           Dimension size = (Dimension) _value.getObject();
           if((size.width==mySize.width)&&(size.height==mySize.height)) {
             return;                                                                 // Do not waste time
           }
           ((JComponent) getComponent()).setPreferredSize(mySize = size);
           if(this instanceof ControlContainer) {
             ((ControlContainer) this).getContainer().validate();
           }
           ControlElement parentElement = myGroup.getElement(getProperty("parent")); //$NON-NLS-1$
           if(parentElement!=null) {
             ((ControlContainer) parentElement).adjustSize();
           }
         }
         break;
       case FOREGROUND :                                                             // Foreground (not much time is wasted if the color is the same)
         if(_value.getObject() instanceof Color) {
           getVisual().setForeground((Color) _value.getObject());
         }
         break;
       case BACKGROUND :                                                             // Background
         if(_value.getObject() instanceof Color) {
           getVisual().setBackground((Color) _value.getObject());
         }
         break;
       case FONT :                                                                   // Font
         if(_value.getObject() instanceof Font) {
           getVisual().setFont((Font) _value.getObject());
         }
         break;
       case TOOLTIP :                                                                // Tooltip
         if(getVisual() instanceof JComponent) {
           ((JComponent) getVisual()).setToolTipText(_value.toString());
         }
         break;
       default :                                                                     // Do nothing. No inherited properties
    }
  }

  public void setDefaultValue(int _index) {
    // System.out.println ("Setting default value for property #"+_index);
    switch(_index) {
       case NAME :
         super.setDefaultValue(ControlElement.NAME);
         break;
       case POSITION : {
         ControlElement parent = myGroup.getElement(getProperty("parent"));          //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).remove(this);
         }
         myPropertiesTable.remove("position");                                       //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).add(this);
         }
       }
       break;
       case PARENT : {
         ControlElement parent = myGroup.getElement(getProperty("parent"));          //$NON-NLS-1$
         if((parent!=null)&&(parent instanceof ControlContainer)) {
           ((ControlContainer) parent).remove(this);
         }
       }
       break;
       case ENABLED :
         getVisual().setEnabled(true);
         break;
       case VISIBLE :
         getVisual().setVisible(true);
         break;
       case SIZE :                                                                   // Size (getComponent() is necessarily a JComponent)
         if(getComponent() instanceof JComponent) {
           ((JComponent) getComponent()).setPreferredSize(null);                     // ask the UI for the default
           if(this instanceof ControlContainer) {
             ((ControlContainer) this).getContainer().validate();
           }
           ControlElement parentElement = myGroup.getElement(getProperty("parent")); //$NON-NLS-1$
           if(parentElement!=null) {
             ((ControlContainer) parentElement).adjustSize();
           }
         }
         break;
       case FOREGROUND :
         getVisual().setForeground(myDefaultFrgd);
         break;
       case BACKGROUND :
         getVisual().setBackground(myDefaultBkgd);
         break;
       case FONT :
         getVisual().setFont(myDefaultFont);
         break;
       case TOOLTIP :
         if(getComponent() instanceof JComponent) {
           ((JComponent) getVisual()).setToolTipText(null);
         }
         break;
       default :
         break;
    }
  }

  /**
   * Gets the value of any internal variable.
   * Subclasses with internal values should extend this
   * @param _index   A keyword index that distinguishes among variables
   * @return Value _value The object holding the value for the variable.
   */
  public Value getValue(int _index) {
    return null; // Any of these properties can be modified by the element
  }

  // ------------------------------------------------
  // A utility for subclasses that require an icon
  // ------------------------------------------------
  protected javax.swing.ImageIcon getIcon(String _iconFile) {
    javax.swing.ImageIcon icon;
    if(getProperty("_ejs_codebase")!=null) {                      //$NON-NLS-1$
      icon = Utils.icon(getProperty("_ejs_codebase"), _iconFile); //$NON-NLS-1$
    } else if((getSimulation()!=null)&&(getSimulation().getCodebase()!=null)) {
      icon = Utils.icon(getSimulation().getCodebase().toString(), _iconFile);
    } else {
      icon = Utils.icon(null, _iconFile);
    }
    return icon;
  }

} // End of Class

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