Homework 2: Object Oriented Graphics

05-631: Software Architecture for User Interfaces , Fall, 2001

Assigned: September 18, 2001
Due: (EXTENDED): Thursday, October 4, 2001

See the new hints and discussion

In this assignment, you are going to create an object-oriented graphics system that provides a retained-object model to automatically handle redrawing. You will create a set of Java classes out of which graphical objects can be constructed. Your classes will be responsible for handling refresh when an area of the screen is damaged and needs to be redrawn.

Graphical Objects

You will provide the following graphical object classes:

These classes should have the following required constructors:

public Rect (int x, int y,
             int width, int height, 
             Color color, 
             int lineThickness);

public FilledRect (int x, int y,
                    int width, int height, 
                    Color color);

public Line (int x1, int y1,
             int x2, int y2, 
             Color color,
             int lineThickness);

public Icon (Image image, 
             int x, int y);

public Text (String text,
             int x, int y,
             Font font,
             Color color);

Your graphical objects should be drawn using the Java 2D graphics model. Each object corresponds to a method of java.awt.Graphics2D. Rect corresponds to drawRect(), FilledRect to fillRect(), Line to drawLine(), Icon to drawImage(), and Text to drawString().

For Rect, FilledRect, and Icon, (x,y) is the top-left corner. For Line, (x1,y1) and (x2,y2) are the endpoints of the line. For Text, (x,y) is the position of the first character's baseline.

Each class should have a getParam() and setParam() method for each parameter in its constructor. For example, Rect should have the following methods:

public int getX ();
public int getY ();
public int getWidth ();
public int getHeight ();
public Color getColor ();
public int getLineThickness ();

public void setX (int newX);
public void setY (int newY);
public void setWidth (int newWidth);
public void setHeight (int newHeight);
public void setColor (Color newColor);
public void setLineThickness (int newLineThickness);

All your graphical object classes must implement the GraphicalObject interface:

public interface GraphicalObject {
     public void draw (Graphics2D graphics);
     public Rectangle getBoundingBox ();
     public void moveTo (int x, int y);
     public Group getGroup ();
     public void setGroup (Group group);
}

draw() draws the graphical object on a drawing surface.

getBoundingBox() returns the smallest rectangle that contains all the pixels drawn by the graphical object. Your graphical objects will have to accurately compute their bounding boxes, taking into account line thickness and the drawing behavior of Java2D. For Text objects, you'll need to use java.awt.FontMetrics to find the bounding box. Note that the Rectangle returned by getBoundingBox() is java.awt.Rectangle, which is different from the Rect graphical object you have to implement.

moveTo() moves the graphical object so that the top-left corner of its bounding box is at (x,y). You have to determine how this affects the coordinates of the underlying graphical object. Calling moveTo() on a Line object should move both endpoints so that the line has the same angle and length after the move.

getGroup() and setGroup() get and set the group to which the graphical object belongs. If the object doesn't belong to a group, getGroup() returns null. Groups are described in more detail next.

Groups

You will also provide the following grouping classes:

These classes have the following required constructors:

public SimpleGroup (int x,
                    int y
                    int width,
                    int height);

public LayoutGroup (int x,
                    int y,
                    int width,
                    int height,
                    int layout,
                    int offset);

public ScaledGroup (int x,
                    int y,
                    int width,
                    int height,
                    double scaleX, 
                    double scaleY);

Each group class should provide setParam() and getParam() methods for each parameter in its constructor.

A group defines a new coordinate system for its children. The coordinates of the children are interpreted relative to the group's origin (the x, y point passed to the group constructor). Children should be clipped to the bounding box of the group.

All grouping classes should implement the Group interface:

public interface Group extends GraphicalObject {
    public void addChild (GraphicalObject child);
    public void removeChild (GraphicalObject child);
    public void resizeChild (GraphicalObject child);
    public void bringChildToFront (GraphicalObject child);
    public void resizeToChildren ();
    public void damage (Rectangle damagedArea);
}

Notice that the Group interface extends GraphicalObject, so your group classes must implement the GraphicalObject methods too.

addChild() and removeChild() add and remove graphical objects from the group. These methods should call setParent() on the child.

resizeChild() notifies the group that the bounding box of one of its children has changed.

bringChildToFront() changes the display order of the children so that the specified child is drawn in front of other children. The default drawing order draws the last child added in front.

resizeToChildren() changes the group's width and height to fit around its children as tightly as possible. The children should not be repositioned , so children at negative (x,y) positions will still lie outside the group's bounding box after this method is called.

damage() collects rectangles that need to be redrawn. When a graphical object changes, it should call the damage() method of the group it belongs to, passing its old bounding box and its new bounding box if it has moved. If a group object is part of another group object, it should propagate the damaged rectangle up to its parent group. The next time draw() is called on the group, it should call draw() on all children that intersect the damaged region.

LayoutGroup has two extra parameters that determine how its children are positioned. The layout parameter has the following possible values, defined as constants in the Group interface. HORIZONTAL means that children are placed side by side from left to right, with top edges aligned. VERTICAL means that children are arranged top to bottom, with left edges aligned. The offset parameter specifies how much space to put between each child. Offsets may be negative, in which case adjacent children should overlap. The offset is not used before the first child, which should always be placed at position (0,0). Children should be laid out in drawing order, so bringChildToFront() should make the specified child the last object in the layout.

Extra credit is given for implementing other kinds of automatic layout; see below.

ScaledGroup draws its children scaled in x and y. Scale factors less than 1.0 shrink the children, and scale factors greater than 1.0 enlarge them. Scaling affects both positions and size, so two children of a ScaledGroup with scaleX=0.5 are not only half as wide but also half as far apart (in the x direction, at least). Be sure to take scale factors into account when you compute bounding boxes and damage rectangles.

Other Notes

New hints and discussion

Do not change the GraphicalObject or Group interfaces. Your code should work with GraphicalObjects and Groups written by other developers. The test driver used for grading will include GraphicalObject and Group implementations that your code has never seen before.

Be careful with ungrouped objects. Your graphical objects should not throw any exceptions if they are used without being added to a group.

When working with java.awt.Image, which you'll need to do to implement Icon, you'll see that some methods take an ImageObserver argument. ImageObserver is used for images that are incrementally loaded over the network. Our test drivers will ensure that images are fully loaded before passing them to your Icon class, so you don't need to provide an ImageObserver. Just pass null to any methods that require an ImageObserver.

For extra credit, you can implement more graphical objects, groups, and layouts than the minimum requirement. Here are some ideas. You may have others.

Graphical objects:

Groups:

Layouts:

Resources

You will be provided with source code for the GraphicalObject and Group interfaces. You will also receive source code for some test programs. These programs have main() methods, so you can run them directly. The tests we provide are far from complete, so you should certainly write your own. We will use these test programs and others to grade your project. You can modify the test programs for your own use, but your code shouldn't depend on any changes you make.

If you implement additional graphical objects, groups, or layouts, please provide a custom test program for each one that demonstrates its features.

Files:

All the files can be found in this ZIP archive:

hw2-files.zip
See also the: new hints and discussion