/*
* 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/>
*/
// Note 1 : comment "AMAVP" stands for "Accept methods as variable properties"
// stands for the BIG change when I first introduced
// expressions as possible values for the properties
package org.opensourcephysics.ejs.control;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import org.opensourcephysics.display.OSPRuntime;
import org.opensourcephysics.ejs.Simulation;
import org.opensourcephysics.ejs.control.value.BooleanValue;
import org.opensourcephysics.ejs.control.value.DoubleValue;
import org.opensourcephysics.ejs.control.value.ExpressionValue;
import org.opensourcephysics.ejs.control.value.IntegerValue;
import org.opensourcephysics.ejs.control.value.ObjectValue;
import org.opensourcephysics.ejs.control.value.StringValue;
import org.opensourcephysics.ejs.control.value.Value;
/**
* <code>ControlElement</code> is a base class for an object that
* can be managed using a series of configurable properties, hold actions
* that when invoked graphically call other objects' methods, and be
* responsible for the display and change of one or more internal variables.
* <p>
* <code>ControlElement</code>s can be included into a GroupControl,
* thus acting in a coordinated way.
* <p>
* In fact, the best way to use a <code>ControlElement</code>, is to include
* it into a GroupControl and then configure it using the
* <code>setProperty()</code> method.
* <p>
* After this, the value common to several of these ControlElements can be
* set and retrived using a single setValue() or getValue() call from the
* ControlGroup.
* <p>
* You can also add any action you want to a ControlElement, but it is the
* implementing class' responsability to trigger an action in response
* to a user's gesture (with the mouse or keyboard)
* <p>
* @see GroupControl
*/
public abstract class ControlElement {
protected GroupControl myGroup = null; // The group of ControlElements with which I share variables
protected Hashtable<String, String> myPropertiesTable = new Hashtable<String, String>(); // A place to hold any property
protected Object myObject = null; // The wrapped object
private boolean myActiveState = true; // Whether I am active or not
private Vector<MethodWithOneParameter> myActionsList = new Vector<MethodWithOneParameter>(); // My list of actions
private GroupVariable[] myProperties = null; // The variables for the registered properties
private String[] myPropertiesNames = null; // The names of the registered properties
protected boolean isUnderEjs = false;
MethodWithOneParameter[] myMethodsForProperties = null; // AMAVP
ExpressionValue[] myExpressionsForProperties = null; // AMAVP
// ------------------------------------------------
// Static constants and constructor
// ------------------------------------------------
public static final int NAME = 0; // The name of the element
public static final int ACTION = 0;
public static final int VARIABLE_CHANGED = 1;
public static final int METHOD_FOR_VARIABLE = 2; // AMAVP
public static final String METHOD_TRIGGER = "_expr_"; // AMAVP //$NON-NLS-1$
/**
* Constructor ControlElement
* @param _object
*/
public ControlElement(Object _object) {
// Create the list of registered properties
ArrayList<?> info = getPropertyList();
myObject = _object;
myPropertiesNames = new String[info.size()];
myProperties = new GroupVariable[info.size()];
myMethodsForProperties = new MethodWithOneParameter[info.size()]; // AMAVP
myExpressionsForProperties = new ExpressionValue[info.size()]; // AMAVP
for(int i = 0; i<info.size(); i++) {
String property = (String) info.get(i);
myPropertiesNames[i] = property;
myProperties[i] = null;
myMethodsForProperties[i] = null; // AMAVP
myExpressionsForProperties[i] = null;
}
}
public Object getObject() {
return myObject;
}
// ------------------------------------------------
// Definition of Properties
// ------------------------------------------------
/**
* Returns the list of all properties that can be set for this
* ControlElement.
* Subclasses that add properties should implement this.
* Order is crucial here: Both for the presentation in an editor
* (f.i. ViewElement) and for the setValue() method.
*/
public abstract ArrayList<String> getPropertyList();
/**
* Returns information about a given property.
* Subclasses that add properties should implement this.
* Order in the implementation is irrelevant.
* <ll>
* <li> The first keyword is ALWAYS the type. If more than one type is
* accepted, they are separated by | (do NOT use spaces!)
* <li> The keyword <b>CONSTANT</b> applies to properties that can not be
* changed using the setValue() methods
* <li> The keyword <b>VARIABLE_EXPECTED</b> is used when a String could be
* accepted, but a variable has priority. In this case, a String requires
* using inverted commas or quotes
* <li> The keyword <b>NotTrimmed</b> specifies that leading or trailing
* spaces must be respected when present. This is useful for labels or
* titles, for instance
* <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
* <li> The keywords <b>PREVIOUS</b> and <b>POSTPROCESS</b> indicate that,
* when setting several properties at once (using setProperties()) the
* property must be process before, resp. after, the others
* </ll>
*/
public abstract String getPropertyInfo(String _property);
/**
* 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
* @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("boolean")>=0) { //$NON-NLS-1$
constantValue = ConstantParser.booleanConstant(_value);
if(constantValue!=null) {
return constantValue;
}
}
if(_propertyType.indexOf("Color")>=0) { //$NON-NLS-1$
constantValue = ConstantParser.colorConstant(_value);
if(constantValue!=null) {
return constantValue;
}
}
if(_propertyType.indexOf("File")>=0) { //$NON-NLS-1$
String codebase = null;
if(getProperty("_ejs_codebase")!=null) { //$NON-NLS-1$
codebase = getProperty("_ejs_codebase"); //$NON-NLS-1$
} else if((getSimulation()!=null)&&(getSimulation().getCodebase()!=null)) {
codebase = getSimulation().getCodebase().toString();
}
if(Utils.fileExists(codebase, _value)) {
return new StringValue(_value);
}
}
if(_propertyType.indexOf("Font")>=0) { //$NON-NLS-1$
java.awt.Font currentFont = null;
if(getVisual()!=null) {
currentFont = getVisual().getFont();
}
constantValue = ConstantParser.fontConstant(currentFont, _value);
if(constantValue!=null) {
return constantValue;
}
}
if(_propertyType.indexOf("Format")>=0) { //$NON-NLS-1$
constantValue = ConstantParser.formatConstant(_value);
if(constantValue!=null) {
return constantValue;
}
}
if((_propertyType.indexOf("Margins")>=0)||(_propertyType.indexOf("Rectangle")>=0)) { //$NON-NLS-1$ //$NON-NLS-2$
constantValue = ConstantParser.rectangleConstant(_value);
if(constantValue!=null) {
return constantValue;
}
}
return null;
}
// ------------------------------------------------
// Set and Get the values of the properties
// ------------------------------------------------
/**
* Sets the value of the registered variables.
* Subclasses with internal values should extend this
* Order is crucial here: it must match exactly that of the getPropertyList()
* method.
* @param int _index A keyword index that distinguishes among variables
* @param Value _value The object holding the value for the variable.
*/
public void setValue(int _index, Value _value) {
switch(_index) {
case NAME :
if(myGroup!=null) {
myGroup.rename(this, _value.toString());
}
break;
}
}
public void setDefaultValue(int _index) {
switch(_index) {
case NAME :
if(myGroup!=null) {
myGroup.rename(this, null);
}
break;
}
}
/**
* Gets the value of any internal variable.
* Subclasses with internal values should extend this
* @param int _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;
}
// -------------------------------------------
// Methods that deal with properties
// -------------------------------------------
/**
* Sets a property for this <code>ControlElement</code>. Implementing
* classes are responsible of deciding (by declaring them in the
* getPropertyList() method) what properties turn into visual
* changes, or different behaviour, of the ControlElement.
* <p>
* However, every propery is accepted, even if it is not meaningful for a
* particular implementation of this interface. This can serve as a
* repository of information for future use.
* <p>
* Implementing classes should make sure that the following
* requirements are met:
* <ll>
* <li> Properties can be set in any order. The final result
* should not depend on the order. Exceptions must be
* explicitly documented.
* <li> Any property can be modified. If so, the old value,
* and whatever meaning it had, is superseded by the
* new one. If the new one is null, the old one is simply removed
* and setDefaultValue(index) is called in case a precise default
* value should be used.
* <li> When the element is part of a GroupControl, final users should
* not use this setProperty method directly, but go through the
* corresponding method of the group.
* </ll>
* @return This same element. This is useful to nest more
* than one call to <code>setProperty</code>
* @param String _property The property name
* @param String _value The value desired for the property
* @see GroupControl
*/
// This one is not final because a few of the subclasses
// (f. i. ControlContainer and ControlTrace) need to overwrite it
public ControlElement setProperty(String _property, String _value) {
_property = _property.trim();
if(_property.equals("_ejs_")) { //$NON-NLS-1$
isUnderEjs = true;
}
// Let's see if the proposed property is registered as a real property
int index = propertyIndex(_property);
if(index<0) {
// It is not a registered property. Store the value but do not call setValue()
if(_value==null) {
myPropertiesTable.remove(_property);
} else {
myPropertiesTable.put(_property, _value);
}
return this;
}
// The property is registered. Unregister and call setValue
myMethodsForProperties[index] = null; // AMAVP
myExpressionsForProperties[index] = null; // AMAVP
if(myProperties[index]!=null) { // remove from the list of listeners for this GroupVariable
myProperties[index].removeElementListener(this, index);
myProperties[index] = null;
}
if(_value==null) { // Treat the easy case separately, so that to avoid a lot of 'if (null)' checks
if(myProperties[index]!=null) { // remove from the list of listeners for this GroupVariable
myProperties[index].removeElementListener(this, index);
myProperties[index] = null;
}
setDefaultValue(index); // use a default value
myPropertiesTable.remove(_property); // remove the property
return this;
}
// From now on, the value is, necessarily, not null
// Some properties should not be trimmed ('text', for instance)
if(!propertyIsTypeOf(_property, "NotTrimmed")) { //$NON-NLS-1$
_value = _value.trim();
}
String originalValue = _value;
// Because of backwards compatibility with version 3.01 or earlier
// There might be confusion with constant strings versus variable names
// This is the reason for most of the following block
// From this version on, it is recommended that constant strings should be
// delimited by either ' or "
Value constantValue = null;
if(_value.startsWith("%")&&_value.endsWith("%")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
_value = _value.substring(1, _value.length()-1); // Force a variable or method
} else if(_value.startsWith("@")&&_value.endsWith("@")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
// empty // Do nothing for parsed expressions
} else if(_value.startsWith("#")&&_value.endsWith("#")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
// empty // Do nothing for variables such as f()
} else {
if(_value.startsWith("\"")||_value.startsWith("'")) { //$NON-NLS-1$ //$NON-NLS-2$
// empty // It IS a constant String, don't try anything else
} else {
// First look for a CONSTANT property that can not be associated to GroupVariables
if(propertyIsTypeOf(_property, "CONSTANT")) { //$NON-NLS-1$
constantValue = new StringValue(_value);
}
// Check for String properties
if(constantValue==null) {
if(propertyType(_property).equals("String")&&!propertyIsTypeOf(_property, "VARIABLE_EXPECTED")) { // See TextField f.i. //$NON-NLS-1$ //$NON-NLS-2$
constantValue = new StringValue(_value);
}
}
}
//
// End of the compatibility block
//
// Now try the particular parser
// The particular parser comes first because it can discriminate between
// a real String and a File, f.i.
if(constantValue==null) {
constantValue = parseConstant(propertyType(_property), _value);
}
// Finally the standard parser
if(constantValue==null) {
constantValue = Value.parseConstantOrArray(_value, true); // silentMode
}
}
if(constantValue!=null) { // Just set the value for this property
// System.out.println ("property = "+_property+" = "+_value+" of the element "+this.toString()+" is a constant!");
if((constantValue instanceof StringValue)&&propertyIsTypeOf(_property, "TRANSLATABLE") // Apply Translator //$NON-NLS-1$
&&(OSPRuntime.getTranslator()!=null)) { // added by D Brown 2007-10-17
Object target = null;
if(myGroup!=null) {
target = myGroup.getTarget("_default_"); //$NON-NLS-1$
}
String translated = OSPRuntime.getTranslator().getProperty(target.getClass(), constantValue.getString());
if(!constantValue.getString().equals(translated)) {
constantValue = new StringValue(translated);
}
}
setValue(index, constantValue);
} else { // Associate the property with a GroupVariable or Method for later use
// System.out.println (_value+" for the property "+_property+" of the element "+this.toString()+" is a variable!: "+originalValue);
// if (myProperties[index]!=null) { // remove from the list of listeners for this GroupVariable
// myProperties[index].removeElementListener(this,index);
// myProperties[index] = null;
// }
if(myGroup!=null) {
boolean isNormalVariable = true, isExpression = false;
if(_value.startsWith("#")&&_value.endsWith("#")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
_value = _value.substring(1, _value.length()-1);
isNormalVariable = true;
} else if(_value.startsWith("@")&&_value.endsWith("@")&&(_value.length()>2)) { //$NON-NLS-1$ //$NON-NLS-2$
_value = _value.substring(1, _value.length()-1);
originalValue = _value;
isNormalVariable = false;
isExpression = true;
} else if(_value.indexOf('(')>=0) {
isNormalVariable = false; // It mist be a method
}
// Begin --- AMAVP
if(isNormalVariable) { // Connect a variable property with a normal variable name
// This is what would normally happen under Ejs with expressions
Value newValue = null;
// If not under Ejs, get the actual value and use it when you register
// to the group. This is arguable...
if(getProperty("_ejs_")==null) { //$NON-NLS-1$
newValue = getValue(index);
}
if(newValue==null) {
// if (propertyIsTypeOf(_property,"[]")) newValue = new ObjectValue(null);
// else
if(propertyIsTypeOf(_property, "double")) { //$NON-NLS-1$
newValue = new DoubleValue(0.0);
} else if(propertyIsTypeOf(_property, "boolean")) { //$NON-NLS-1$
newValue = new BooleanValue(false);
} else if(propertyIsTypeOf(_property, "int")) { //$NON-NLS-1$
newValue = new IntegerValue(0);
} else if(propertyIsTypeOf(_property, "String")) { //$NON-NLS-1$
newValue = new StringValue(_value);
} else {
newValue = new ObjectValue(null);
}
}
myProperties[index] = myGroup.registerVariable(_value, this, index, newValue);
} else if(isExpression) { // Connect a variable property to an expression
String returnType = null;
if(propertyIsTypeOf(_property, "double")) { //$NON-NLS-1$
returnType = "double"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "boolean")) { //$NON-NLS-1$
returnType = "boolean"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "int")) { //$NON-NLS-1$
returnType = "int"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "String")) { //$NON-NLS-1$
returnType = "String"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "Action")) { //$NON-NLS-1$
returnType = "Action"; //$NON-NLS-1$
} else {
System.out.println("Error for property "+_property+" of the element "+this.toString() //$NON-NLS-1$ //$NON-NLS-2$
+". Cannot be set to : "+originalValue); //$NON-NLS-1$
myPropertiesTable.put(_property, originalValue);
return this;
}
if(!returnType.equals("Action")) { //$NON-NLS-1$
myExpressionsForProperties[index] = new ExpressionValue(_value, myGroup);
myGroup.methodTriggerVariable.addElementListener(this, index);
myProperties[index] = myGroup.methodTriggerVariable;
}
} else { // Connect a variable property to a method
// System.out.println ("Connecting property "+_property+" to method '"+_value+"' for the element "+this.toString());
// Under Ejs do something reasonable.
// For instance Labels need a string to size themselves properly
if(getProperty("_ejs_")!=null) { // Do nothing in Ejs //$NON-NLS-1$
// System.out.println ("Under ejs");
} else {
String returnType = null;
if(propertyIsTypeOf(_property, "double")) { //$NON-NLS-1$
returnType = "double"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "boolean")) { //$NON-NLS-1$
returnType = "boolean"; //$NON-NLS-1$
} else if(propertyIsTypeOf(_property, "int")) { //$NON-NLS-1$
returnType = "int"; //$NON-NLS-1$
// else if (propertyIsTypeOf(_property,"byte")) returnType = "byte";
} else if(propertyIsTypeOf(_property, "String")) { //$NON-NLS-1$
returnType = "String"; //$NON-NLS-1$
} else {
System.out.println("Error for property "+_property+" of the element "+this.toString() //$NON-NLS-1$ //$NON-NLS-2$
+". Cannot be set to : "+originalValue); //$NON-NLS-1$
myPropertiesTable.put(_property, originalValue);
return this;
}
// Resolve for non-default target
String[] parts = MethodWithOneParameter.splitMethodName(_value);
if(parts==null) {
System.err.println(getClass().getName()+" : Error! method <"+originalValue+"> not found"); //$NON-NLS-1$ //$NON-NLS-2$
myPropertiesTable.put(_property, originalValue);
return this;
}
if(parts[0]==null) {
parts[0] = "_default_"; //$NON-NLS-1$
}
Object target = myGroup.getTarget(parts[0]);
if(target==null) {
System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
myPropertiesTable.put(_property, originalValue);
return this;
}
if(parts[2]==null) {
_value = parts[1]+"()"; //$NON-NLS-1$
} else {
_value = parts[1]+"("+parts[2]+")"; //$NON-NLS-1$ //$NON-NLS-2$
}
myMethodsForProperties[index] = new MethodWithOneParameter(METHOD_FOR_VARIABLE, target, _value, returnType, null, this); // Pass the element itself Jan 31st 2004 Paco
// Register the property of this element to a standard boolean (why not?) variable
// myGroup.update() will take care of triggering the method
myGroup.methodTriggerVariable.addElementListener(this, index);
myProperties[index] = myGroup.methodTriggerVariable;
// myProperties[index] = myGroup.registerVariable (METHOD_TRIGGER,this,index,new BooleanValue(false));
} // End of the real part, i.e. not under Ejs
} // End --- AMAVP
}
}
myPropertiesTable.put(_property, originalValue);
return this;
}
/**
* Sets more than one property at once. The pairs
* <code>property=value</code> must be separated by ';'.
* If any value has a ';' in it, then it must be set
* in a separate <code>setProperty</code> call.
* @return This same element. This is useful to nest more
* than one call to <code>setProperties</code>
* @param String _propertyList The list of properties and Values
* to be set
*/
final public ControlElement setProperties(String _propertyList) {
Hashtable<String, String> propTable = new Hashtable<String, String>();
StringTokenizer tkn = new StringTokenizer(_propertyList, ";"); //$NON-NLS-1$
while(tkn.hasMoreTokens()) {
String token = tkn.nextToken();
if(token.trim().length()<=0) {
continue;
}
int index = token.indexOf("="); //$NON-NLS-1$
if(index<0) {
System.err.println(getClass().getName()+" : Error! Token <"+token+"> invalid for "+toString()); //$NON-NLS-1$ //$NON-NLS-2$
} else {
propTable.put(token.substring(0, index).trim(), token.substring(index+1));
}
}
return setProperties(propTable);
}
// This is neccesary just to make sure that some properties are processed
// first and some others (such as 'value') last
private void preprocess(String _property, Hashtable<String, String> _propertyTable) {
String value = _propertyTable.get(_property);
if(value!=null) {
setProperty(_property, value);
_propertyTable.remove(_property);
}
}
private ControlElement setProperties(Hashtable<String, String> _propertyTable) {
// _ejs_ is used by Ejs to signal that the element is working under it.
// This has some consequences in the behaviour of some properties
// (f.i. 'exit' on a Frame will not exit the application)
preprocess("_ejs_", _propertyTable); //$NON-NLS-1$
Hashtable<String, String> postTable = new Hashtable<String, String>();
for(Enumeration<String> e = _propertyTable.keys(); e.hasMoreElements(); ) {
String key = e.nextElement();
// Some need to be processed before the others
if(propertyIsTypeOf(key, "PREVIOUS")) { //$NON-NLS-1$
preprocess(key, _propertyTable);
// And some need to be the last ones
} else if(propertyIsTypeOf(key, "POSTPROCESS")) { //$NON-NLS-1$
String value = _propertyTable.get(key);
_propertyTable.remove(key);
postTable.put(key, value);
}
}
// Process the normal ones
for(Enumeration<String> e = _propertyTable.keys(); e.hasMoreElements(); ) {
String key = e.nextElement();
setProperty(key, _propertyTable.get(key));
}
// Finally proccess those which need to be the last ones
for(Enumeration<String> e = postTable.keys(); e.hasMoreElements(); ) {
String key = e.nextElement();
setProperty(key, postTable.get(key));
}
return this;
}
/**
* Returns the value of a property.
* @param String _property The property name
*/
final public String getProperty(String _property) {
return myPropertiesTable.get(_property);
}
/**
* Returns wether a property information contains a given keyword in its preamble
* @param String _property The property name
* @param String _keyword The keyword to look for
*/
final public boolean propertyIsTypeOf(String _property, String _keyword) {
String info = getPropertyInfo(_property);
if(info==null) {
return false;
}
if(info.toLowerCase().indexOf(_keyword.toLowerCase())>=0) {
return true;
}
return false;
}
/**
* Returns the type of the property
* @param String _property The property name
* @return String The type of the property
*/
final public String propertyType(String _property) {
String info = getPropertyInfo(_property);
if(info==null) {
return "double"; //$NON-NLS-1$
}
StringTokenizer tkn = new StringTokenizer(info, " "); //$NON-NLS-1$
if(tkn.countTokens()>=1) {
return tkn.nextToken();
}
return "double"; //$NON-NLS-1$
}
/**
* Provided for backwards compatibiliy only
*/
public java.awt.Component getComponent() {
return null;
}
/**
* Provided for backwards compatibiliy only
*/
public java.awt.Component getVisual() {
return null;
}
/**
* resets the element
*/
public void reset() {}
/**
* initializes the element. A kind of soft reset()
*/
public void initialize() {}
/**
* refresh the element
*/
// final public void update() { } Moved to interface NeedsUpdate
/**
* Returns the integer index of a given variable property
*/
private int propertyIndex(String _property) {
if(myPropertiesNames!=null) {
for(int i = 0; i<myPropertiesNames.length; i++) {
if(myPropertiesNames[i].equals(_property)) {
return i;
}
}
}
return -1;
}
/**
* Whether the element implements a given property
* @param _property the property
*/
public boolean implementsProperty(String _property) {
return(propertyIndex(_property)>=0);
}
/**
* Clear all registered internal variable properties
*/
final public void variablePropertiesClear() {
if(myPropertiesNames!=null) {
for(int i = 0; i<myPropertiesNames.length; i++) {
setProperty(myPropertiesNames[i], null);
}
}
}
/**
* Reports its name, if it has been set. If not, returns
* a standard value.
*/
public String toString() {
String name = myPropertiesTable.get("name"); //$NON-NLS-1$
if(name!=null) {
return name;
}
String text = this.getClass().getName();
int index = text.lastIndexOf("."); //$NON-NLS-1$
if(index>=0) {
text = text.substring(index+1);
}
return "Unnamed element of type "+text; //$NON-NLS-1$
}
/**
* Clears any trace of myself (specially in the group)
*/
public void destroy() {
setProperty("parent", null); //$NON-NLS-1$
if(myProperties!=null) {
for(int i = 0; i<myProperties.length; i++) {
if(myProperties[i]!=null) {
myProperties[i].removeElementListener(this, i);
}
}
}
}
// ------------------------------------------------
// Actions
// ------------------------------------------------
/**
* Defines a generic action that can be invoked from this
* <code>ControlElement</code>. It is the responsability of implementing
* classes to decide what actions types can be invoked and how.
* <p>
* If the method field is not a valid method for this target object
* it will ignore the command (and perhaps print an error message).
* <p>
* @return This same element. This is useful to nest it with
* other calls to <code>setProperty</code> or <code>adAction</code>.
* @param int _type The action type
* @param Object _target The object whose method will be invoked
* @param String _method The method to call in the target object.
* The method can accept a single CONSTANT parameter, either boolean, int,
* double or String. See MethodWithOneParameter for more details.
*/
final public ControlElement addAction(int _type, Object _target, String _method) {
myActionsList.addElement(new MethodWithOneParameter(_type, _target, _method, null, null, this)); // null = void, null = no2nd action
return this;
}
/**
* This is an advanced form of addAction that allows for nested actions
*/
final public ControlElement addAction(int _type, Object _target, String _method, MethodWithOneParameter _secondAction) {
myActionsList.addElement(new MethodWithOneParameter(_type, _target, _method, null, _secondAction, this)); // null = void
return this;
}
/**
* Similar to the other addAction but extracts the target from the method,
* which must be of the form 'target.method:optional parameter', where
* target has been previously added to the list of targets of the group.
*/
final public ControlElement addAction(int _type, String _method) {
// A special entry point for Ejs
if(getProperty("_ejs_")!=null) { //$NON-NLS-1$
_method = "_ejs_.execute(\""+_method+"\")"; //$NON-NLS-1$ //$NON-NLS-2$
}
Object target = null;
MethodWithOneParameter secondAction = null;
String parts[] = MethodWithOneParameter.splitMethodName(_method);
if(parts==null) {
System.err.println(getClass().getName()+" : Error! Method <"+_method+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
return this;
}
if(parts[0]==null) {
parts[0] = "_default_"; //$NON-NLS-1$
}
if(myGroup!=null) {
target = myGroup.getTarget(parts[0]);
// Only ACTIONs can have a second ACTION
if((_type==ACTION)&&(getProperty("_ejs_SecondAction_")!=null)&&(myGroup.getTarget("_default_")!=null)) { //$NON-NLS-1$ //$NON-NLS-2$
secondAction = new MethodWithOneParameter(_type, myGroup.getTarget("_default_"), //$NON-NLS-1$
getProperty("_ejs_SecondAction_"), null, null, this); // null = void , null= no 2nd action //$NON-NLS-1$
}
}
if(target==null) {
System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
return this;
}
if(parts[2]==null) {
return addAction(_type, target, parts[1]+"()", secondAction); //$NON-NLS-1$
}
return addAction(_type, target, parts[1]+"("+parts[2]+")", secondAction); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Removes an action. If the action does not exists, it does nothing.
* <p>
* @param int _type The action type
* @param Object _target The object whose method will be invoked
* @param String _method The method to call in the target object.
* @see addAction(int,Object,String)
*/
final public void removeAction(int _type, Object _target, String _method) {
if(_method==null) {
return;
}
for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
MethodWithOneParameter meth = e.nextElement();
if(meth.equals(_type, _target, _method)) {
if(!myActionsList.removeElement(meth)) {
System.err.println(getClass().getName()+": Error! Action "+_method+" not removed"); //$NON-NLS-1$ //$NON-NLS-2$
}
return;
}
}
}
/**
* Similar to removeAction but extracts the target from the method
*/
final public void removeAction(int _type, String _method) {
if(_method==null) {
return;
}
// A special entry point for Ejs
if(getProperty("_ejs_")!=null) { //$NON-NLS-1$
_method = "_ejs_.execute(\""+_method+"\")"; //$NON-NLS-1$ //$NON-NLS-2$
}
String parts[] = MethodWithOneParameter.splitMethodName(_method);
if(parts==null) {
System.err.println(getClass().getName()+" : Error! Method <"+_method+"> not removed"); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
if(parts[0]==null) {
parts[0] = "_default_"; //$NON-NLS-1$
}
Object target = null;
if(myGroup!=null) {
target = myGroup.getTarget(parts[0]);
}
if(target==null) {
System.err.println(getClass().getName()+" : Error! Target <"+parts[0]+"> not assigned"); //$NON-NLS-1$ //$NON-NLS-2$
return;
}
removeAction(_type, target, parts[1]+"("+parts[2]+")"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Invokes all actions of type ACTION
*/
final public void invokeActions() {
invokeActions(ControlElement.ACTION);
}
/**
* Invokes all actions of this BasicControl of a given type
* @param int _type The action type
*/
final public void invokeActions(int _type) {
if(myActiveState) {
for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
(e.nextElement()).invoke(_type, this);
}
}
// Next line (for _model actions only!) would make unnecesary the trick of the secondActions!
// I still use this choice because otherwise a button that calls simulation.step() would update twice
// if (myGroup!=null && myGroup.getSimulation()!=null) myGroup.getSimulation().update();
}
/**
* Reports changes of internal variables but simulation doesn't update
* Needed by RadioButtons
* @param int _variableIndex the index of the internal variable that changed
* @param Value _value the new value for the variable
*/
final public void variableChangedDoNotUpdate(int _variableIndex, Value _value) {
// Changing the order of next two sentences is important!!!!
if((myGroup!=null)&&(myProperties!=null)) {
myGroup.variableChanged(myProperties[_variableIndex], this, _value);
}
if(myActiveState) {
for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
MethodWithOneParameter method = e.nextElement();
method.invoke(ControlElement.VARIABLE_CHANGED, this);
}
}
}
/**
* Reports changes of internal variables
* @param int _variableIndex the index of the internal variable that changed
* @param Value _value the new value for the variable
*/
final public void variableChanged(int _variableIndex, Value _value) {
if(myMethodsForProperties[_variableIndex]!=null) { // AMAVP
// System.out.println ("Do not update because of method "+myMethodsForProperties[_variableIndex].toString());
return;
}
variableChangedDoNotUpdate(_variableIndex, _value);
// Next line should apply only to model actions, but these are the only ones expected.
// Assigning a slider a simulation.pause() would trigger a lot of update()s!
if((myGroup!=null)&&(myGroup.getSimulation()!=null)) {
myGroup.getSimulation().update();
}
}
/**
* Reports changes of more than one internal variables
* @param int[] _variableIndexes the indexes of the internal variables that changed
* @param Value[] _value the new values for the variables
*/
final public void variablesChanged(int[] _variableIndex, Value[] _value) {
boolean doMore = false;
if((myGroup!=null)&&(myProperties!=null)) {
for(int i = 0; i<_variableIndex.length; i++) {
if(myMethodsForProperties[_variableIndex[i]]==null) { // AMAVP
// System.out.println ("Do not update this one because of method "+myMethodsForProperties[_variableIndex[i]].toString());
myGroup.variableChanged(myProperties[_variableIndex[i]], this, _value[i]);
doMore = true;
}
}
}
if(!doMore) {
return; // AMAVP Nothing has changed
}
if(myActiveState) {
for(Enumeration<MethodWithOneParameter> e = myActionsList.elements(); e.hasMoreElements(); ) {
MethodWithOneParameter method = e.nextElement();
method.invoke(ControlElement.VARIABLE_CHANGED, this);
}
}
if((myGroup!=null)&&(myGroup.getSimulation()!=null)) {
myGroup.getSimulation().update();
}
}
/**
* Sets whether a <code>ControlElement</code> actually invokes actions.
* The default is true.
* @param boolean _active Whether it is active
*/
final public void setActive(boolean _act) {
myActiveState = _act;
}
/**
* Returns the active status of the <code>ControlElement</code>.
*/
final public boolean isActive() {
return myActiveState;
}
// ------------------------------------------------
// Group behavior
// ------------------------------------------------
/**
* Sets the GroupControl in which to operate
* @param GroupControl _group The GroupControl
*/
final public void setGroup(GroupControl _group) {
myGroup = _group;
}
/**
* Gets the GroupControl in which it operates
* @return the GroupControl
*/
final public GroupControl getGroup() {
return myGroup;
}
/**
* Gets the Simulation in which it runs
* @return the Simulation
*/
final public Simulation getSimulation() {
if(myGroup==null) {
return null;
}
return myGroup.getSimulation();
}
} // 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
*/