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: * A VariableSlider is a slider (implemented as a Scrollbar) whose
031: * position represents the value of an associated variable.
032: * The range of values represented by the slider is given by a pair of
033: * Value objects. They can be specified in the constructor or later with the
034: * setMin and setMax methods. A VariableSlider has an associated variable
035: * that represents the value of the slider. Note that the value of the variable
036: * can change only when the setInput() or checkInput() method is called.
037: * If you want the value of the variable to track the position
038: * of the slider as it is is manipulated by the user, add the slider to
039: * a Controller and set the Controller as the Slider's "onUserAction" property.
040: * This allows other objects that depend on the values of the slider to
041: * be recomputed by the controller when the value changes, as long as they
042: * are also registered with the Controller.
043: *
044: * <p>Some points to note:
045: *
046: * 1) setVal() can set a value outside the range from min to max,
047: * which will persist until the next time checkInput()
048: * or setVal() is called again.
049: * 2) If the value of min or max changes, the value of this variable
050: * will not change EXCEPT that it is clamped to the range between min and max.
051: * 3) Min does not have to be less than max.
052: * 4) The checkInput() routine only sets the needValueCheck flag to true.
053: * (The setVal() and getVal() routines both set this flag to false.) This "lazy evaluation" is used because
054: * checkInput() can't compute the new value itself. (The max and min
055: * might depend on Values that are themselves about to change when some
056: * other object's checkInput() mehtod is called.)
057: * 5) getVal() returns the current value, as stored in the variable,
058: * UNLESS needValueCheck is true. In that case, it recomputes
059: * the value first. getSerialNumber() works similarly.
060: * 6) A VariableSlider never throws JCMErrors. If an error occurs when min
061: * or max is evaluated, the value of the variable associated with this VariableSlider
062: * becomes undefined. (The point is, it doesn't generate any errors of its own.
063: * The error would be caused by other InputObjects which should throw
064: * their own errors when their checkInput() methods are called.)
065: */
066: public class VariableSlider extends Scrollbar implements InputObject,
067: Tieable, Value {
068: /**
069: * The variable associated with this VariableSlider.
070: * VS is a nested private class, defined below.
071: */
072: protected VS variable;
073:
074: /**
075: * The Values that specify the range of values represented
076: * by the slider. min does not have to be less than max.
077: */
078: protected Value min, max;
079:
080: private Controller onUserAction; // If this is non-null, the compute() method
081: // of onUserAction is called when the user
082: // changes the position of the slider.
083:
084: /**
085: * If this is true, then the value of the
086: * variable associated with this slider is
087: * an integer. Furthermore, the number of
088: * intervals on the slider is set to be
089: * the same as the range of possible values
090: * (unless this range is too big).
091: */
092: protected boolean integerValued;
093:
094: /**
095: * The number of possible value of the scrollbar
096: * (Unless integerValued is true.)
097: */
098: protected int intervals;
099:
100: /**
101: * This increases every time the value of the variable changes.
102: */
103: protected long serialNumber;
104:
105: /**
106: * This is set to true when checkInput() is called
107: * to indicate that the min and max values must be
108: * checked the next time getVal() is called.
109: */
110: protected boolean needsValueCheck;
111:
112: /**
113: * This is the position of the scrollbar the last time
114: * getVal() or setVal() was called. It is used to check
115: * whether the user has repositioned the slider.
116: */
117: protected int oldPosition;
118:
119: /**
120: * The values found for min and max the last time
121: * checkInput() was called.
122: */
123: protected double minVal = Double.NaN, maxVal;
124:
125: /**
126: * Create a horizontal variable slider with no name and with a default
127: * value range of -5 to 5.
128: */
129: public VariableSlider() {
130: this (null, null, null, null);
131: }
132:
133: /**
134: * Create a horizontal variable slider with no name and with the
135: * specified range of values. If min is null, a default
136: * value -5 is used. If max is null, a default value 5 is used.
137: */
138: public VariableSlider(Value min, Value max) {
139: this (null, min, max, null);
140: }
141:
142: /**
143: * Create a horizontal variable slider with the given name and range of
144: * values, and register it with the given parser (but only if
145: * both name and p are non-null). If min is null, a default
146: * value -5 is used. If max is null, a default value 5 is used.
147: */
148: public VariableSlider(String name, Value min, Value max, Parser p) {
149: this (name, min, max, p, -1, Scrollbar.HORIZONTAL);
150: }
151:
152: /**
153: * Create a variable slider with the given name and range of
154: * values, and register it with the given parser (but only if
155: * both name and p are non-null). The "intervals" parameter specifes
156: * how many different positions there are on the slider. (The value
157: * of the scrollbar ranges from 0 to intervals.) If intervals is <= 0,
158: * it will be set to 1000. If it is between 1 and 9, it will be set to 10.
159: * The orientation must be either Scrollbar.HORIZONTAL or Scrollbar.VERTICAL.
160: * It specifies whether this is a horizontal or vertical scrollbar.
161: * If min is null, a default value -5 is used. If max is null, a default
162: * value 5 is used.
163: *
164: * @param name name for this VariableSlider.
165: * @param min minimum value for slider.
166: * @param max maximum value for slider.
167: * @param p register VariableSlider with this Parser.
168: * @param intervals discrete positions on slider.
169: * @param orientation Scrollbar.HORIZONTAL or Scrollbar.VERTICAL.
170: */
171: public VariableSlider(String name, Value min, Value max, Parser p,
172: int intervals, int orientation) {
173: super (orientation);
174: setBackground(Color.lightGray);
175: setMin(min);
176: setMax(max);
177: if (intervals <= 0)
178: intervals = 1000;
179: if (intervals <= 10)
180: intervals = 10;
181: this .intervals = intervals;
182: int visible = (intervals / 50) + 3;
183: if (intervals < 100)
184: setBlockIncrement(1);
185: else
186: setBlockIncrement(intervals / 100);
187: setValues(intervals / 2, visible, 0, intervals + visible);
188: variable = new VS(name);
189: if (name != null)
190: super .setName(name);
191: if (p != null && name != null)
192: p.add(variable);
193: needsValueCheck = true; // Force getVal() to compute a new value for the variable.
194: oldPosition = -1;
195: getVal();
196: }
197:
198: /**
199: * Set the name of the associated variable. You shouldn't do this
200: * if it has been added to a parser. If name is non-null, then
201: * the name of this Component is also set to the specified name.
202: */
203: public void setName(String name) {
204: variable.setName(name);
205: if (name != null)
206: super .setName(name);
207: }
208:
209: /**
210: * A convenience method that registers this VariableSlider's variable
211: * with p (but only if both p and the name of the variable are non-null).
212: */
213: public void addTo(Parser p) {
214: if (p != null && variable.getName() != null)
215: p.add(variable);
216: }
217:
218: /**
219: * Return the variable associated with this VariableSlider.
220: */
221: public Variable getVariable() {
222: return variable;
223: }
224:
225: /**
226: * If set to true, restrict the values of the variable associated with this
227: * slider to be integers. Furthermore, the number of intervals on the
228: * scrollbar will be set to be the same as the size of the range from
229: * min to max (unless this range is too big). The setVal()
230: * method can still set the value of the variable to be a non-integer.
231: */
232: public void setIntegerValued(boolean b) {
233: integerValued = b;
234: if (b && !Double.isNaN(minVal) && !Double.isNaN(maxVal))
235: checkIntegerLimits(minVal, maxVal);
236: needsValueCheck = true;
237: }
238:
239: /**
240: * Return a boolean which is true if the VariableSlider restricts ranges of values to integers, false otherwise.
241: */
242: public boolean getIntegerValued() {
243: return integerValued;
244: }
245:
246: /**
247: * Set the value that the variable has when the slider is at the left (or
248: * bottom) of the scrollbar. If v is null, -5 is used as the default value.
249: */
250: public void setMin(Value v) {
251: min = (v == null) ? new Constant(-5) : v;
252: }
253:
254: /**
255: * Set the value that the variable has when the slider is at the right (or
256: * top) of the scrollbar. If v is null, 5 is used as the default value.
257: */
258: public void setMax(Value v) {
259: max = (v == null) ? new Constant(5) : v;
260: }
261:
262: /**
263: * Get the Value object that gives the value of the variable when the slider is
264: * at the left (or bottom) of the scrollbar. The Value is always non-null.
265: */
266: public Value getMin() {
267: return min;
268: }
269:
270: /**
271: * Get the Value object that gives the value of the variable when the slider is
272: * at the right (or top) of the scrollbar. The Value is always non-null.
273: */
274: public Value getMax() {
275: return max;
276: }
277:
278: /**
279: * If the Controller, c, is non-null, then its compute method will be called whenever
280: * the user adjusts the position of the scroll bar.
281: */
282: public void setOnUserAction(Controller c) {
283: onUserAction = c;
284: enableEvents(AWTEvent.ADJUSTMENT_EVENT_MASK);
285: }
286:
287: /**
288: * Method required by InputObject interface; in this class, it simply calls
289: * setOnUserAction(c). This is meant to be called by JCMPanel.gatherInputs().
290: */
291: public void notifyControllerOnChange(Controller c) {
292: setOnUserAction(c);
293: }
294:
295: /**
296: * Return the Controller, if any, that is notified when the user
297: * adjusts the position of the scroll bar.
298: */
299: public Controller getOnUserAction() {
300: return onUserAction;
301: }
302:
303: /**
304: * Return this object's serial number, which is increased every time the
305: * value changes.
306: */
307: public long getSerialNumber() {
308: if (needsValueCheck)
309: getVal(); // Make sure the value/serialNumber data is up-to-date.
310: return serialNumber;
311: }
312:
313: /**
314: * Change the value and serial number of this object to match
315: * those of newest. See the Tie class for more information.
316: * This is not meant to be called directly
317: */
318: public void sync(Tie tie, Tieable newest) {
319: if (newest != this ) {
320: if (!(newest instanceof Value))
321: throw new IllegalArgumentException(
322: "Internal Error: A VariableSlider can only sync with Value objects.");
323: setVal(((Value) newest).getVal());
324: serialNumber = newest.getSerialNumber();
325: }
326: }
327:
328: /**
329: * Get the value of this VariableSlider. (If needsValueCheck is
330: * true, then the value is recomputed. Otherwise, the current
331: * value is returned.)
332: */
333: public double getVal() {
334: if (needsValueCheck) {
335: double newMinVal = Double.NaN;
336: double newMaxVal = Double.NaN;
337: boolean maxMinChanged = false;
338: double value = variable.getVariableValue(); // Current value of the variable.
339: try { // Compute new max/min values.
340: newMinVal = min.getVal();
341: newMaxVal = max.getVal();
342: if (!Double.isNaN(newMinVal)
343: && !Double.isNaN(newMaxVal)
344: && (newMinVal != minVal || newMaxVal != maxVal)) {
345: if (integerValued)
346: checkIntegerLimits(newMinVal, newMaxVal);
347: minVal = newMinVal;
348: maxVal = newMaxVal;
349: maxMinChanged = true;
350: }
351: } catch (JCMError e) { // don't allow error to propagate
352: }
353: if (Double.isNaN(minVal) || Double.isNaN(maxVal)
354: || Double.isInfinite(minVal)
355: || Double.isInfinite(maxVal)) {
356: variable.setVariableValue(Double.NaN);
357: if (!Double.isNaN(value))
358: serialNumber++; // Value has changed.
359: setValue(0);
360: } else if (oldPosition != getValue()) { // Position of scroll bar has been changed by user,
361: // so compute a new value for the variable.
362: double newVal = minVal
363: + ((maxVal - minVal) * getValue()) / intervals;
364: newVal = clamp(newVal, minVal, maxVal);
365: if (integerValued)
366: newVal = Math.round(newVal);
367: if (newVal != value) {
368: variable.setVariableValue(newVal);
369: serialNumber++;
370: }
371: } else if (!Double.isNaN(value) && maxMinChanged) {
372: // Max/min have changed, but user has not changed scroll bar position.
373: // Change the value only if that is necessary to clamp it to the min/max range.
374: // Possibly, we have to change the position of the scroll.
375: double newVal = clamp(value, minVal, maxVal);
376: if (newVal != value) {
377: variable.setVariableValue(newVal);
378: serialNumber++;
379: }
380: if (minVal != maxVal) {
381: int pos = (int) ((value - minVal)
382: / (maxVal - minVal) * intervals);
383: setValue(pos);
384: }
385: }
386: oldPosition = getValue();
387: needsValueCheck = false;
388: }
389: return variable.getVariableValue();
390: }
391:
392: /**
393: * Set the value of the variable to x. If possible, set the
394: * value on the scroll bar to match.
395: */
396: public void setVal(double x) {
397: try {
398: double minVal = min.getVal();
399: double maxVal = max.getVal();
400: if (Double.isNaN(x) || Double.isNaN(minVal)
401: || Double.isNaN(maxVal) || Double.isInfinite(x)
402: || Double.isInfinite(minVal)
403: || Double.isInfinite(maxVal)) {
404: } else {
405: if (integerValued) {
406: minVal = Math.round(minVal);
407: maxVal = Math.round(maxVal);
408: }
409: double xpos = clamp(x, minVal, maxVal);
410: int pos = (int) ((xpos - minVal) / (maxVal - minVal) * intervals);
411: setValue(pos);
412: }
413: } catch (JCMError e) {
414: }
415: variable.setVariableValue(x);
416: needsValueCheck = false;
417: oldPosition = getValue();
418: serialNumber++;
419: }
420:
421: // ------------------ Some implementation details -------------------------
422:
423: /**
424: * From the InputObject interface. This will force the slider to recompute
425: * its max and min values, and possibly clamp its value between these two
426: * extremes) the next time the value or serial number is checked. This is
427: * ordinarily called by a Controller.
428: */
429: public void checkInput() {
430: needsValueCheck = true;
431: }
432:
433: /**
434: * Modify getPreferredSize to return a width of
435: * 200, if the scrollbar is horzontal, or a height
436: * of 200, if it is vertical. This is not meant to
437: * be called directly.
438: */
439: public Dimension getPreferredSize() {
440: Dimension d = super .getPreferredSize();
441: if (getOrientation() == Scrollbar.HORIZONTAL)
442: return new Dimension(200, d.height);
443: else
444: return new Dimension(d.width, 200);
445: }
446:
447: private void checkIntegerLimits(double minVal, double maxVal) {
448: // Called if integerValued is true to set values on scrollbar.
449: int oldpos = getValue();
450: minVal = Math.round(minVal);
451: maxVal = Math.round(maxVal);
452: double value = Math.round(variable.getVariableValue());
453: double range = Math.abs(minVal - maxVal);
454: if (range > 0 && range != intervals) {
455: intervals = (int) Math.min(range, 10000);
456: double v = clamp(value, minVal, maxVal);
457: int pos = (int) ((v - minVal) / (maxVal - minVal) * intervals);
458: int visible = (intervals / 50) + 3;
459: if (intervals < 10)
460: setBlockIncrement(1);
461: else if (intervals < 100)
462: setBlockIncrement(intervals / 10);
463: else
464: setBlockIncrement(10 + intervals / 100);
465: setValues(pos, visible, 0, intervals + visible);
466: }
467: if (oldpos == oldPosition)
468: oldPosition = getValue();
469: else
470: oldPosition = -1;
471: }
472:
473: private double clamp(double val, double minVal, double maxVal) {
474: // Utility routine used by setVal and getVal. If val is
475: // between minVal and maxVal, it returns val. Otherwise,
476: // it returns one of the endpoints, minVal or maxVal.
477: // minVal can be greater than maxVal.
478: double newVal = val;
479: if (minVal < maxVal) {
480: if (newVal < minVal)
481: newVal = minVal;
482: else if (newVal > maxVal)
483: newVal = maxVal;
484: } else {
485: if (newVal < maxVal)
486: newVal = maxVal;
487: else if (newVal > minVal)
488: newVal = minVal;
489: }
490: return newVal;
491: }
492:
493: /**
494: * Overridden to call onUserAction.compute() if onUserAction is non-null.
495: * This is not meant to be called directly.
496: */
497: public void processAdjustmentEvent(AdjustmentEvent evt) {
498: if (onUserAction != null)
499: onUserAction.compute();
500: super .processAdjustmentEvent(evt);
501: }
502:
503: private class VS extends Variable {
504: // A modified Variable class in which the getVal and
505: // setVal methods are redirected to calls to the
506: // getVal and setVal methods in the VariableSlider
507: // class. The methods getVariableValue and setVariableValue
508: // provide access to the original getVal and setVal of
509: // the variable class.
510: VS(String name) {
511: super (name);
512: }
513:
514: public double getVal() {
515: return VariableSlider.this .getVal();
516: }
517:
518: public void setVal(double x) {
519: VariableSlider.this .setVal(x);
520: }
521:
522: void setVariableValue(double x) {
523: super .setVal(x);
524: }
525:
526: double getVariableValue() {
527: return super .getVal();
528: }
529: }
530:
531: } // end class VariableSlider
|