Logo Search packages:      
Sourcecode: wims version File versions  Download package

Controller.java

/*************************************************************************
*                                                                        *
*   1) This source code file, in unmodified form, and compiled classes   *
*      derived from it can be used and distributed without restriction,  *
*      including for commercial use.  (Attribution is not required       *
*      but is appreciated.)                                              *
*                                                                        *
*    2) Modified versions of this file can be made and distributed       *
*       provided:  the modified versions are put into a Java package     *
*       different from the original package, edu.hws;  modified          *
*       versions are distributed under the same terms as the original;   *
*       and the modifications are documented in comments.  (Modification *
*       here does not include simply making subclasses that belong to    *
*       a package other than edu.hws, which can be done without any      *
*       restriction.)                                                   *
*                                                                        *
*   David J. Eck                                                         *
*   Department of Mathematics and Computer Science                       *
*   Hobart and William Smith Colleges                                    *
*   Geneva, New York 14456,   USA                                        *
*   Email: eck@hws.edu          WWW: http://math.hws.edu/eck/            *
*                                                                        *
*************************************************************************/

package edu.hws.jcm.awt;

import edu.hws.jcm.data.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

/**
 * Controllers are the focus of all the action in the JCM system.  A Controller can be
 * set to listen for changes (generally changes in user input).  This is done by
 * registering the Controller with (usally) an InputObject.  For example, if a Controller, c,
 * is to respond when the user presses return in a VariableInput, v, then
 * v.setOnUserAction(c) should be called to arrange to have the Controller listen
 * for such actions.   VariableSliders, ExpressionInputs, MouseTrackers, Animators have a similar
 * methods.  It is also possible to set the Controller to listen for events of
 * type AdjustmentEvent, ActionEvent, TextEvent, or ItemEvent (but this feature is
 * left over from an older version of JCM, and I'm not sure whether it's necessary).
 * Whenever a Controller learns of some change, it will process any InputObjects,
 * Ties, and Computables that have been registered with it.
 *   
 * <p>InputObjects and Computables have to be added to a Controller to be processed,
 * using the Controller's add method.  (If you build your inteface out of JCMPanels,
 * then this is done automatically.)  (Note that an InputObject is added to a Controller
 * to have its value checked -- This is separate from registering the Controller to
 * listen for changes in the InputObject.  Often, you have to do both.)  The gatherInputs()
 * method in class JCMPanel can be used to do most of this registration automaticaly.
 *   
 * <p>A Tie that synchronizes two or more Values, to be effective, has to be added to a Controller.
 * See the Tie class for inforamtion about what Ties are and how they are used.
 *   
 * <p>A Controller can have an associated ErrorReporter, which is used to report any
 * errors that occur during the processing.  Currently, an ErrorReporter is either
 * a DisplayCanvas or a MessagePopup.
 *   
 * <p>A Controller can be added to another Controller, which then becomes a sub-controller.
 * Whenever the main Controller hears some action, it also notifies all its sub-controllers
 * about the action.  Furthermore, it can report errors that occur in the sub-controllers,
 * if they don't have their own error reporters.  (Usually, you will just set an error 
 * reporter for the top-level Controller.)
 **/
00065 public class Controller implements java.io.Serializable, Computable, InputObject,
                      AdjustmentListener, ActionListener, TextListener, ItemListener {
                      
   /**
    * Computable objects controlled by this controller.  Note that Controllers
    * are Computables, so this list can include sub-controllers.
    */
00072    protected Vector computables;

   /**
    * InputObjects controlled by this controller.  Note that Controllers
    * are InputObjects, so this list can include sub-controllers.
    */
00078    protected Vector inputs;
                                            
   /**
    * Ties that have been added to this controller.
    */
00083    protected Vector ties;
   
   /**
    * Used for reporting errors that occur in the
    * compute() method of this controller.  If the errorReporter
    * is null and if this controller has a parent,
    * then the parent will report the error.  If
    * no ancestor has an errorReporter, the error
    * message is written to standard output.
    */
00093    protected ErrorReporter errorReporter;
                                            
   /**
    * The parent of this controller, if any.
    * This is set automatically when one
    * controller is added to another.
    */
00100    protected Controller parent;

   /**
    * If non-null, this is an error message
    * that has been reported and not yet cleared.
    */
00106    protected String errorMessage;
   
   /**
    * Create a Controller.
    */
00111    public Controller() {
   }

   /**   
    * Set the ErrorReporter used to report errors that occur when the
    * compute() method of this Controller is executed.
    */
00118    public void setErrorReporter(ErrorReporter r) {
      errorReporter = r;
   }
   
   /**
    * Get the ErrorReporter for this Controller.  Return null if there is none.
    */
00125    public ErrorReporter getErrorReporter() {
      return errorReporter;
   }

   /**   
    * Add an object to be controlled by this controller.  It should be of
    * one or more of the types InputObject, Computable, Tie.  If it is
    * a Controller, then this Controller becomes its parent.
    */
00134    public void add(Object obj) {
      if (obj == null)
         return;
      if (obj instanceof Controller) {
          Controller c = (Controller)obj;
          if (c.parent != null)
             c.parent.remove(this);
          c.parent = this;
      }
      if (obj instanceof Computable) {
         if (computables == null)
            computables = new Vector();
         computables.addElement(obj);
      }
      if (obj instanceof InputObject) {
         if (inputs == null)
            inputs = new Vector();
         inputs.addElement(obj);
      }
      if (obj instanceof Tie) {
         if (ties == null)
            ties = new Vector();
         ties.addElement(obj);
      }
   }
   
   /**
    * Remove the object from the controller (if present).
    */
00163    public void remove(Object obj) {
      if (obj == null)
         return;
      if (computables != null) {
         computables.removeElement(obj);
         if (computables.size() == 0)
            computables = null;
      }
      if (inputs != null) {
         inputs.removeElement(obj);
         if (inputs.size() == 0)
            inputs = null;
      }
      if (ties != null) {
         ties.removeElement(obj);
         if (ties.size() == 0)
            ties = null;
      }
      if (obj instanceof Controller && ((Controller)obj).parent == this)
         ((Controller)obj).parent = null;
   }
   
   /**
    * If this controller has a parent, remove it from its parent.  (Then, a call to the
    * former parent's compute() method will not call this controller's compute().)
    */
00189    public void removeFromParent() {
      if (parent != null)
         parent.remove(this);
   }
   
   // ----------------- Listening for events ----------------------
   
   /**
    *  Simply calls compute when the Controller hears an ActionEvent.
    *  This is not meant to be called directly.
    */
00200    public void actionPerformed(ActionEvent evt) {
      compute();
   }
   
   /**
    *  Simply calls compute when the Controller hears a TextEvent.
    *  This is not meant to be called directly.
    */
00208    public void textValueChanged(TextEvent evt) {
      compute();
   }
   
   /**
    *  Simply calls compute when the Controller hears an AdjustmantEvent.
    *  This is not meant to be called directly.
    */
00216    public void adjustmentValueChanged(AdjustmentEvent evt) {
      compute();
   }
   
   /**
    *  Simply calls compute when the Controller hears an ItemEvent.
    *  This is not meant to be called directly.
    */
00224    public void itemStateChanged(ItemEvent evt) {
      compute();
   }

   // -------------- Implementation and error-handling ----------------------

   /**   
    * When an contoller computes, it first calls checkInput() for any
    * InputOjects that it controls (including those in sub-controllers).
    * It then handles any Ties.  Finally,
    * it calls the compute() method of any Computables.  If an error
    * occurs, it reports it.  JCMErrors (which should represent errors
    * on the part of the user) will generally only occur during the
    * checkInput() phase.  Internal, programmer errors can occur at
    * any time and might leave the sytem in an unhappy state.  They are
    * reported as debugging aids for the programmer.  When one occurs,
    * a stack trace is printed to standard output.
    */
00242    synchronized public void compute() {
      try {
         checkInput();
         doTies();
         clearErrorMessage();
         doCompute();
      }
      catch (JCMError e) { 
         if (errorMessage == null || !errorMessage.equals(e.getMessage()))
            reportError(e.getMessage());
      }
      catch (RuntimeException e) {
         reportError("Internal programmer's error detected?  " + e);
         e.printStackTrace();
      }
   }

   /**   
    * Call checkInput() of each InputObject.  Can throw a JCMError.
    * This is mostly meant to be called by Controller.compute().
    * Note that this will recurse though any sub-controllers of
    * this controller, so that when comput() is called,
    * all the InputObjects in the sub-controllers
    * are processed before ANY Tie or Computable is processed.
    * Similarly, the Ties and Computables in the sub-controllers
    * are processed in separate passes.
    */
00269    public void checkInput() {
      if (inputs != null) {
         int top = inputs.size();
         for (int i = 0; i < top; i++)
            ((InputObject)inputs.elementAt(i)).checkInput();
      }
   }
   
   /**
    * Check the Ties in this controller and its sub-controllers.
    */
00280    protected void doTies() {
      if (inputs != null) {
         int top = inputs.size();
         for (int i = 0; i < top; i++)
            if (inputs.elementAt(i) instanceof Controller)
               ((Controller)inputs.elementAt(i)).doTies();
      }
      if (ties != null) {
         int top = ties.size();
         for (int i = 0; i < top; i++)
            ((Tie)ties.elementAt(i)).check();
      }
   }
   
   /**
    * Compute the Computables in this controller and its sub-controllers.
    */
00297    protected void doCompute() {
      if (computables != null) {
         int top = computables.size();
         for (int i = 0; i < top; i++) {
            Object obj = computables.elementAt(i);
            if (obj instanceof Controller)
               ((Controller)obj).doCompute();
            else
               ((Computable)obj).compute();
         } 
      }
   }
   
   /**
    * Report the specified error message.
    */
00313    public void reportError(String message) {
      if (message == null)
         clearErrorMessage();
      if (errorReporter != null) {
         errorReporter.setErrorMessage(this,message);
         errorMessage = message;
      }
      else if (parent != null)
         parent.reportError(errorMessage);
      else {
         errorMessage = message;
         System.out.println("***** Error:  " + errorMessage);
      }
   }
   
   /**
    * Clear the error message.
    */
00331    protected void clearErrorMessage() {
      if (errorReporter != null)
         errorReporter.clearErrorMessage();
      else if (parent != null)
            parent.clearErrorMessage();
      errorMessage = null;
   }
   
   /**
    * Should be called by the ErrorReporter if the ErrorReporter clears the error itself.
    * (This is only used to avoid repeatedly setting the same error message, during an
    * animation for example.)
    */
00344    public void errorCleared() {
      errorMessage = null;
   }
   
   /**
    * Method required by InputObject interface; in this class, calls the same method
    * recursively on any input objects controlled by this controller.  This is meant to 
    * be called by JCMPanel.gatherInputs().
    */
00353     public void notifyControllerOnChange(Controller c) {
      if (inputs != null) {
         int top = inputs.size();
         for (int i = 0; i < top; i++)
            ((InputObject)inputs.elementAt(i)).notifyControllerOnChange(c);
      }
    }
    
    /**
     * Calles notifyControllerOnChange(this).  That is, it sets all the InputObjects in
     * this Controller, and in subcontrollers, to notify this Controller when they change.
     */
00365     public void gatherInputs() {
       notifyControllerOnChange(this);
    }

} // end class Controller

Generated by  Doxygen 1.6.0   Back to index