Course notes for April 5-7, and homework assignment due April 14:

In class this week we discussed abstract classes. An abstract class is missing the implementation of one or more methods, which means it cannot be instantiated (ie: you can't use the "new" operator.

For example, the class Shape below is abstract because its two methods contains and draw are abstract -- these two methods have a declaration part, but not an implementation part:

public abstract class Shape
{
   ...

   public abstract boolean contains(int x, int y);
   public abstract void draw(Graphics g);
}
Even though we cannot instantiate Shape objects with the new Shape(...) operator, it is, nonetheless, ok for an abstract class to contain a constructor. When we extend this class the constructor will indeed come in handy. Here was the example we showed in class:

public abstract class Shape
{
   int x, y, width, height;
   Color color = gray;

   public Shape(int x, int y, int width, int height) {
       setBounds(x, y, width, height);
   }

   void setBounds(int x, int y, int width, int height) {
       this.x = x;
       this.y = y;
       this.width = width;
       this.height = height;
   }

   public abstract boolean contains(int x, int y);
   public abstract void draw(Graphics g);
}
We can extend abstract class Shape to create an instantiable class, such as Box, by providing implementations for all of the abstract methods:

public class Box extends Shape
{
   public Box(int x, int y, int width, int height) {
      super(x, y, width, height);
   }

   public boolean contains(x, y) {
      return x >= this.x && x < this.x + width &&
      return y >= this.y && y < this.y + height ;
   }

   public void draw(Graphics g) {
      g.setColor(color);
      g.fillRect(x, y, width, height);
      g.setColor(Color.black);
      g.drawRect(x, y, width, height);
   }
}
Some terminology: Since Box extends Shape, Box is a subclass of Shape,; while Shape is a superclass of Box.

Note that the Box class makes use of field variables color, x, y, width and height, which were defined in its superclass Shape. Any field variable defined in a class is also available for use by its subclasses.

Note also that in order to allow the application program to make shapes with colors other than gray, you should define the two "setter" and "getter" methods void setColor(Color color) and Color getColor() in your Shape base class. These will look a lot like the void setGift(String gift) and String getGift() methods you defined in a recent homework assignment.

We can also extend Shape to create a subclass Disk. In this case, the math we would use to create the contains method uses the two field variables cx and cy, which we define in the constructor:

   int cx, cy;

   ...

   public Disk(int x, int y, int width, int height) {
      super(x, y, width, height);
   }

   // For a disk we need to do a little extra work to set the bounds:

   void setBounds(int x, int y, int width, int height) {
      super.setBounds(x, y, width, height);
      cx = x + width / 2;
      cy = x + height / 2;
   }
In the contains method we would then implement the following math, to find out whether a point is inside an ellipse:
   public boolean contains(int x, int y) {
              dx = (x - cx) / (width / 2)
              dy = (y - cy) / (height / 2)
              return dx2 + dy2 ≤ 1
      }
I will leave it to you to turn the math above (the part in italics) into proper Java code. Remember that dx and dy should be declared to be of type double.

Hint: The first line of your code to do this could be:

      double dx = (double)(x - cx) / (width / 2);

You will also need to implement a draw method for class Disk. This looks very much like the draw method for class Box, but with drawRect and fillRect replaced by drawOval and fillOval, respectively.

We can also extend Shape to create a subclass Triangle. In this case, the math we would employ to create the draw method uses the two field arrays X[] and Y[], to contain the vertices of the triangle, which we define in the constructor:

   int X[] = new int[3], Y[] = new int[3];

   ...

   public Triangle(int x, int y, int width, int height) {
      super(x, y, width, height);
   }

   public void setBounds(int x, int y, int width, int height) {
      super.setBounds(x, y, width, height);
      setTriangleVertices(x, y, width, height);
   }

   void setTriangleVertices(int x, int y, int width, int height) {
      X[0] = x;
      X[1] = x + width / 2;
      X[2] = x + width;

      Y[0] = y + height;
      Y[1] = y;
      Y[2] = y + height;
   }

   ...

   public void draw(Graphics g) {
      g.setColor(color);
      g.fillPolygon(X, Y, 3);
      g.setColor(Color.black);
      g.drawPolygon(X, Y, 3);
   }
As we discussed in class, one way to do the math to implement the contains method for the Triangle class is to divide the triangle into a left half and a right half, and to use appropriate math for each of the resulting right triangles, to see whether the point is actually inside the triangle:
   public boolean contains(int px, int py) {
              if   px < x + width / 2
                      dx = (px - x) / (width / 2)
                      dy = (py - y) / height
                      return dx + dy ≥ 1
              else
                      dx = (px - (x + width / 2)) / (width / 2)
                      dy = (py - y) / height
                      return dx ≤ dy
      }
I will leave it to you to turn the math above (the part in italics) into proper Java code. Remember again that dx and dy should be declared to be of type double.

Homework, due Thursday April 14

Your homework assignment is to create a simple drawing system as a Java applet that contains base class Shape, and at least the three subclasses Box, Disk and Triangle. For extra credit, you can also extend Shape to other kinds of shapes, which can be anything you like, including hexagon, diamond, car, dragon or puppy.

When your applet comes up on the screen, there should already be some drawing on the screen, of your own original design. I recommend, in your applet, creating an array of Shape, something like the following:

    Shape myShapes[] = {
       new Box(100, 100, 300, 100),  // body of a car
       new Disk(125, 150, 100, 100), // rear wheels of a car
       new Disk(275, 150, 100, 100), // front wheels of a car
    };

When you do your assignment, refer to the above example for form, not for content. In other words, don't draw a simple car. :-)

Note that in the above example, I have an array that is declared to have elements of type Shape, but I am filling it with objects like Box and Disk. I can get away with doing this because Box and Disk are subclasses of Shape. Once they are in array myShapes[], these objects will all appear to be of type Shape, but if we call their draw and contains methods, they will use their own internal definitions for those methods, which is exactly what we want.

Your applet should allow users to drag on any shape to move it to a different position. I recommend that you do this by maintaining a field variable, which you can call something like selectedShape. Whenever this object is not null, it means the user is dragging that object to change its position.

In your render method, you can look for the mouse down condition, to see whether the mouse has landed on a shape:

   Shape selectedShape; // this is a field variable

   ...

   public void render(Graphics g) {

      ...
 
      if (! wasMouseDown && mouseDown) {
         selectedShape = null;
         
	         Loop through all the elements of myShapes[].
	         If you find one that contains (mouseX,mouseY),
	         set selectedShape to that shape.
      }
Note that in the code you will need to implement above (described in italics) you will need to loop through all of the shapes in your myShapes[] array. Once you find one for which myShapes[i].contains(mouseX, mouseY) returns true, then you should save that shape to a field variable called something like selectedShape.

Then in your render method you can add logic for detecting a mouse drag, and respond by moving the selectedShape, as follows:

      if (wasMouseDown && mouseDown) {
         if (selectedShape != null)
	    selectedShape.setBounds(mouseX - selectedShape.width / 2,
	                            mouseY - selectedShape.height / 2,
	                            selectedShape.width,
	                            selectedShape.height);
      }
For extra credit you might try some of these things, or add features of your own design: