JCM Version 1.0
This page discusses the more useful Java classes in the package edu.hws.jcm.awt, which is part of the JCM project. The full API can be found in the JavaDoc documentation.
Panel is a Java AWT component that can contain other components, including other panels. JCMPanel is a subclass of Panel for use in JCM applications. If a graphical user interface is built entirely from JCMPanels, some of the necessary JCM setup will be done automatically. Typically, the code for creating the GUI would look something like this:setLayout(new BorderLayout()); // Set layout for the applet as a whole. JCMPanel mainPanel = new JCMPanel(); add(mainPanel, BorderLayout.CENTER); // The JCMPanel fills the entire applet. . . // Fill mainPanel with sub-JCMPanel's and other components. . mainPanel.gatherInputs(); // Make sure applet will respond to user actions.
The default constructor for the JCMPanel sets its layout manager to be "new BorderLayout(3,3)". There are a few additional constructors that assign other layout managers. The gatherInputs() method that is called at the end ensures that the applet will be updated in response to most types of user actions: pressing return in a VariableInput or ExpressionInput, adjusting a VariableSlider, clicking a ComputeButton, and mouse interaction with InputObjects such as DraggablePoints and Panners in DisplayCanvases. Animators will also work properly. There are a few exceptions to the automatic setup that you will still have to handle by hand, if you want to take advantage of certain features. To do the manual part of the setup, you need the Controller object that is part of every JCMPanel:Controller mainController = mainPanel.getController();
The things that you will have to do by hand include: Setting an error reporter for the controller, adding Tie objects to the controller, setting the controller to respond to arbitrary text changes in VariableInputs and ExpressionInputs rather than just carriage returns, setting the Controller to respond to ComputeButtons, and responding to extra components that have been added to LimitControlPanels. All this is discussed in more detail elsewhere.
Behind the scenes, when one JCMPanel is added as a sub-panel of another JCMPanel, the controller in the main panel also becomes the parent of the controller in the sub-panel. This means that the sub-panel will automatically be updated whenever the controller in the main panel does an update. Also, errors that occur in the subpanel are passed up to the main controller to be reported. Essentially, you don't even have to worry about the fact that there is more than one controller. However, you should know that if you use a Panel as a sub-panel instead of a JCMPanel, then the components in the Panel will not be handled automatically. This fact is useful occasionally, if you want to exclude part of your interface from the automatic setup.
Not that there is no rule that says you have to use JCMPanels at all. You can do the interface without them and do all the required setup by hand. This can increase efficiency, but the fact is that on today's fast computers the change will often be unnoticeable.
class Controller and interfaces InputObject and Computable
The task of updating the JCM components in an application when necessary is handled by one or more Controller objects. Three types of objects can be added to a Controller (by calling its add(Object) method): InputObjects, Computables, and Ties. Since the Controller class implements both the InputObject and Computable interfaces, one Controller can be added to another Controller. Effectively, though not physically, this adds all the objects in the child Controller to the parent Controller. This allows for hierarchically organization: You can have a subcontroller that updates only a part of the interface while a parent controller updates everything, including the things in the subcontroller. So the main point of having subcontrollers is efficiency: you can recompute part of the application without recomputing the whole thing.
The Computable interface defines just one method: compute(). It is implemented by objects that can depend on other objects and so might have to be recomputed when those other objects change. When a Computable's compute() compute method is called, it is a signal that it should recompute itself.
An InputObject is an object that has an associated value that can change because of user interaction (or, in the case of an Animator, spontaneously). The value could be a number, as in the case of a VariableInput, or some other type of object, such as an Expression in the case of an ExpressionInput. The InputObject interface defines the checkInput() method. When this is called, the InputObject updates the associated value, if necessary, to conform to its current content or state. Now, this can get confusing: The rule is that the value of an InputObject should only change when its checkInput() method is called. When you type in a VariableInput, that doesn't automatically make the value of the variable change. It only changes when the VariableInput's checkInput() method is called and the VariableInput notices the change in its content. (This turned out to be the only way that I could make sure that various object interdependencies are handled properly.)
When a Controller's compute() method is called, it first calls the checkInput() method of each InputObject that has been added to itself or to any of its subcontrollers. It then processes Tie's, which are described below. Finally, it calls the compute() method of the Computable objects in itself and its subcontrollers. The effect of all this should be to correctly update that part of the application controlled by the controller.
If any error occurs during the computation, the process is stopped and the Controller reports the error. By default, it just prints an error message to standard output, which is almost never useful, so you should set an ErrorReporter for the main controller of your application, using the setErrorReporter(ErrorReporter) method. (This error reporter will also be used by sub-controllers of the main controller.) Usually, the ErrorReporter will be a DisplayCanvas, but it could also be a member of the MessagePopup class, which will report the error in a separater window or dialog.
So, to function properly, InputObjects and Computables should be added to Controllers. (This is the part that is done automatically if you simply build your interface out of JCMPanels.) However -- and this is another tricky bit -- you still have to arrange for the Controllers' compute() method to be called at appropriate times. Generally, this is done in response to some user interaction with an InputObject. You have to tell the InputObject to notify the controller when such interaction occurs. This is a separate issue from adding the InputObject to the Controller, so you generally have to do two things with an InputObject: add it to a Controller, and set a Controller to respond to user interactions. For example:VariableInput var = new VariableInput(); controller.add(var); // controller will call var's checkInput() var.setOnUserAction(controller); // var will call controller's compute()
The last line is not always required -- you might, for example, only want the controller to be called when the user clicks a certain button. In that case, only the button would have to be set to inform the controller when it is clicked. (Yes, I regret this complexity, but I haven't found a better way to do things.) By the way, the gatherInputs() method in class JCMPanel is what does the second part of the setup for InputObjects in the panel.
(If you ever decide to write your own InputObject, there is one other complication that you need to know about: When the checkInput() method is called, object values are in a state of flux, so if the InputObjects depends on other InputObjects or Computables, it can't use their values during the call to checkInput(). The solution is always to use "lazy" evaluation: The checkInput() method simply sets a flag that says that the value needs to be recomputed. Any methods that access the value associated with the object should test this flag and recompute the value if necessary. The value itself should, of course, be declared private so that it can only be accessed by accessor methods. By the time the value is actually accessed and recomputed, the checkInput phase should be finished and the compute phase underway. Logically, then, by the beginning of the compute phase, any access to any object will produce the correct, updated value.)
An ExpressionInput is an InputObject where the user can type a mathematical expression. The recommended constructor, ExpressionInput(String,Parser), specifies the initial content of the input box as a String and provides a Parser that will be used to parse the textual content of the input box into an object of type Expression. This means, for example, that Variables that have been added to the Parser can be used in the expression. The default constructor produces an initially empty input box and creates a new, default parser. (The parser can be changed later by calling the setParser() method.)
ExpressionInput implements the Value interface. Its value is the current value of the expression in the box -- possibly "undefined" if the expression has a syntax error. You can also get the associated Expression object by calling the getExpression() method. Note that the Expression returned by this method is a full-fledged Expression. For example, if you take its derivative, the derivative Expression will always be in sync with the current value of the expression.
Sometimes you want to use an ExpressionInput to input the definition of a function. The method getFunction(Variable v) returns the Function that is defined by the expression in the input box, considered as a function of the variable v. This variable should, of course, be one that has been added to the parser that is associated with the ExpressionInput. (The return value is declared to be of type Function, but is in fact a member of the class SimpleFunction.) You can also get a function of several variables by calling the getFunction(Variable) method.
When the checkInput() method is called, the contents of the text input box are parsed. If a syntax error is found, the value of the expression becomes equal to the special "undefined" value (Double.NaN). By default, an exception will also be thrown which can be detected and reported by the Controller that called the checkInput() method. However, the exception is not always desirable. If you call the setThrowErrors(boolean) method with argument false, then no exception will be thrown when a syntax error is found.
An ExpressionInput should be added to a Controller (either automatically or by hand), and it should generally be set to inform a Controller when the user interacts with it. The setOnUserAction(Controller) method tells the ExpressionInput to call the Controller's compute() method when the user presses return in the input box. The setOnTextChange(Controller) method tells the ExpressionInput to call the Controller's compute() method whenever the text in the input box changes. In this case, the application will be updated continuously as the user types. Since it is unlikely that you would want to generate a continual stream of error messages, the throwErrors property of the ExpressionInput is automatically set to false when setOnTextChange() is called with a non-null argument.
The ExpressionInput class is a subclass of the standard AWT class TextField, so all the usual TextField methods are also available, including setText(String), setEnabled(boolean), and setEditable(boolean).
A VariableInput is an InputObject that lets the user type in a value. Like ExpressionInput, VariableInput is a subclass of TextField. The VariableInput class implements the Value interface, so a VariableInput can be used directly wherever a Value is required. A VariableInput also has an associated Variable that represents its value. It can be retrieved by calling the getVariable() method. Setting the value of this Variable will change the contents of the input box. This variable can be added to a Parser as usual (provided it has been given a name).
The most useful constructor for VariableInput is VariableInput(String,String), where the first parameter gives a name for the VariableInput and for its associated Variable, and the second parameter gives the initial contents of the input box. The default constructor produces an unnamed Variable and uses "0" as the initial content. An unnamed Variable is fine if you just want to use the VariableInput as a Value.
VariableInput shares the methods setOnUserAction(Controller), setOnTextChange(Controller), and setThrowErrors(boolean) with class ExpressionInput. They are discussed above.
It's possible to set maximum and/or minimum allowed values for the number in the VariableInput, using the methods getMin(double) and getMax(double). A value outside the specified range will be considered an error. An exception will be thrown when checkInput() is called, unless the throwErrors property has been set to false. (Note, of course, that there can also be syntax errors.)
By default, the value in a VariableInput can be given as a constant expression, such as sqrt(2) or pi/3. Call setInputStyle(VariableInput.REAL) if you want to restrict the value to be an actual real number. Call setInputStyle(VariableInput.INTEGER) if you want the value to be restricted to be an integer.
A VariableSlider is an InputObject that lets the user change its value by adjusting a slider (actually a Scrollbar). A VariableSlider is a Value, and so can be used wherever a Value is needed. It also has an associated Variable whose value corresponds to the value of the slider. The Variable can be retrieved by calling the getVariable() method. A VariableSlider always has a maximum possible value and a minimum possible value, which can be given as Value objects. (They could, for example, be given by VariableInputs or even other VariableSliders.) The default contstuctor creates an unnamed VariableSlider with minimum value -5 and maximum value 5. This range of values is divided into 1000 subintervals, so that the VariableSlider can take on 1001 different values in the range. The constructor VariableSlider(Value,Value) produces an unnamed slider whose minimum and maximum values are given by the specified Value objects. (A name could be set later by calling the setName(String) method.) The most general constructor isVariableSlider(String name, Value min, Value max, Parser p, int intervals, int orientation)
This allows you to name the variable, register it with the parser, and specify the number of sub-intervals into which the range of values is subdivided. The final parameter should be either Scrollbar.HORIZONTAL or Scrollbar.VERTICAL and specifies the orientation of the scrollbar.
You can use the setMin(Value) and setMax(Value() methods at any time to set the maximum and minimum values. Use setOnUserAction(Controller) to tell the VariableSlider to call the Controller's compute() method whenever the user moves the slider. If you call setIntegerValued(boolean) with parameter true, then the value of the slider will be forced to be an integer.
An Animator is an InputObject that can be used for animation. It has an associated value that changes as the animation proceeds. Other objects in an application can use this value -- this is the only way an Animator has of affecting other components. The Animator can itself be used as a Value, and it has an associated Variable that can be retrieved by calling the getValueAsVariable() method. This variable could be given a name and added to a Parser. Like other InputObjects, an Animator should be added to a Controller, either automatically or by hand. It should also be set to notify a Controller when its value changes. This can be done by calling its setOnChange(Controller) method (or it can be left up to the gatherInputs() method of a JCMPanel).
By default, an Animator appears as a "Start" button. When the user clicks this, it changes to a "Stop" button, and the Animator starts changing its value in the sequence 0, 1, 2, 3, ..... This continues until the user clicks "Stop". However, things can get much more complicated. See the JavaDoc documentation.
An object of type DataTableInput lets the user input one or more columns of numbers. The user moves from cell to cell using the arrow keys, the tab key, or the return key. By default, when the user moves down from the last row, a new row is automatically added to the table. It is also possible to add rows to the bottom of the table by calling the addRows(int) method. (Note that empty rows at the bottom of the table are ignored in calculations.)
A DataInputTable is a MathObject that can be given a name and added to a Parser. This lets the parser understand certain pseudo-functions that access the data in the table. For example, if "datatab" is the name of the table, then "datatab.count" refers to the number of rows in the table (ignoring empty rows at the bottom). "datatab.sum(<expression>)" finds the sum of the expression over all the rows of the table. The expression can include references to the names of columns in the table. For example, if "A" is the name of the first column in the table, "datatab.sum(A)" is the sum of all the entries in the column (ignoring empty rows at the bottom). Also, access to individual cell values is provided: "datatab.A(i)" would be the number in the i-th row of column A. Column variables can also be added to any Parser to enable the Parser to understand references to the column names. (But expressions that contain such references are rather unusual and should be used with care.)
This is a fairly complicated class. See the JavaDoc documentation.
ComputeButton is a subclass of Button that can be configured to notify a Controller when it is clicked. Call the setOnUserAction(Controller) to arrange for the ComputeButton to call the Controller's compute() method when the button is clicked. Note that the gatherInputs() method in class JCMPanel does not automatically set ComputeButtons to respond properly. You still have to call setOnUserAction() explicitly.
A DisplayLabel is a Computable that can display the values of one or more Value objects, embedded in a string of text. DisplayLabel is a subclass of the standard AWT class Label, so all the methods from that class are available.
When the text for a DisplayLabel is specified, it should contain a single "#" character at each point where a value is to be inserted. A single Value or an array of Value objects will provide the values. The string and Values can be specified in the constructor. Use the constructor DisplayLabel(String,Value) for a string that contains just one "#". Use DisplayLabel(String,Value) to provide an array of Values that can be substituted for several #'s. The string can be changed later by calling the setText(String) method. The Values can be set later by calling the setValues(Value) method.
Numbers in the DisplayLabel will, by default, use a maximum of 12 characters. If you would like a different maximum length, call setNumSize(int). A shorter maximum length can make the label more readable, at the expense of some inaccuracy. The maximum is clamped to the range 6 to 25. (In some cases, the size of the number might actually end up being larger than the specified maximum length.)
class Tie and interface Tieable
Sometimes, it's important to synchronize two objects so that they always have the same value. In JCM, objects of class Tie are provided for this purpose. Some objects, including VariableInputs and VariableSliders, are "Tieable", which means that they can be added to a Tie, which will be responsible for keeping their values in sync. The Tie object must, in turn, be added to a Controller for it to do its job at the right times. In a typical case, you might want to synchronize a VariableInput, xInput, with a VariableSlider, xSlide, in order to provide the user with alternative ways of inputting the same value. You would do this by saying:controller.add( new Tie( xInput, xSlide ) );
where controller is probably the same Controller that has responsibility for calling xInput and xSlide. If you are using JCMPanels, you could usemainPanel.getController().add( new Tie( xInput, xSlide ) );
where mainPanel is the top-level JCMPanel. You can have more than two objects in a Tie, but you have to add the extra ones using the Tie's add(Tieable) method.