import java.awt.*;
import java.applet.Applet;

/*
 * The basic structure of this code originates in a Sun
 *  Microsystems code fragment which receives a mouse click in a frame
 *  and prints the coordinates of the click and a dot where the click was.
 *  It has been completely modified to implement an
 *  interactive demo of a theory of musical meter.
 *  (Christopher F. Hasty, *Meter as Rhythm*, Oxford, 1997)
 *  The user can input 3 click-move-click sequences to define three events,
 *  and the program will give a graphical representation of these events
 *  together with some commentary and instructions.
 *  Program design: John Roeder
 *  Java programming: Tom Roeder, 1998.
 */

public class Rhythm extends Applet {
  CoordinateArea coordinateArea; // the area receiving the clicks
  TextArea label; // to hold the instructions to the user
  Button button1, button2; // the "Back one step" and "Restart" buttons
  TextArea textArea; // to hold the commentary
  // TextArea results; // not required any more: used to hold results
  final int commWidth = 60;
  final int commHeight = 8;
  Point[] points; // the array of events
  int wherein; // where we are in the array
  String buffer = ""; // a handy constant to have, given what happens in init
  //final int row1Begin = 0; // result-associated comments
  //final int row1End = 53;
  //final int row2Begin = 54;
  //final int row2End = 107;
  //final int row3Begin = 108;
  //final int row3End = 161;
  //final int row4Begin = 162;
  //final int row4End = 215;
  //final int row5Begin = 216;
  //final int row5End = 269;

  /* The messages to go into the commentary box */
  final static String commIntro = "This applet demonstrates the concepts in
Chapter 7 of\n Christopher Hasty\'s \"Meter as Rhythm\". Imagine time 0\n
as an instant that is a potential beginning of a sound,\n yet prior to and
independent of it.";
  final static String comm1MoveStart = "The first sound begins, but time 0
will not be a beginning\n until it is past.";
  final static String comm1MoveP5 = "The first sound is becoming. Time 0
becomes its beginning.\n \"Projective potential\"--the potential of a
duration to\n be reproduced by a successive duration--accumulates,\n as
indicated by the solid arc.";
  final static String comm1MoveA5 = "The first sound\'s duration is so long
that it is\n \"mensurally indeterminate\"--it has lost its projective\n
potential to be reproduced.";
  final static String commEnd1Right = "The first sound ends. Its duration
is \"mensurally\n determinate\" because it has the potential for being\n
precisely reproduced.";
  final static String commEnd1Wrong = "The first sound ends; it is too long
to have\n projective potential.";
  final String commTooBack = buffer;
  final static String commPLimit = "There is a pause between the first two
sounds. Its duration\n is relatively indeterminate, if our attention is
focused\n on the beginning of sounds. The growing arc indicates that\n the
duration of the first sound *plus* the following\n silence itself has the
\"projective potential\" to be\n reproduced.";
  final static String commPLimitEvent = "This beginning of the second sound
\"realizes\" the\n projective potential of the duration begun by the
first\n event\'s attack. The solid arrow represents this\n projective
potential. The event now beginning has the\n potential to reproduce this
past duration. The dotted arc,\n extending for this duration into the
future, symbolizes this\n \"projected potential\".";
  final static String commALimitEvent = "The second sound begins. It is so
long since the beginning\n of the first event that the interonset duration
is mensurally\n indeterminate--it has no potential to be reproduced--so\n
there is no projection.";
  final static String commP2bMove = "The accumulating duration of the
second sound is realizing\n the projected potential (symbolized by the
dashed arc) of\n the first interonset duration. Simultaneously the
present\n event accumulates its own projective potential\n (represented by
the growing solid arc) to be reproduced by\n a successive, third event.";
  final static String commA2bPbPLimitMove = "The second sound exceeds the
duration projected at its onset;\n the projection is not clearly realized,
as indicated by\n the X through the dashed arc.";
  final static String commAbPLimitMoveIn2 = "The second sound is so long
that it is mensurally\n indeterminate. (The projection of the first
interonset\n duration is not realized.)";
  final static String commP2bEvent = "The second sound ends. Its duration
is \"mensurally\n determinate\" because it has the potential for being\n
precisely reproduced. But it does not affect the\n projection of the first
interonset duration, shown by the\n arrow and dashed arc";
  final static String commA2bPbLimitEvent = "The second sound exceeds the
duration projected at its\n onset. The projection is not clearly realized,
as\n indicated by the X through the dashed arc. The projective\n potential
of the duration initiated by the second\n sound\'s beginning continues to
accumulate.";
  final static String commAbPLimitEventIn2 = "The second sound is so long
that it is mensurally\n indeterminate. Since the projected potential of the
first\n interonset duration is denied there is no projection at all.";
  final String commPcMove = buffer;
  final static String commAcPEbPLimitMove = "The silence between the second
and third sounds is\n relatively indeterminate if our attention is focused
on the\n sounds\' beginnings. The growing arc indicates that the\n duration
from the beginning of the second sound up to now,\n including the silence,
has \"projective potential\" to be\n reproduced.";
  final static String commAbPLimitMove = "The time since the beginning of
the second sound is\n mensurally indeterminate, having no projective
potential\n to be reproduced.";
  final static String commdP2bAE175bEvent = "The beginning of the third
sound is earlier than projected.\n The second interonset duration is
shorter than, but at\n least three-fourths of the first interonset
duration.\n We feel an *acceleration* because we sense the realization\n of
the first projected duration even as we also perceive\n the difference
between the two durations.";
  final static String commdE2bEvent = "Since the third sound begins exactly
at the end of the\n projected duration (the upper dashed arc), the
projected\n duration is \"realized\". A new projection is created,\n
conditioned by the first, in which the second interonset\n duration has the
projective potential (the lower arrow)\n to be reproduced.";
  final static String commAbPLimitEvent = "The projective potential of the
first interonset duration\n (the dashed arc) is realized, but the
projective potential\n of the second interonset duration is not, since it
is\n mensurally indeterminate. Because the third sound begins\n much later
than projected, we may come to feel \"hiatus\"\n (symbolized by the double
bar)--a break between the\n realization of projected potential and a new
beginning. A\n new and relatively unconditioned potential emerges\n from
the beginning of the third sound.";
  final static String commdP175bEvent1 = "The projection of the first
interonset duration is realized.\n Another projection (the rightmost arrow
and dashed arc)\n can be completed within the promised duration, so may\n
enhance its mensural determinacy. The emergence of a new\n beginning, shown
in parentheses, would clarify this.";
  final static String commdP175bEvent2 = "In this interpretation the accent
symbolizes an unequivocal\n second beginning that denies the projection of
the first\n interonset duration in order to realize a larger projective\n
potential, symbolized by the large arrow.";
  final static String commdB2t25Event = "The beginning of the third sound
is slightly later than\n projected. We hear a *deceleration* because we
sense the\n realization of the first projected duration even as we\n also
perceive the difference between the two durations.";
  final static String commA25bPbPLimitEvent = "The third sound begins
somewhat later than projected. A new\n projection, indicated by the lowest
arrow and dashed arc,\n emerges, breaking off from the emerging first
projection.\n We reject the relevance of the first projection to the\n
mensural determinacy of the second interonset duration.";

  /* The messages to go into the instruction box */
  final static String instrStart = "You may perform graphically up to three
successive sounds\n by clicking and moving the mouse. First, click the
mouse\n at time 0, the leftmost point, but don\'t move it.";
  final static String instr1MoveStart = "Perform the first sound by moving
the mouse to the right.";
  final static String instr1MoveP5 = "End the first sound by clicking the
mouse.";
  final static String instr1MoveA5 = "To make the first sound's duration
determinate, move the\n mouse back to the left. Or click to end the
sound.";
  final static String instrEnd1Right = "To begin the second sound, click
the mouse.";
  final static String instrEnd1Wrong = "Click on the Restart button to try
again.";
  final static String instrTooBack = "Click the mouse at the end of the
first sound or later.";
  final static String instrPLimit = "Click the mouse to begin the second
sound.";
  final static String instrPLimitEvent = "Perform the second sound by
moving the mouse to the right.";
  final static String instrALimitEvent = "Click on the \"Back one step\"
button to select an earlier\n beginning for the second sound, or click
\"Restart\".";
  final static String instrP2bMove = "Click the mouse to end the second sound.";
  final static String instrA2bPbPLimitMove = "Click the mouse to end the
second sound.";
  final static String instrAbPLimitMoveIn2 = "Move the mouse to the left to
shorten the second sound,\n or click the mouse to end it.";
  final static String instrP2bEvent = "Click on the \"Back one step\"
button to select an earlier\n beginning for the second sound, or click
\"Restart\".";
  final static String instrA2bPbLimitEvent = "Click the mouse to begin the
third sound.";
  final static String instrAbPLimitEventIn2 = "Click on the \"Back one
step\" button to define a\n different second sound or \"Restart\" to start
all over.";
  final static String instrPcMove = "Click the mouse at the end of the
second sound or later.";
  final static String instrAcPEbPLimitMove = "Click the mouse button to
begin the third sound.";
  final static String instrAbPLimitMove = "Click the mouse button to begin
the third sound (earlier if\n you want a projection).";
final static String instrdP2bAE175bEvent = "Click on \"Back one step\" to
define a different third sound\n or \"Restart\" to begin again.";
  final static String instrdE2bEvent =  "Click on \"Back one step\" to
define a different third sound\n or \"Restart\" to begin again.";
  final static String instrAbPLimitEvent =  "Click on \"Back one step\" to
define a different third sound\n or \"Restart\" to begin again.";
  final static String instrdP175bEvent1 = "Click anywhere to see an
alternate interpretation.";
  final static String instrdP175bEvent2 =  "Click on \"Back one step\" to
define a different third sound\n or \"Restart\" to begin again.";
  final static String instrdB2t25Event =  "Click on \"Back one step\" to
define a different third sound\n or \"Restart\" to begin again.";
  final static String instrA25bPbPLimitEvent =  "Click on \"Back one step\"
to define a different third sound\n or \"Restart\" to begin again.";

  public void init() {

    /* This gridbag code is used to set everything up in its
     *  proper place.
     */
    GridBagLayout layout = new GridBagLayout();
    setLayout(layout);
    GridBagConstraints c = new GridBagConstraints();
    /* First we want a text area into which go the various explanations
     *  (The text area for instructions is below the graphics)
     */
    textArea = new TextArea(commIntro, commHeight, commWidth);
    c.fill = GridBagConstraints.NONE;
    c.gridwidth = GridBagConstraints.REMAINDER;
    layout.setConstraints(textArea, c);
    textArea.setEditable(false);
    add(textArea);


    /* Here we add the main, controlling part of the applet: the
     *  coordinateArea (see class below).  It serves to receive the
     *  events and display the results.
     */
    coordinateArea = new CoordinateArea(this);
    c.fill = GridBagConstraints.BOTH;
    c.weighty = 1.0;
    c.gridwidth = GridBagConstraints.REMAINDER; //end row
    layout.setConstraints(coordinateArea, c);
    add(coordinateArea);

    /* The data for each of the points to be defined later
       results = new TextArea("           | First Sound | Second Sound |
Third Sound ------------------------------------------------------begin
time |             |              |             end time   |             |
|             duration   |             |              |
", 5, 54, TextArea.SCROLLBARS_NONE);
       c.fill = GridBagConstraints.NONE;
       c.weightx = 1.0;
       c.weighty = 0.0;
       results.setEditable(false);
       gridBag.setConstraints(results, c);
       add(results);
    */


    /* This label is merely used for displaying status and other
     *  handy pieces of information.
     */
    label = new TextArea(instrStart, 2, 70);
    c.fill = GridBagConstraints.HORIZONTAL;
    c.weightx = 1.0;
    c.weighty = 0.0;
    label.setEditable(false);
    layout.setConstraints(label, c);
    add(label);

    /* I then added two buttons which do exactly as they claim (see action
     *  code below).
     */
    button1 = new Button("Back one step");
    add(button1);

    button2 = new Button("Restart");
    add(button2);


    // I initialize the points and their associated index pointer
    points = new Point[6];
    for (int i = 0; i < 6; i++)
      points[i] = null;
    wherein = -1;

    // make the buffer big enough to erase anything else
    for (int i = 0; i< 200; i++)
      buffer += " ";

    validate();
  }

  /* This method is the main workhorse of the class; it serves to control
   *  the points and their array, to the point of deciding whether or not
   *  to add a new one and printing associated status messages.  It is
   *  called from the coordinateArea class.
   */
  public void newPoint(int x, int y) {
    for(int i = 0; i <= wherein; i++) { // for each earlier point
      if (points[i].x >= x) { // make sure it preceedes the current
	//...otherwise complain and die
	label.replaceText("Not a well-defined event. Try again", 0, 200);
	coordinateArea.backOne(wherein + 2);
	repaint();
	return;
      }
    }
    //...but if we're OK, increment the index and add the point
    wherein++;
    if (wherein != 0)
      points[wherein] = new Point(x, y);
    else
      points[wherein] = new Point(0, y);
    // print the status message
    //int val =
Math.round((x/(coordinateArea.unitLength/(float)(coordinateArea.interval))))
;
    /*switch (wherein) {
      case 1:
      results.replaceText(String.valueOf(val), row3Begin + 13, row3Begin + 26);
      break;
      case 2:
      results.replaceText(String.valueOf(val), row4Begin + 13, row4Begin + 26);
      int val2 = Math.round((points[wherein -
1].x)/(coordinateArea.unitLength/(float)(coordinateArea.interval)));
      results.replaceText(String.valueOf(val2 - val), row3Begin + 13,
row3Begin + 26);
      break;
	}*/
    /*  label.replaceText("Event " + ((wherein + 2)/2) +
	  " began at " +
	
Math.round((points[wherein-1].x)/(coordinateArea.unitLength/(float)(coordina
teArea.interval))) +
	  " and ended at " +
	
Math.round(x/(coordinateArea.unitLength/(float)(coordinateArea.interval))),
0, 200 );
    */
    //...and repaint
    repaint();
  }

  public void setMessages(int x) {
    switch (x) {
      case -1:
	label.replaceText("", 0, 100);
	textArea.replaceText(buffer+buffer, 0, 400);
	break;
      case 0:
	label.replaceText(instrStart, 0, 100);
	textArea.replaceText(commIntro, 0, 400);
	break;
      case 1:
	label.replaceText(instr1MoveStart, 0, 100);
	textArea.replaceText(comm1MoveStart, 0, 400);
	break;
      case 2:
	label.replaceText(instr1MoveP5, 0, 100);
	textArea.replaceText(comm1MoveP5, 0, 400);
	break;
      case 3:
	label.replaceText(instr1MoveA5, 0, 200);
	textArea.replaceText(comm1MoveA5, 0, 400);
	break;
      case 4:
	label.replaceText(instrEnd1Right, 0, 100);
	textArea.replaceText(commEnd1Right, 0, 400);
	break;
      case 5:
	label.replaceText(instrEnd1Wrong, 0, 100);
	textArea.replaceText(commEnd1Wrong, 0, 400);
	break;
      case 6:
	label.replaceText(instrTooBack, 0, 100);
	textArea.replaceText(commTooBack, 0, 400);
	break;
      case 7:
	label.replaceText(instrPLimit, 0, 100);
	textArea.replaceText(commPLimit, 0, 400);
	break;
      case 8:
	label.replaceText(instrPLimitEvent, 0, 100);
	textArea.replaceText(commPLimitEvent, 0, 400);
	break;
      case 9:
	label.replaceText(instrALimitEvent, 0, 100);
	textArea.replaceText(commALimitEvent, 0, 400);
	break;
      case 10:
	label.replaceText(instrP2bMove, 0, 100);
	textArea.replaceText(commP2bMove, 0, 400);
	break;
      case 11:
	label.replaceText(instrA2bPbPLimitMove, 0, 100);
	textArea.replaceText(commA2bPbPLimitMove, 0, 400);
	break;
      case 12:
	label.replaceText(instrAbPLimitMoveIn2, 0, 100);
	textArea.replaceText(commAbPLimitMoveIn2, 0, 400);
	break;
      case 13:
	label.replaceText(instrP2bEvent, 0, 100);
	textArea.replaceText(commP2bEvent, 0, 400);
	break;
      case 14:
	label.replaceText(instrA2bPbLimitEvent, 0, 100);
	textArea.replaceText(commA2bPbLimitEvent, 0, 400);
	break;
      case 15:
	label.replaceText(instrAbPLimitEventIn2, 0, 100);
	textArea.replaceText(commAbPLimitEventIn2, 0, 400);
	break;
      case 16:
	label.replaceText(instrPcMove, 0, 100);
	textArea.replaceText(commPcMove, 0, 400);
	break;
      case 17:
	label.replaceText(instrAcPEbPLimitMove, 0, 100);
	textArea.replaceText(commAcPEbPLimitMove, 0, 400);
	break;
      case 18:
	label.replaceText(instrAbPLimitMove, 0, 100);
	textArea.replaceText(commAbPLimitMove, 0, 400);
	break;
      case 19:
	label.replaceText(instrdP2bAE175bEvent, 0, 100);
	textArea.replaceText(commdP2bAE175bEvent, 0, 400);
	break;
      case 20:
	label.replaceText(instrdE2bEvent, 0, 100);
	textArea.replaceText(commdE2bEvent, 0, 400);
	break;
      case 21:
	label.replaceText(instrAbPLimitEvent, 0, 100);
	textArea.replaceText(commAbPLimitEvent, 0, 400);
	break;
      case 22:
	label.replaceText(instrdP175bEvent1, 0, 100);
	textArea.replaceText(commdP175bEvent1, 0, 400);
	break;
      case 23:
	label.replaceText(instrdP175bEvent2, 0, 100);
	textArea.replaceText(commdP175bEvent2, 0, 400);
	break;
      case 24:
	label.replaceText(instrdB2t25Event, 0, 100);
	textArea.replaceText(commdB2t25Event, 0, 400);
	break;
      case 25:
	label.replaceText(instrA25bPbPLimitEvent, 0, 100);
	textArea.replaceText(commA25bPbPLimitEvent, 0, 400);
	break;
    }
    repaint();
  }

  // Just good programming style: private data and public accessors
  //  I don't worry about array bounds checking, 'cause Java does it for me
  public Point getPoint(int which) {
    return points[which];
  }


  // The button pressing procedure.
  public boolean action(Event evt, Object arg) {
    if ("Back one step".equals(arg)) { // if we want to step back
      if (wherein >= 0) { // make sure we really can
	points[wherein--] = null;
	if (wherein == 4)
	  points[wherein--] = null;
	// print a status message and update the display
	label.replaceText("Stepped back", 0, 100);
	coordinateArea.repaint();
	coordinateArea.backOne(wherein + 1);
	return true;
      }
    }
    if ("Restart".equals(arg)) { // if we want to restart
      // just reset all the data
      wherein = -1;
      for (int i = 0; i < 6; i++)
	points[i] = null;
      //... print a status message, and repaint
      setMessages(0);
      coordinateArea.repaint();
      coordinateArea.restart();
      return true;
    }
    return false;
  }

  public int getWherein() {
    return wherein;
  }

  public void main(String argv[]) {
    init();
  }

}


// The class to deal with the main mouse events and to handle the display
class CoordinateArea extends Canvas {
  Rhythm controller; // a controlling object
  Point[] line1; // the line connecting the first two points
  Point[] line2; // ditto for the second two
  Point[] line3; // same here for the third
  boolean arcing1 = false; // is the first arc on?
  boolean arcing2 = false; // is the second?
  boolean in1 = false; // are we in the first line?
  boolean in2 = false; // second?
  boolean done = false; // have we finished (in one of the many ways)?
  Point moveArcPos = null; // where the end of the current arc is
  public final static int interval = 5; // constant for drawing
  public final static int unitLength = 400/3; // same here
  int X1 = -1; // where is the X through the first dashed arc?
  boolean redrawDashedArc1 = false; // do we resize the first dashed arc?
  boolean clickToChange = false; // can we click anywhere to switch views?
  boolean addParenthShort = false; // do we add a hypothetical segment?
  boolean inAlternativeView = false; // are we in a second view?
  boolean drawTracks = false; // do we draw the railroad tracks?

  public void restart() {

    // set all the points to null
    for(int i = 0; i < 2; i++) {
      line1[i] = null;
      line2[i] = null;
      line3[i] = null;
    }

    // and reset all the data to its default state
    arcing1 = arcing2 = in1 = in2 = done = false;
    moveArcPos = null;
    redrawDashedArc1 = clickToChange = addParenthShort = false;
    inAlternativeView = drawTracks = false;
    X1 = -1;

    // then redraw
    repaint();
  }

  public void backOne(int wherein) {
    switch (wherein) {
      case 0:
	// only one point was defined, so just reset everything
	restart();
	controller.setMessages(0);
	break;
      case 1:
	// two points were defined,
	// so put us back into drawing the first line
	in1 = true;
	// reset the drawing point
	line1[1] = null;
	// set the default message (this'll change very quickly)
	controller.setMessages(2);
	// turn off any possible X's
	X1 = -1;
	// make sure the arcing is set
	arcing1 = true;
	// we're not done, either
	done = false;
	break;
      case 2:
	// three points were defined,
	// so reset the third point to null
	line2[0] = null;
	// fix the arcing parameters
	arcing1 = true;
	arcing2 = false;
	// get us out of the second event
	in2 = false;
	// turn off any possible X's
	X1 = -1;
	// set the default message
	controller.setMessages(7);
	break;
      case 3:
	// four points were defined
	line2[1] = null;
	// so put us back into the second event
	in2 = true;
	// turn off the X's
	X1 = -1;
	// set the default message
	controller.setMessages(10);
	// make sure the arcing is set correctly
	arcing2 = true;
	// we're not done anymore
	done = false;
	break;
      case 4: case 5:
	// all 6 points were defined, since the 5th event generates a 6th
	// so set the second line back to null
	line3[0] = null;
	line3[1] = null;
	// (note that we can never be in the third event)
	// set the second arc to begin again
	arcing2 = true;
	// we're not done anymore
	done = false;
	// nor are we in a possible alternate reality ;-)
	inAlternativeView = false;
	// nor can we click to change
	clickToChange = false;
	// and the next three variables cannot possibly be set yet
	addParenthShort = false;
	redrawDashedArc1 = false;
	drawTracks = false;
	// so set the default message
	controller.setMessages(13);
	break;
    }
    repaint();
  }

  // a standard constructor
  public CoordinateArea(Rhythm controller) {
    super();
    // create the new lines and make them null
    line1 = new Point[2];
    line2 = new Point[2];
    line3 = new Point[2];
    for(int i = 0; i < 2; i++) {
      line1[i] = null;
      line2[i] = null;
      line3[i] = null;
    }

    // set the controller
    this.controller = controller;
  }

  // the mouseDown event handler
  public boolean mouseDown(Event event, int x, int y) {
    // if we can go to another reality :-)
    if (clickToChange) {
      // set that fact, and fix it so nothing else can happen
      inAlternativeView = true;
      clickToChange = false;
      done = true;
      controller.setMessages(23);
      repaint();
    }

    // if we're not done yet
    if (!done) {
      // just pass it off to the more intelligent code in Rhythm
      controller.newPoint(x, y);
      // and then fix the condition variables
      switch (controller.getWherein()) {
        case 0:
	  // we just started the first event
	  // so start arcing
	  arcing1 = true;
	  // the point must be at 0
	  line1[0] = new Point(0,y);
	  // note that we're in the first event
	  in1 = true;
	  // set the appropriate message
	  controller.setMessages(1);
	  break;
        case 1:
	  // we just ended the first event
	  // so note that this happened
	  in1 = false;
	  // make sure we're not indeterminate, and finish if we are
	  if ((x > 0) && (x <= unitLength))
	    controller.setMessages(4);
	  else {
	    controller.setMessages(5);
	    done = true;
	  }
	  // get the new point
	  line1[1] = new Point(x,y);
	  break;
        case 2:
	  // we just started the second event
	  // so stop the first arc and begin the second
	  arcing1 = false;
	  arcing2 = true;
	  // note that we're in the second event
	  in2 = true;
	  // now make sure we haven't gone too far in the intervening space
	  int a = controller.getPoint(1).x;
	  if ((x >= a) && (x < (a + unitLength)))
	    controller.setMessages(8);
	  else if (x >= (a + unitLength)) {
	    controller.setMessages(9);
	    done = true;
	  }
	  // and get the next point
	  line2[0] = new Point(x,y);
	  break;
        case 3:
	  // we just ended the second event
	  // so note this fact
	  in2 = false;
	  // now check to see what the distance relationships are
	  // and print the correct commentary and instructions
	  int b = controller.getPoint(2).x;
	  if (x < (2*b))
	    controller.setMessages(13);
	  else if ((x > 2*b) && (x < b + unitLength)) {
	    controller.setMessages(14);
	    done = true;
	  } else if (x > b + unitLength) {
	    controller.setMessages(15);
	    done = true;
	  } else if (x == 2*b)
	    controller.setMessages(20);
	  // now get the new point
	  line2[1] = new Point(x,y);
	  break;
        case 4:
	  // we're just starting the third event now; we'll end it too
	  // so turn off the arcing
	  arcing2 = false;
	  // get the beginning of the second event
	  int where = controller.getPoint(2).x;
	  line3[0] = new Point(x,y);
	  if ((x < 2*where) && (x >= 1.75*where)) {
	    controller.setMessages(19);
	    controller.newPoint(2*where - 5, y);
	    line3[1] = new Point(2*where - 5, y);
	    redrawDashedArc1 = true;
	  } else if ((x < 1.75*where) && (x >= controller.getPoint(3).x)) {
	    controller.setMessages(22);
	    clickToChange = true;
	    addParenthShort = true;
	    controller.newPoint(2*where - 5, y);
	    line3[1] = new Point(2*where - 5, y);
	  } else if (x == 2*where) {
	    controller.setMessages(20);
	    controller.newPoint(x + 20, y);
	    line3[1] = new Point(x + 20, y);
	  } else if (x > where + unitLength) {
	    controller.newPoint(x + 20, y);
	    line3[1] = new Point(x + 20, y);
	    controller.setMessages(21);
	    drawTracks = true;
	  } else if ((x < 2.5*where) && (x > 2*where)) {
	    controller.setMessages(24);
	    controller.newPoint(x + 20, y);
	    line3[1] = new Point(x + 20, y);
	    redrawDashedArc1 = true;
	  } else if ((x > 2.5*where) && (x < where + unitLength)) {
	    controller.newPoint(x + 20, y);
	    line3[1] = new Point(x + 20, y);
	    controller.setMessages(25);
	  } else if (x > where + unitLength) {
	    controller.newPoint(x + 20, y);
	    line3[1] = new Point(x + 20, y);
	    controller.setMessages(26);
	    drawTracks = true;
	  }
	  done = true;
	  break;
        case 5:
	  done = true;
	  break;
      }
      repaint();
    }
    return false;
  }

  // The mouseMove code
  public boolean mouseMove(Event evt, int x, int y) {
    if (!done) {
      if (in1) {
	line1[1] = new Point(x,y);
	if (x == 0)
	  controller.setMessages(1);
	else if (x <= unitLength) {
	  controller.setMessages(2);
	  arcing1 = true;
	} else {
	  controller.setMessages(3);
	  arcing1 = false;
	}
	repaint();
      }
      if (in2) {
	int b = line2[0].x;
	if (x >= b)
	  line2[1] = new Point(x,y);
	else
	  line2[1] = null;
	if (x <= 2*b)
	  X1 = -1;
	if (x < 2*b)
	  controller.setMessages(10);
	else if ((x > 2*b) && (x < (b + unitLength))) {
	  controller.setMessages(11);
	  X1 = b;
	  arcing2 = true;
	} else if (x > (b + unitLength)) {
	  controller.setMessages(12);
	  arcing2 = false;
	}
	repaint();
      }

      /*     if (in3)
	     if (x >= line3[0].x)
	     line3[1] = new Point(x,y);
	     else
	     line3[1] = null;
      */

      if (controller.getPoint(2) == null) {
	if (controller.getPoint(0) != null) {
	  if (x > controller.getPoint(0).x + unitLength)
	    arcing1 = false;
	  else if (!in1)
	    arcing1 = true;
	}
      } else {
	if (x > controller.getPoint(2).x + unitLength)
	  arcing2 = false;
	else if (!in1 && !in2)
	  arcing2 = true;
      }

      if (in2 || arcing2) {
	arcing1 = false;
      }

      if (arcing1 || arcing2) {
	moveArcPos = new Point(x, y);
	if (arcing1 && !in1)
	  if (x < controller.getPoint(1).x)
	    controller.setMessages(6);
	  else if ((x > controller.getPoint(1).x) &&
		   (x < controller.getPoint(1).x + unitLength))
	    controller.setMessages(7);
	if (arcing2 && !in2)
	  if (x < controller.getPoint(3).x)
	    controller.setMessages(16);
	  else if ((x > controller.getPoint(3).x) &&
		   (x < controller.getPoint(3).x + unitLength))
	    controller.setMessages(17);
	  else if (x > controller.getPoint(3).x + unitLength) {
	    controller.setMessages(18);
	  }
      }
      repaint();
    }
    return false;
  }

  /* a simple helper routine to draw a 180 degree dashed arc on
   *  a graphics object g, starting at start and going for length.
   */
  public void drawDashedArcSpecial(Graphics g, int start, int length, int y) {
    for(int i = 0; i < 17; i += 2)
      g.drawArc(start, y, length, 15, -(i*10), 10);
  }

  // Another helper, this time to draw an arrowhead at the end of a line
  public void drawArrowhead(Graphics g, int x, int y) {
    g.drawLine(x, y, x - 5, y + 5);
    g.drawLine(x, y, x + 5, y + 5);
    g.drawLine(x - 5, y + 5, x + 5, y + 5);
  }

  // A helper to draw railroad tracks
  public void drawParallel(Graphics g, int whereX, int whereY) {
    g.drawLine(whereX - 5, whereY - 5, whereX - 5, whereY + 5);
    g.drawLine(whereX - 8, whereY - 5, whereX - 8, whereY + 5);
  }

  // A helper to draw an arrow
  public void drawArrow(Graphics g, int where) {
    g.drawArc(where, 60, 30, 15, -90, -90);
    g.drawLine(where + 10, 70, where + 15, 75);
    g.drawLine(where + 10, 80, where + 15, 75);
  }

  // A helper to draw an X through an arc
  public void addX(Graphics g, int start) {
    int halfway = 3*start/2;
    g.drawLine(halfway - 10, 65, halfway + 10, 75);
    g.drawLine(halfway - 10, 75, halfway + 10, 65);
  }

  // A helper to draw an "accel" centered at xEnd
  public void drawAccel(Graphics g, int xEnd) {
    g.drawString("accel.", xEnd - 20, 110);
  }

  public void drawRall(Graphics g, int xEnd) {
    g.drawString("rall.", xEnd - 20, 110);
  }

  // a helper to draw an accent centered at center
  public void drawAccent(Graphics g, int center, int height) {
    g.drawLine(center - 2, height - 2, center + 2, height);
    g.drawLine(center - 2, height + 2, center + 2, height);
  }

  // a helper to draw a hypothetical parenthesized point
  public void drawShortParenth(Graphics g, int center) {
    g.drawString("(", center, 60);
    g.drawLine(center + 2, 55, center + 30, 55);
    g.drawString(")", center + 32, 60);
  }

  /* This code will be called an umpteen number of times, since it is used
   *  in the execution of repaint().
   */
  public void paint(Graphics g) {
    // draw a number line
    int unitLength2 = unitLength/interval;
    g.drawLine(0, 10, 3*unitLength, 10);
    for (int n = 0; n < 3*interval; n++)
      g.drawLine(n*unitLength2, 8, n*unitLength2, 12);
    for (int k = 0; k < 4; k++) {
      g.drawLine(k*unitLength2*interval, 5, k*unitLength2*interval, 15);
      switch (k) {
        case 0:
	  g.drawString(Integer.toString(k),
		       k*unitLength + 3, 25);
	  break;
        case 1:
	  g.drawString("Lim",
		       k*unitLength - 10, 25);
	  break;
        case 2:
	  g.drawString(Integer.toString(k) + "*Lim",
		       k*unitLength - 20, 25);	
	  break;
        case 3:
	  g.drawString(Integer.toString(k) + "*Lim",
		       k*unitLength - 35, 25);	
	  break;
      }
    }

    // draw any points stored in the array
    for(int i = 0; i < 6; i++) {
      Point point = controller.getPoint(i);
      if (point != null)
	g.fillRect(point.x - 1, 50, 2, 2);
    }

    // draw the lines as we drag
    if (line1[0] != null && line1[1] != null
	&& controller.getPoint(1) == null)
      g.drawLine(line1[0].x, 50, line1[1].x, 50);
    if (line2[0] != null && line2[1] != null
	&& controller.getPoint(3) == null)
      g.drawLine(line2[0].x, 50, line2[1].x, 50);
    if (line3[0] != null && line3[1] != null
	&& controller.getPoint(5) == null)
      g.drawLine(line3[0].x, 50, line3[1].x, 50);

    //... and then once they're stable
    for(int i = 0; i <= 4; i += 2) {
      Point start = controller.getPoint(i);
      Point end = controller.getPoint(i+1);
      if (start != null && end != null)
	g.drawLine(start.x, 50, end.x, 50);
    }

    if (!inAlternativeView) {
      // draw the arcs if we're arcing
      if (arcing1) {
	if (moveArcPos != null) {
	  int end = moveArcPos.x;
	  if (line1[1] != null) {
	    if (line1[1].x <= end)
	      g.drawArc(0, 55, 2*end , 15, -90, -90);
	    else
	      g.drawArc(0, 55, 2*line1[1].x, 15, -90, -90);
	  }
	}
      } else if (arcing2) {
	if (line2[0] != null && moveArcPos != null) {
	  int start = line2[0].x;
	  int end = moveArcPos.x;
	  if (line2[1] != null) {
	  	if (in2)
	  		 g.drawArc(start, 80, 2*(end - start), 15, -90, -90);
	  	else if (line2[1].x <= end)
	  		 g.drawArc(start, 80, 2*(end - start), 15, -90, -90);
	  	else
	  		 g.drawArc(start, 80, 2*(controller.getPoint(3).x - start),
			  15, -90, -90);			
	  }	
	}
      }

      // draw railroad tracks and an arrow if we're supposed to
      if (drawTracks) {
	drawParallel(g, controller.getPoint(2).x + unitLength, 50);
	drawArrow(g, line3[0].x);
	
      }

      // Draw an X if we're supposed to
      if (X1 != -1)
	addX(g, X1);

      // add an extra hypothetical event if we're supposed to
      if (addParenthShort) {
	int b = controller.getPoint(2).x;
	drawShortParenth(g, 2*b);
      }

      // Draw arcs between the first and third points and the second and fourth
      // Also draw a dashed arc projection.
      for (int m = 0; m <= 2; m += 2) {
	Point point1 = controller.getPoint(m);
	Point point2 = controller.getPoint(m+1);
	Point point3 = controller.getPoint(m+2);
	if ((point1 != null) && (point2 != null) && (point3 != null)) {
	  if (m == 0) {
	    int xStart = point1.x;
	    int xEnd = point3.x;
	    g.drawArc(xStart, 55, xEnd - xStart, 15, 0, -180);
	    drawArrowhead(g, xEnd, 55);
	    if (redrawDashedArc1) {
	      xEnd = controller.getPoint(4).x;
	      if (xEnd > 2*point3.x)
		drawRall(g, xEnd);
	      else
		drawAccel(g, xEnd);
	      for(int i = 0; i < 17; i += 2)
		g.drawArc(point3.x, 55, xEnd - point3.x, 15, -(i*10), 10);
	    } else
	      drawDashedArcSpecial(g, xEnd, xEnd - xStart, 55);
	  }
	  if (m == 2) {
	    int xStart = point1.x;
	    int xEnd = point3.x;
	    g.drawArc(xStart, 80, xEnd - xStart, 15, 0, -180);
	    drawArrowhead(g, xEnd, 80);
	    drawDashedArcSpecial(g, xEnd, xEnd - xStart, 80);
	  }
	}
      }
    } else if (controller.getPoint(4) != null) {
      int xStart1 = controller.getPoint(0).x;
      int xEnd1 = controller.getPoint(2).x;
      int xStart2 = xStart1;
      int xEnd2 = controller.getPoint(4).x;
      g.drawArc(xStart1, 55, xEnd1 - xStart1, 15, 0, -180);
      drawArrowhead(g, xEnd1, 55);
      for(int i = 8; i < 17; i += 2)
	g.drawArc(xEnd1, 55, xEnd2 - xEnd1, 15, -(i*10), 10);
      addX(g, (xEnd1 + xEnd2)/3);
      drawAccent(g, xEnd2, 45);
      g.drawArc(xStart2, 80, xEnd2 - xStart2, 15, 0, -180);
      drawArrowhead(g, xEnd2, 80);
      drawDashedArcSpecial(g, xEnd2, xEnd2 - xStart2, 80);
    }
  }
}


// FIN









