001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.awt;
024:
025: import edu.hws.jcm.data.*;
026: import java.awt.*;
027: import java.awt.event.*;
028:
029: /**
030: * An ExpressionInput is an input box that allows the user
031: * input a mathematical expression. There is an associated
032: * object that belongs to the class Expression. The
033: * value of this object can change only when checkInput()
034: * is called. The checkInput() method is usually called by a Controller.
035: * <p>An ExpressionInput will ordinarily be registered with
036: * a Controller in TWO ways: It's added to a Controller
037: * with the Controller's add() method. This makes the Contrller
038: * call the ExpressionInput's checkInput() method during the
039: * Controller's compute() method. Secondly, the Controller
040: * is set as the "onUserAction" property. This causes the
041: * Controller's compute() method to be called when the user
042: * presses return in the ExpressionInput box. This is optional--
043: * you might, for example, only want the Controller to compute()
044: * when a Compute button is pressed. You can also set the
045: * ExpressionInput's onTextChange property to a Controller
046: * if you want it to compute every time the text in the box
047: * changes.
048: * <p>Use the function getFunction() if you want to
049: * use an ExpressionInput as a way of inputting a function.
050: *
051: */
052: public class ExpressionInput extends TextField implements InputObject,
053: Value {
054:
055: /**
056: * The Expression associate with this input box.
057: * Class EI is a private nested class.
058: */
059: protected EI expr;
060:
061: /**
062: * A parser for parsing the user's input
063: * expression. If this is null,
064: * a default parser will be used and
065: * only constant expressions will
066: * be allowed.
067: */
068: protected Parser parser;
069:
070: //protected boolean hasChanged;
071: protected String previousContents;
072:
073: /**
074: * True if an error should be thrown
075: * when checkInput() is called,
076: * but the content of the box is not
077: * a legal expression. Otherwise, the
078: * expression will become a constant
079: * expression with value Double.NaN.
080: */
081: protected boolean throwErrors;
082:
083: private Controller onUserAction; // If this is non-null, the compute() method
084: // of onUserAction is called when the user
085: // presses return in this input-box.
086:
087: private Controller onTextChange; // If this is non-null, the compute() method
088: // of onTextChange is called when the text
089: // in this input box changes
090:
091: /**
092: * Error message from the most recent
093: * time the input was checked by a
094: * call to checkInput(). If this is
095: * null, then no error occurred.
096: */
097: protected String errorMessage;
098:
099: private long serialNumber; // This goes up by one every time checkInput()
100:
101: // is called and finds a change in the
102: // user's input;
103:
104: /**
105: * Create a new ExpressionFunction with no associated parser. This can only
106: * be used to input constant expressions, unless you set a parser later with setParser().
107: */
108: public ExpressionInput() {
109: this ("", null);
110: }
111:
112: /**
113: * Create an ExpressionInputBox with initial contents given by initialValue.
114: * (If initialValue is null, the empty string is used.) If p is not null,
115: * then p will be used to parse the contents of the box.
116: *
117: * @param initialValue initial contents of ExpressionInputBox.
118: * @param p if non-null, this parser will be used to parse contents of the ExpressionInputBox.
119: */
120: public ExpressionInput(String initialValue, Parser p) {
121: super (30);
122: expr = new EI();
123: if (initialValue == null)
124: initialValue = "";
125: super .setText(initialValue);
126: setBackground(Color.white);
127: //enableEvents(KeyEvent.KEY_EVENT_MASK);
128: setParser(p); // (Sets previousContents to null, so checkInput() will actually check the input.)
129: checkInput(); // Won't throw an error, since throwErrors is false.
130: throwErrors = true;
131: }
132:
133: /**
134: * Set the parser that is used to parse the user's input strings.
135: * If this is null, then a default parser is used that will
136: * only allow constant expressions.
137: *
138: * @param p parser to register with user's input strings.
139: */
140: public void setParser(Parser p) {
141: parser = (p == null) ? new Parser() : p;
142: //hasChanged = true; // force re-compute when checkInput() is next called.
143: previousContents = null;
144: }
145:
146: /**
147: * Get the Expression associated with this ExpressionInput.
148: *
149: */
150: public Expression getExpression() {
151: return expr;
152: }
153:
154: /**
155: * Get a function of one variable whose value at a real number
156: * x is computed by assigning x to the variable v and then
157: * returning the value of the expression associated with this
158: * ExpressionInput. Of couse, v should be one of the variables
159: * registered with the Parser for this ExpressionInput, or else
160: * in can never occur in the expression.
161: * Note that the value of the variable v changes TEMPORARILY
162: * when the function is evaluated. (So you should not use
163: * a variable where setting the value has a side effect,
164: * such as the variable associated with a SliderVariable.)
165: *
166: * @param v The function that is returned in a function of this variable.
167: */
168: public Function getFunction(Variable v) {
169: return new SimpleFunction(expr, v);
170: }
171:
172: /**
173: * Get a function of one or more variables whose value at arguments
174: * x1, x2, ... is computed by assigning the x's to the variables and then
175: * returning the value of the expression associated with this
176: * ExpressionInput. Of couse, each v[i] should be one of the variables
177: * registered with the Parser for this ExpressionInput.
178: * Note that the value of the variables change TEMPORARILY
179: * when the function is evaluated.
180: *
181: * @param v The function that is returned is a function of the variables in this array.
182: */
183: public Function getFunction(Variable[] v) {
184: return new SimpleFunction(expr, v);
185: }
186:
187: /**
188: * Return the current value of the expression associated with
189: * this ExpressionInput.
190: */
191: public double getVal() {
192: return expr.getVal();
193: }
194:
195: /**
196: * If the parameter c is non-null, then its compute method will be called whenever
197: * the user presses the return key while typing in this text-input box.
198: */
199: public void setOnUserAction(Controller c) {
200: onUserAction = c;
201: enableEvents(AWTEvent.ACTION_EVENT_MASK);
202: }
203:
204: /**
205: * Return the Controller, if any, that is notified when the user
206: * presses return in this text-input box.
207: */
208: public Controller getOnUserAction() {
209: return onUserAction;
210: }
211:
212: /**
213: * Method required by InputObject interface; in this class, it simply calls
214: * setOnUserAction(c). This is meant to be called by JCMPanel.gatherInputs().
215: */
216: public void notifyControllerOnChange(Controller c) {
217: setOnUserAction(c);
218: }
219:
220: /**
221: * If the parameter, c, is non-null, then its compute method will be called whenever
222: * the text in this input box changes. Furthermore, the throwErrors
223: * property will be set to false, to avoid throwing multiple errors
224: * while the user is typing. (You can change it back to true if
225: * you want by calling setThrowErrors(true).) It would only rarely make sense to
226: * use this feature.
227: */
228: public void setOnTextChange(Controller c) {
229: onTextChange = c;
230: enableEvents(AWTEvent.TEXT_EVENT_MASK);
231: if (c != null)
232: throwErrors = false;
233: }
234:
235: /**
236: * Return the Controller, if any, that is notified whenever the text
237: * in this input box changes
238: */
239: public Controller getOnTextChange() {
240: return onTextChange;
241: }
242:
243: /**
244: * Set the throwErrors property. When this is true, a JCMError can be thrown
245: * when checkInput() is called an a parse error is found in the contents of the input box.
246: * If throwErrors is false, no error is thrown. Instead,
247: * the expression is set to a constant expression with value Double.NaN.
248: */
249: public void setThrowErrors(boolean throwErrors) {
250: this .throwErrors = throwErrors;
251: }
252:
253: /**
254: * Return the value of the throwErrors property, which determines whether errors
255: * can be thrown when checkInput() is called.
256: */
257: public boolean getThrowErrors() {
258: return throwErrors;
259: }
260:
261: /**
262: * Get error message from previous call to checkInput().
263: * Returns null if and only if there was no error.
264: */
265: public String getErrorMessage() {
266: return errorMessage;
267: }
268:
269: //---------------- Some implementation details -------------------------------------------------
270:
271: /**
272: * Get the expression from the box, maybe throw a JBCError
273: * if a ParseError occurs. This is meant to be called by a Controller, in general.
274: * The expression associated with this ExpressionInput can only change when this
275: * method is called; it DOES NOT change continuously as the user types.
276: */
277: public void checkInput() {
278: boolean hasChanged = previousContents == null
279: || !previousContents.equals(getText());
280: if (!hasChanged)
281: return;
282: expr.serialNumber++;
283: String contents = getText();
284: try {
285: expr.exp = parser.parse(contents);
286: errorMessage = null;
287: previousContents = getText();
288: } catch (ParseError e) {
289: expr.exp = null;
290: if (throwErrors) {
291: errorMessage = "Error in expression: " + e.getMessage();
292: setCaretPosition(e.context.pos);
293: requestFocus();
294: throw new JCMError(e.getMessage(), this );
295: } else
296: errorMessage = "Error in expression at position "
297: + e.context.pos + ": " + e.getMessage();
298: }
299: }
300:
301: /**
302: * Set the text displayed in this input box. This overrides TextField.setText
303: * to make sure that the expression will be recomputed the next time
304: * checkInput() is called.
305: */
306: public void setText(String str) {
307: super .setText(str);
308: //hasChanged = true;
309: previousContents = null;
310: }
311:
312: /**
313: * Override processKeyEvent to only allow characters
314: * that are legal in expressions. This is not meant to be called directly.
315:
316: public void processKeyEvent(KeyEvent evt) {
317: if (evt.getID() == KeyEvent.KEY_PRESSED) {
318: int ch = evt.getKeyCode();
319: char chr = evt.getKeyChar();
320: boolean use = (chr != 0 && (Character.isDigit(chr) || Character.isLetter(chr))
321: || chr == '.' || chr == '(' || chr == ')'
322: || chr == '-' || chr == '+' || chr == '*'
323: || chr == '/' || chr == '^' || chr == ','
324: || chr == ':' || chr == '?' || chr == '|'
325: || chr == '&' || chr == '~' || chr == '='
326: || chr == '<' || chr == '>' || chr == '!')
327: || ch == KeyEvent.VK_DELETE || ch == KeyEvent.VK_SPACE
328: || ch == KeyEvent.VK_BACK_SPACE;
329: boolean useControl = use || ch == KeyEvent.VK_TAB
330: || ch ==KeyEvent.VK_ENTER || chr == 0;
331: if (!useControl) {
332: evt.consume();
333: Toolkit.getDefaultToolkit().beep();
334: }
335: else if (use)
336: hasChanged = true;
337: }
338: super.processKeyEvent(evt);
339: }
340: */
341:
342: /**
343: * Overridden to call onUserAction.compute() if onUserAction is non-null.
344: * This is not meant to be called directly
345: */
346: public void processActionEvent(ActionEvent evt) {
347: if (onUserAction != null)
348: onUserAction.compute();
349: super .processActionEvent(evt);
350: }
351:
352: /**
353: * Overridden to call onUserAction.compute() if onUserAction is non-null.
354: * This is not meant to be called directly.
355: */
356: public void processTextEvent(TextEvent evt) {
357: if (onTextChange != null)
358: onTextChange.compute();
359: super .processTextEvent(evt);
360: }
361:
362: /**
363: * The expression associated with an ExpressionInput belongs to this class.
364: * So is any derivative of such a function. Note that derivatives
365: * must be recomputed when the expression changes. This is done
366: * via "lazy evaluation", that is, only when necessary. When
367: * a derivative is used, it tests whether it is out of date
368: * by comparing its serialNumber to the serial number of the
369: * expression that it is the derivative of. If they don't match,
370: * then the expression is recomputed and the serial number is updated.
371: * The serial number and defintion of the main expresssion is changed by
372: * checkInput() whenever the user's input has changed.
373: */
374: protected class EI implements Expression {
375: /**
376: * The actual expression, or null if the
377: * expression is undefined. If this is a
378: * derivative of another EI, this will be
379: * recomputed as necessary when the expression is used
380: * in some way.
381: */
382: ExpressionProgram exp;
383:
384: /**
385: * This is null for the original expression input by the
386: * user. If this EI was formed by taking the derivative
387: * of anotehr EI, that EI is stored here.
388: */
389: EI derivativeOf;
390:
391: /**
392: * Which Variable is this a derivative with respect to?
393: * If derivativeOf is null, so is wrt.
394: */
395: Variable wrt;
396:
397: /**
398: * For the original expression input by the user, this
399: * goes up by one each time checkInput() is called and
400: * finds a change in the user's input. For derivative
401: * EI, this is the serial number of "derivativeOf" at
402: * the time this derivative expression was last computed.
403: */
404: int serialNumber;
405:
406: EI() {
407: serialNumber = -1; // Forces exp to be computed the first time it is needed.
408: }
409:
410: public double getVal() {
411: checkForChanges();
412: if (exp == null)
413: return Double.NaN;
414: return exp.getVal();
415: }
416:
417: public double getValueWithCases(Cases c) {
418: checkForChanges();
419: if (exp == null)
420: return Double.NaN;
421: return exp.getValueWithCases(c);
422: }
423:
424: public String toString() {
425: checkForChanges();
426: if (exp == null)
427: return "(undefined)";
428: return exp.toString();
429: }
430:
431: public Expression derivative(Variable wrt) {
432: EI deriv = new EI();
433: deriv.derivativeOf = this ;
434: deriv.wrt = wrt;
435: return deriv;
436: }
437:
438: public boolean dependsOn(Variable x) {
439: checkForChanges();
440: return exp.dependsOn(x);
441: }
442:
443: void checkForChanges() {
444: if (derivativeOf != null) {
445: derivativeOf.checkForChanges();
446: if (serialNumber != derivativeOf.serialNumber) {
447: serialNumber = derivativeOf.serialNumber;
448: if (errorMessage != null)
449: exp = null;
450: else
451: exp = (ExpressionProgram) derivativeOf.exp
452: .derivative(wrt);
453: }
454: }
455: }
456: } // end nested class EI
457:
458: } // end class ExpressionInput
|