/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008). * Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org). * This source code is released under a BSD License, which allows redistribution * in source and binary form, with or without modification, provided copyright * and license information are included, and with no warranty or guarantee of * any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt */ package vmm.core; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import vmm.actions.AbstractActionVMM; import vmm.conformalmap.UserConformalMap; import vmm.functions.ComplexFunction; import vmm.functions.Function; import vmm.functions.Function1; import vmm.functions.Function2; import vmm.functions.Function3; import vmm.functions.ParseError; import vmm.functions.Parser; import vmm.planecurve.parametric.UserPlaneCurveParametric; /** * A UserExhbit is meant to be an Exhibit whose data is computed by one * or more functions entered by the user. A UserExhibit class should be * declared as a subclass of some subclass of Exhibit and it should implement * the UserExhbit interface. All the methods specified by this interface, * except for {@link #getUserExhibitSupport()}, are already defined in the * Exhibit class and do not have to be reimplmented in the user exhibit * class. The user exhibit class should define an instance variable of * type {@link UserExhibit.Support}. It should create and customize * an object of that type in its contstructor, and should return that * object as the value of {@link #getUserExhibitSupport()}. *
User exhibit classes can be very simple. For examples, see * {@link UserPlaneCurveParametric} and {@link UserConformalMap}. *
Note that a user exhibits that is a subclass of {@link vmm.core3D.Exhibit3D}
* should implement {@link vmm.core3D.UserExhibit3D} instead of UserExhibit.
*/
public interface UserExhibit extends Parameterizable {
/**
* This method should return a non-null object that is created
* and customized in the constructor of the class that implements UserExhbit.
*/
public UserExhibit.Support getUserExhibitSupport();
/**
* This method is already defined in class {@link Exhibit} and will not
* ordinarily be redefined in a user exhibit class.
* @see Exhibit#getParameters()
*/
public Parameter[] getParameters();
/**
* This method is already defined in class {@link Exhibit} and will not
* ordinarily be redefined in a user exhibit class.
* @see Exhibit#getDefaultWindow()
*/
public double[] getDefaultWindow();
/**
* This method is already defined in class {@link Exhibit} and will not
* ordinarily be redefined in a user exhibit class.
* @see Exhibit#setDefaultWindow(double[])
*/
public void setDefaultWindow(double[] window);
/**
* This method is already defined in class {@link Exhibit} and will not
* ordinarily be redefined in a user exhibit class.
* @see Exhibit#getDefaultView()
*/
public View getDefaultView();
/**
* An object of type FunctionInfo holds information about one the functions, to be entered
* by the user, that compute the data for the user exhibit. An object of type
* FunctionInfo is created by the methods {@link UserExhibit.Support#addRealFunction(String, String, String[])}
* and {@link UserExhibit.Support#addComplexFunction(String, String, String[])}; it is not possible to
* create an object of this type directly.
* A FunctionInfo object contains either a real-valued function, of type
* {@link Function}, or a complex-valued function, of type {@link ComplexFunction},
* depending on which method created it.
*/
public static class FunctionInfo {
private Object function; // Will be either Function or ComplexFunction
final private boolean isComplex;
final private String name;
final private String[] argumentNames;
private String prompt;
private String definition;
private FunctionInfo(String name, String definition, String[] argumentNames, boolean isComplex) {
this.name = name;
this.definition = definition;
this.argumentNames = argumentNames;
this.isComplex = isComplex;
setPrompt(null);
}
/**
* Returns the value of the real-valued function contained in this FunctionInfo object,
* at a specified list of input arguments for the functions. This method should
* only be called for a FucntionInfo that was created by the
* method {@link UserExhibit.Support#addRealFunction(String, String, String[])}.
* @throws ClassCastException if the function is complex-valued, not real-valued
* @throws IllegalArgumentException if the number of arguments does not match the arity of the function
*/
public double realFunctionValue(double... arg) {
return ((Function)function).value(arg);
}
/**
* Returns the value of the complex-valued function contained in this FunctionInfo object,
* at a specified list of input arguments for the functions. This method should
* only be called for a FucntionInfo that was created by the
* method {@link UserExhibit.Support#addComplexFunction(String, String, String[])}.
* @throws ClassCastException if the function is real-valued, not complex-valued
* @throws IllegalArgumentException if the number of arguments does not match the arity of the function
*/
public Complex complexFunctionValue(Complex... arg) {
return ((ComplexFunction)function).value(arg);
}
/**
* Returns the value of the complex-valued function contained in this FunctionInfo object,
* at a specified list of input arguments for the functions. The arguments are specified
* by values of type double that give the real and imaginary parts of the complex arguments
* that are to be passed to the function. This method should
* only be called for a FucntionInfo that was created by the
* method {@link UserExhibit.Support#addComplexFunction(String, String, String[])}.
* That is, Note that the once the support object has been created, the system handles most
* of the details, such as showing the dialog box where the user will enter the
* definitions of the functions and saving the values of those functions when the
* user exhibit is saved to a settings file.
*/
public static class Support {
final private UserExhibit exhibit;
private ArrayList Note that, by default, the user can change the list of parameters.
* See {@link #setAllowNewParameters(boolean)}.
* @param p the non-null parameter to be made available for use in the function
* definitions. Note that the parameter can belong to the subclass
* {@link ComplexVariableParamAnimateable}; if that is the case, then the user will
* be able to specify upper and lower limits for morphing the parameter.
*/
public void addFunctionParameter(ComplexVariableParam p) {
functionParameters.add(p);
p.setOwner(exhibit);
}
/**
* Add a real-valued function to the user info. The user will be able to modify the definition
* of the function in a dialog box. A reference to the return value of this function should
* be saved; that reference is needed to compute the value of the function.
* @param name The name of the function, which will be used only in the label for the text field
* where the user enters the definition.
* @param definition The initial value for the defintion of the function. This can be modified
* by the user. The defintion will be processed by a {@link Parser} to produce the
* actual function.
* @param argumentName The names of the arguments of the function, such as the "x" and
* "y" in "f(x,y)". These arguments can be used in the definition of the function.
* @return A FunctionInfo object that will contain the function and that can be used to
* compute values of the function. (Note that the function is null just after this
* function is called.)
*/
public FunctionInfo addRealFunction(String name, String definition, String... argumentName) {
FunctionInfo f = new FunctionInfo(name, definition, argumentName, false);
functions.add(f);
return f;
}
/**
* Add a complex-valued function to the user info. The user will be able to modify the definition
* of the function in a dialog box. A reference to the return value of this function should
* be saved; that reference is needed to compute the value of the function.
* @param name The name of the function, which will be used only in the label for the text field
* where the user enters the definition.
* @param definition The initial value for the defintion of the function. This can be modified
* by the user. The defintion will be processed by a {@link Parser} to produce the
* actual complex-valued function.
* @param argumentName The names of the arguments of the function, such as the "z" and
* "w" in "f(z,w)". These arguments can be used in the definition of the function.
* @return A FunctionInfo object that will contain the function and that can be used to
* compute values of the function. (Note that the function is null just after this
* function is called.)
*/
public FunctionInfo addComplexFunction(String name, String definition, String... argumentName) {
FunctionInfo f = new FunctionInfo(name, definition, argumentName, true);
functions.add(f);
return f;
}
/**
* Returns the number of functions, real and complex, that have been added to this
* Support object. (This method is used by the system, but is not usually needed
* otherwise.)
*/
public int getFunctionCount() {
return functions.size();
}
/**
* Returns the FunctionInfo object for the i-th function that was added to
* this object. (This method is used by the system, but is not usually needed
* otherwise.)
*/
public FunctionInfo getFunctionInfo(int index) {
return functions.get(index);
}
/**
* Gets the list of parameters that can be used in the definitions of the functions.
* (These are not function arguments, but are other parameters.) The parameters
* were added either by the user or by {@link #addFunctionParameter(ComplexVariableParam)}
* or {@link #addFunctionParameter(VariableParam)}. (This method is used by the system,
* but is not usually needed otherwise.)
*/
public Parameter[] getFunctionParameters() {
Parameter[] array = new Parameter[functionParameters.size()];
functionParameters.toArray(array);
return array;
}
/**
* @see #setAllowChangeUserDataCommand(boolean)
*/
public boolean getAllowChangeUserDataCommand() {
return allowChangeUserDataCommand;
}
/**
* Sets the value of the allowChangeUserData property. The default value is true.
* When this property is true, a "Change User Data" command appears in the 3DXM Settings
* menu. This command calls up the dialog box where the user enters the data for the
* user exhibit. If this property is false, then that command is omitted and the
* user will not be able to change the user data once it has been created.
*/
public void setAllowChangeUserDataCommand(boolean allowChangeUserDataCommand) {
this.allowChangeUserDataCommand = allowChangeUserDataCommand;
}
/**
* @see #setAllowNewParameters(boolean)
*/
public boolean getAllowNewParameters() {
return allowNewParameters;
}
/**
* Sets the value of the allowNewParameters property. The default value is true.
* If this property is true, then the user will be able to add new parameters
* for use in function definitions and will also be able to remove existing parameters.
* "Add Parameter" and "Remove Parameter" buttons are added to the dialog box to
* implement this. If the value of the property if false, then the buttons are
* omitted from the dialog box, and the only parameters that are available for
* use in the functions are those added by {@link #addFunctionParameter(VariableParam)}
* and {@link #addFunctionParameter(ComplexVariableParam)}.
*/
public void setAllowNewParameters(boolean allowNewParameters) {
this.allowNewParameters = allowNewParameters;
}
/**
* @see #setShowWindow(boolean)
*/
public boolean getShowWindow() {
return showWindow;
}
/**
* Sets the value of the showWindow property. The default value is true. If
* the value is true, then a panel is added to the dialog box where the user can
* specify the xy-window (that is, the range of values that are visible in the
* view) for the exhibit. Setting the property to false removes this panel
* from the dialog box.
*/
public void setShowWindow(boolean showWindow) {
this.showWindow = showWindow;
}
/**
* This method initializes the user exhibit to the default values from its
* create dialog. It has the same effect as if {@link #showCreateDialog(Display)}
* were called and the user clicked "OK" without making any changes to the data
* in the dialog box, except that the dialog is not actually shown. This is
* used to initially load a user exhibit in the main program for 3D-XplorMath-J
* and in the 3DXM LauncherApplet; it will probably not be used otherwise.
* @return A view that is configured according to the default data, or null
* if an error occurs. There should not be an error, unless there is a bug
* in the user exhibit.
*/
public View defaults() {
View view = exhibit.getDefaultView();
Dialog dialog = createDialog(null,view,true);
boolean ok = dialog.getData();
if (!ok)
return null;
finish(dialog,view,true);
return view;
}
/**
* Shows the dialog box where the user enters the data for the exhibit.
* This method is called by the system just after an object of type UserExhibit
* is constructed. It will not ordinarily be used directly.
* @param display The display that serves as the parent component of the dialog box; can be null.
*/
public View showCreateDialog(Display display) {
View view = exhibit.getDefaultView();
Dialog dialog = createDialog(display,view,true);
boolean ok = dialog.showDialog();
if (!ok)
return null;
finish(dialog,view,true);
return view;
}
/**
* Shows the dialog box where the user enters the data for the exhibit.
* This method is called by the system in response to a "Change User
* Data" command. It will not ordinarily be used directly.
*/
public boolean showChangeDialog(Display display, View view) {
Dialog dialog = createDialog(display,view,false);
boolean ok = dialog.showDialog();
if (!ok)
return false;
finish(dialog,view,false);
return true;
}
/**
* Creates the "Change User Data" command for adding to the 3DXM Settings menu.
* This is called by the system and will not ordinarily be used directly.
*/
public AbstractActionVMM makeChangeUserDataAction(final View view) {
return new AbstractActionVMM(I18n.tr("vmm.core.UserExhibitDialog.SetUserData")) {
public void actionPerformed(ActionEvent evt) {
if ( showChangeDialog(view.getDisplay(), view) ) {
if (view.getExhibit() != null)
view.getExhibit().forceRedraw(); // ( probalby done automatically, but to be safe... )
}
}
};
}
/**
* This is called by the system when the user exhibit is being saved to a settings
* file to write the user data to the settings file. This method will not oridinarly
* be used directly.
*/
public void addToXML(Document containingDocument, Element userDataElement) {
for (Parameter param : functionParameters) {
Element paramElement = containingDocument.createElement("functionParam");
SaveAndRestore.buildParameterElement(containingDocument, paramElement, param);
paramElement.setAttribute("isComplex", param instanceof ComplexVariableParam? "yes" : "no");
paramElement.setAttribute("isAnimateable", param instanceof Animateable? "yes" : "no");
userDataElement.appendChild(paramElement);
}
for (FunctionInfo func : functions) {
Element functionElement = containingDocument.createElement("function");
functionElement.setAttribute("name", func.name);
functionElement.setAttribute("definition",func.definition);
userDataElement.appendChild(functionElement);
}
SaveAndRestore.addProperties(this,
new String[] { "allowChangeUserDataCommand", "showWindow", "allowNewParameters" },
containingDocument, userDataElement);
}
/**
* This method is called by the system when the user exhibit is being read
* from a settings file. This method will not ordinarily be called directly.
*/
public void readFromXML(Element userDataElement) throws IOException {
ArrayListcomplexFunctionValue(re1,im1,re2,im2...) is equivalent to
* complexFunction(new Complex(re1,im1), new Complex(re2,im2)...)Warning: Just after a FunctionInfo
* object has been created, the return value is null; it will only be non-null after
* the user has had a chance to enter the definition of the function. Furthermove,
* the function can change later, if {@link UserExhibit.Support#showChangeDialog(Display, View)}
* is called.} This means that you should ordinarily not keep a copy of the value returned by
* this method, and that there is generally little reason to use it. Note that
* if the arity of the fucntion is 1, 2, or 3, then the return value will actuall be
* of type {@link Function1}, {@link Function2}, or {@link Function3}.
* @throws ClassCastException if the function is complex-valued, not real-valued.
*/
public Function getRealFunction() {
return (Function)function;
}
/**
* Returns the complex-valued function contained in this FunctionInfo object, or returns
* null if no function has been created yet. Warning: Just after a FunctionInfo
* object has been created, the return value is null; it will only be non-null after
* the user has had a chance to enter the definition of the function. Furthermove,
* the function can change later, if {@link UserExhibit.Support#showChangeDialog(Display, View)}
* is called.} This means that you should ordinarily not keep a copy of the value returned by
* this method, and that there is generally little reason to use it. Note that
* if the arity of the fucntion is 1, 2, or 3, then the return value will actuall be
* of type {@link vmm.functions.ComplexFunction1}, {@link vmm.functions.ComplexFunction2}, or {@link vmm.functions.ComplexFunction3}.
* @throws ClassCastException if the function is real-valued, not complex-valued.
*/
public ComplexFunction getComplexFunction() {
return (ComplexFunction)function;
}
/**
* Returns the names of the arguments to the function, as specified in the parameter list
* of {@link UserExhibit.Support#addRealFunction(String, String, String[])} or
* {@link UserExhibit.Support#addComplexFunction(String, String, String[])} when this
* FunctionInfo object was created.
*/
public String[] getArgumentNames() {
return argumentNames;
}
/**
* Returns the names of the arguments to the function, as entered by the user.
*/
public String getDefinition() {
return definition;
}
/**
* Tells whether the function is complex-valued or real-valued.
*/
public boolean getIsComplex() {
return isComplex;
}
/**
* Returns the name of the function, as specified in the parameter list
* of {@link UserExhibit.Support#addRealFunction(String, String, String[])} or
* {@link UserExhibit.Support#addComplexFunction(String, String, String[])} when this
* FunctionInfo object was created.
*/
public String getName() {
return name;
}
/**
* Set the label to be used next to the text fiels in the dialog box where the user
* enters the definition of the fuction. By default, the prompt is of a form such
* as "f(x,y)" where "f" is the name of the function and "x" and "y" are the arguments
* of the function. You only need to use this method if you would like a different
* prompt.
*/
public void setPrompt(String prompt) {
if (prompt != null)
this.prompt = prompt;
else {
this.prompt = name;
this.prompt += "(";
if (argumentNames != null && argumentNames.length > 0) {
for (int i = 0; i < argumentNames.length; i++) {
this.prompt += argumentNames[i];
this.prompt += (i == argumentNames.length-1)? ") = " : ",";
}
}
}
}
}
/**
* An object of type UserExhibit.Support holds the information need for a user
* exhibit and provides some methods for manipulating that information. Most
* users of this class will only have to declare an instance variable of
* type UserExhibit.Support and create and customize the object in the constructor
* of the user exhibit class (saving references to the {@link UserExhibit.FunctionInfo}
* objects created by the support object). This Support object should be returned
* as the value of {@link UserExhibit#getUserExhibitSupport()}.
*