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: import java.util.Vector;
029:
030: /**
031: * Controllers are the focus of all the action in the JCM system. A Controller can be
032: * set to listen for changes (generally changes in user input). This is done by
033: * registering the Controller with (usally) an InputObject. For example, if a Controller, c,
034: * is to respond when the user presses return in a VariableInput, v, then
035: * v.setOnUserAction(c) should be called to arrange to have the Controller listen
036: * for such actions. VariableSliders, ExpressionInputs, MouseTrackers, Animators have a similar
037: * methods. It is also possible to set the Controller to listen for events of
038: * type AdjustmentEvent, ActionEvent, TextEvent, or ItemEvent (but this feature is
039: * left over from an older version of JCM, and I'm not sure whether it's necessary).
040: * Whenever a Controller learns of some change, it will process any InputObjects,
041: * Ties, and Computables that have been registered with it.
042: *
043: * <p>InputObjects and Computables have to be added to a Controller to be processed,
044: * using the Controller's add method. (If you build your inteface out of JCMPanels,
045: * then this is done automatically.) (Note that an InputObject is added to a Controller
046: * to have its value checked -- This is separate from registering the Controller to
047: * listen for changes in the InputObject. Often, you have to do both.) The gatherInputs()
048: * method in class JCMPanel can be used to do most of this registration automaticaly.
049: *
050: * <p>A Tie that synchronizes two or more Values, to be effective, has to be added to a Controller.
051: * See the Tie class for inforamtion about what Ties are and how they are used.
052: *
053: * <p>A Controller can have an associated ErrorReporter, which is used to report any
054: * errors that occur during the processing. Currently, an ErrorReporter is either
055: * a DisplayCanvas or a MessagePopup.
056: *
057: * <p>A Controller can be added to another Controller, which then becomes a sub-controller.
058: * Whenever the main Controller hears some action, it also notifies all its sub-controllers
059: * about the action. Furthermore, it can report errors that occur in the sub-controllers,
060: * if they don't have their own error reporters. (Usually, you will just set an error
061: * reporter for the top-level Controller.)
062: **/
063: public class Controller implements java.io.Serializable, Computable,
064: InputObject, AdjustmentListener, ActionListener, TextListener,
065: ItemListener {
066:
067: /**
068: * Computable objects controlled by this controller. Note that Controllers
069: * are Computables, so this list can include sub-controllers.
070: */
071: protected Vector computables;
072:
073: /**
074: * InputObjects controlled by this controller. Note that Controllers
075: * are InputObjects, so this list can include sub-controllers.
076: */
077: protected Vector inputs;
078:
079: /**
080: * Ties that have been added to this controller.
081: */
082: protected Vector ties;
083:
084: /**
085: * Used for reporting errors that occur in the
086: * compute() method of this controller. If the errorReporter
087: * is null and if this controller has a parent,
088: * then the parent will report the error. If
089: * no ancestor has an errorReporter, the error
090: * message is written to standard output.
091: */
092: protected ErrorReporter errorReporter;
093:
094: /**
095: * The parent of this controller, if any.
096: * This is set automatically when one
097: * controller is added to another.
098: */
099: protected Controller parent;
100:
101: /**
102: * If non-null, this is an error message
103: * that has been reported and not yet cleared.
104: */
105: protected String errorMessage;
106:
107: /**
108: * Create a Controller.
109: */
110: public Controller() {
111: }
112:
113: /**
114: * Set the ErrorReporter used to report errors that occur when the
115: * compute() method of this Controller is executed.
116: */
117: public void setErrorReporter(ErrorReporter r) {
118: errorReporter = r;
119: }
120:
121: /**
122: * Get the ErrorReporter for this Controller. Return null if there is none.
123: */
124: public ErrorReporter getErrorReporter() {
125: return errorReporter;
126: }
127:
128: /**
129: * Add an object to be controlled by this controller. It should be of
130: * one or more of the types InputObject, Computable, Tie. If it is
131: * a Controller, then this Controller becomes its parent.
132: */
133: public void add(Object obj) {
134: if (obj == null)
135: return;
136: if (obj instanceof Controller) {
137: Controller c = (Controller) obj;
138: if (c.parent != null)
139: c.parent.remove(this );
140: c.parent = this ;
141: }
142: if (obj instanceof Computable) {
143: if (computables == null)
144: computables = new Vector();
145: computables.addElement(obj);
146: }
147: if (obj instanceof InputObject) {
148: if (inputs == null)
149: inputs = new Vector();
150: inputs.addElement(obj);
151: }
152: if (obj instanceof Tie) {
153: if (ties == null)
154: ties = new Vector();
155: ties.addElement(obj);
156: }
157: }
158:
159: /**
160: * Remove the object from the controller (if present).
161: */
162: public void remove(Object obj) {
163: if (obj == null)
164: return;
165: if (computables != null) {
166: computables.removeElement(obj);
167: if (computables.size() == 0)
168: computables = null;
169: }
170: if (inputs != null) {
171: inputs.removeElement(obj);
172: if (inputs.size() == 0)
173: inputs = null;
174: }
175: if (ties != null) {
176: ties.removeElement(obj);
177: if (ties.size() == 0)
178: ties = null;
179: }
180: if (obj instanceof Controller
181: && ((Controller) obj).parent == this )
182: ((Controller) obj).parent = null;
183: }
184:
185: /**
186: * If this controller has a parent, remove it from its parent. (Then, a call to the
187: * former parent's compute() method will not call this controller's compute().)
188: */
189: public void removeFromParent() {
190: if (parent != null)
191: parent.remove(this );
192: }
193:
194: // ----------------- Listening for events ----------------------
195:
196: /**
197: * Simply calls compute when the Controller hears an ActionEvent.
198: * This is not meant to be called directly.
199: */
200: public void actionPerformed(ActionEvent evt) {
201: compute();
202: }
203:
204: /**
205: * Simply calls compute when the Controller hears a TextEvent.
206: * This is not meant to be called directly.
207: */
208: public void textValueChanged(TextEvent evt) {
209: compute();
210: }
211:
212: /**
213: * Simply calls compute when the Controller hears an AdjustmantEvent.
214: * This is not meant to be called directly.
215: */
216: public void adjustmentValueChanged(AdjustmentEvent evt) {
217: compute();
218: }
219:
220: /**
221: * Simply calls compute when the Controller hears an ItemEvent.
222: * This is not meant to be called directly.
223: */
224: public void itemStateChanged(ItemEvent evt) {
225: compute();
226: }
227:
228: // -------------- Implementation and error-handling ----------------------
229:
230: /**
231: * When an contoller computes, it first calls checkInput() for any
232: * InputOjects that it controls (including those in sub-controllers).
233: * It then handles any Ties. Finally,
234: * it calls the compute() method of any Computables. If an error
235: * occurs, it reports it. JCMErrors (which should represent errors
236: * on the part of the user) will generally only occur during the
237: * checkInput() phase. Internal, programmer errors can occur at
238: * any time and might leave the sytem in an unhappy state. They are
239: * reported as debugging aids for the programmer. When one occurs,
240: * a stack trace is printed to standard output.
241: */
242: synchronized public void compute() {
243: try {
244: checkInput();
245: doTies();
246: clearErrorMessage();
247: doCompute();
248: } catch (JCMError e) {
249: if (errorMessage == null
250: || !errorMessage.equals(e.getMessage()))
251: reportError(e.getMessage());
252: } catch (RuntimeException e) {
253: reportError("Internal programmer's error detected? " + e);
254: e.printStackTrace();
255: }
256: }
257:
258: /**
259: * Call checkInput() of each InputObject. Can throw a JCMError.
260: * This is mostly meant to be called by Controller.compute().
261: * Note that this will recurse though any sub-controllers of
262: * this controller, so that when comput() is called,
263: * all the InputObjects in the sub-controllers
264: * are processed before ANY Tie or Computable is processed.
265: * Similarly, the Ties and Computables in the sub-controllers
266: * are processed in separate passes.
267: */
268: public void checkInput() {
269: if (inputs != null) {
270: int top = inputs.size();
271: for (int i = 0; i < top; i++)
272: ((InputObject) inputs.elementAt(i)).checkInput();
273: }
274: }
275:
276: /**
277: * Check the Ties in this controller and its sub-controllers.
278: */
279: protected void doTies() {
280: if (inputs != null) {
281: int top = inputs.size();
282: for (int i = 0; i < top; i++)
283: if (inputs.elementAt(i) instanceof Controller)
284: ((Controller) inputs.elementAt(i)).doTies();
285: }
286: if (ties != null) {
287: int top = ties.size();
288: for (int i = 0; i < top; i++)
289: ((Tie) ties.elementAt(i)).check();
290: }
291: }
292:
293: /**
294: * Compute the Computables in this controller and its sub-controllers.
295: */
296: protected void doCompute() {
297: if (computables != null) {
298: int top = computables.size();
299: for (int i = 0; i < top; i++) {
300: Object obj = computables.elementAt(i);
301: if (obj instanceof Controller)
302: ((Controller) obj).doCompute();
303: else
304: ((Computable) obj).compute();
305: }
306: }
307: }
308:
309: /**
310: * Report the specified error message.
311: */
312: public void reportError(String message) {
313: if (message == null)
314: clearErrorMessage();
315: if (errorReporter != null) {
316: errorReporter.setErrorMessage(this , message);
317: errorMessage = message;
318: } else if (parent != null)
319: parent.reportError(errorMessage);
320: else {
321: errorMessage = message;
322: System.out.println("***** Error: " + errorMessage);
323: }
324: }
325:
326: /**
327: * Clear the error message.
328: */
329: protected void clearErrorMessage() {
330: if (errorReporter != null)
331: errorReporter.clearErrorMessage();
332: else if (parent != null)
333: parent.clearErrorMessage();
334: errorMessage = null;
335: }
336:
337: /**
338: * Should be called by the ErrorReporter if the ErrorReporter clears the error itself.
339: * (This is only used to avoid repeatedly setting the same error message, during an
340: * animation for example.)
341: */
342: public void errorCleared() {
343: errorMessage = null;
344: }
345:
346: /**
347: * Method required by InputObject interface; in this class, calls the same method
348: * recursively on any input objects controlled by this controller. This is meant to
349: * be called by JCMPanel.gatherInputs().
350: */
351: public void notifyControllerOnChange(Controller c) {
352: if (inputs != null) {
353: int top = inputs.size();
354: for (int i = 0; i < top; i++)
355: ((InputObject) inputs.elementAt(i))
356: .notifyControllerOnChange(c);
357: }
358: }
359:
360: /**
361: * Calles notifyControllerOnChange(this). That is, it sets all the InputObjects in
362: * this Controller, and in subcontrollers, to notify this Controller when they change.
363: */
364: public void gatherInputs() {
365: notifyControllerOnChange(this );
366: }
367:
368: } // end class Controller
|