Assigned: Monday, March 2, 2020,
Due: Wednesday, 3/18/2020 - extended until Monday, 3/23/2020 Wednesday, 3/25/2020 at 1:30pm.

This is the specification for Homework 4 in Java. The specifications for JavaScript are in a different file.

See also the general instructions for homework 4.

If you have questions about this homework, it is best to post them on the class Piazza page, and then I will answer the questions for everyone. Be sure to mention you are doing the homework in Java.

Note that since the mouse is being used by the application and toolkit, it is fine to remove support for the pause() function, or restrict it to only work when you click in the println() area.

Behavior Objects

A Behavior is an object that can be attached to a group to make it respond to mouse or keyboard events. You will write three kinds of behaviors:

Each Behavior can be modeled by a finite state machine with three states:

Transitions between idle and running states are triggered by input events, such as mouse presses or mouse releases.

All Behavior objects should implement the Behavior interface:

public interface Behavior {
     public Group getGroup ();
     public void setGroup (Group group);

     public int getState ();
         public static final int IDLE = 0;
         public static final int RUNNING_INSIDE = 1;
         public static final int RUNNING_OUTSIDE = 2;

     public BehaviorEvent getStartEvent ();
     public void setStartEvent (BehaviorEvent startEvent);

     public BehaviorEvent getStopEvent ();
     public void setStopEvent (BehaviorEvent stopEvent);

     public void start (BehaviorEvent event);
     public void running (BehaviorEvent event);
     public void stop (BehaviorEvent event);
     public void cancel (BehaviorEvent event);
}

getGroup() accesses the group that the Behavior is attached to, and setGroup() attaches it to a new group.

getState() returns the current state of the Behavior: IDLE, RUNNING_INSIDE, or RUNNING_OUTSIDE.

getStartEvent() and getStopEvent() access the events that trigger the Behavior. Start events make the Behavior transition from idle to running. Stop events make it transition from running back to idle. It is OK if the behaviors only start and stop using mouse events (and not key events), but the cancel event is likely to be a keyboard key (e.g., ESC).

start() should test whether this behavior should start based on incoming BehaviorEvent. it should check to see if event matches this object's startEvent, and if a member of this behavior's group contains the event location, as appropriate. It should do everything needed to start the Behavior and put it in a running state and change the internal state to be either RUNNING_INSIDE or RUNNING_OUTSIDE as appropriate. Then, it will return true if this behavior object starts, and therefore consumes the event, or return false, if the behavior doesn't start based on the event.

running() should process the BehaviorEvent passed in, which will often be a MOUSE_MOVED event. It should be sure to test whether should switch to RUNNING_INSIDE or RUNNING_OUTSIDE based on the posiiton in the event. It should also check to see if the event matches this behavior's STOP_EVENT or the ABORT_EVENT, and if so, directly call stop() or abort(). Return true if this behavior consumes the event, and false if not.

stop() should check to see if the BehaviorEvent matches this behavior's STOP_EVENT and if so, stop and perform the behavior's final action. Returns true if stops, else returns false. If it stops, it should return the behavior's state to be IDLE.

cancel() is called when the cancel event occurs (usually ESC, but you can make it settable). It should cancel the Behavior as if it had never started, and return the Behavior to an IDLE state.

To make your Behaviors work, you'll have to write a class that handles Java mouse and keyboard events and makes the appropriate state changes to the Behaviors. You can start by modifying the top-level TestFrame class, or whatever you used for homework 3, to be something like a InteractiveWindowGroup. Like TestFrame, it will need to implement the Group interface, but also have a way of adding and removing Behavior objects to the window. It will also have a way of listening for Java input events on the window. There will only be one InteractiveWindowGroup per window.

Whenever your InteractiveWindowGroup gets a Java input event, it should convert it to be a BehaviorEvent and then should scan its collection of Behaviors and call start(), running(), stop(), and/or cancel() as appropriate, passing in the event.

You can  make the simplifying assumption that there can only be one Behavior running at a time. This means that when a Behavior is already running, the InteractiveWindowGroup can check only that behavior using the running method. Afterwards, it should check to see if the Behavior is no longer running (if state == IDLE), which might be because it stopped or was cancelled.

public class BehaviorEvent {
    public BehaviorEvent (int id, int modifiers, int key, int x, int y);
    public int getID () { 
    public int getModifiers ();
    public int getKey ();
    public int getX ();
    public int getY ();
    public boolean matches (BehaviorEvent event);

    public final static int KEY_DOWN_ID = 0;
    public final static int KEY_UP_ID = 1;
    public final static int MOUSE_DOWN_ID = 2;
    public final static int MOUSE_UP_ID = 3;
    public final static int MOUSE_MOVE_ID = 4;
    public final static int SCROLLWHEEL_ID = 5;

    public final static int NO_MODIFIER = 0x0; 
    public final static int SHIFT_MODIFIER = 0x1; 
    public final static int CONTROL_MODIFIER = 0x2;
    public final static int ALT_MODIFIER = 0x4;
    public final static int WINDOWS_KEY_MODIFIER = 0x8;
    public final static int FUNCTION_KEY_MODIFIER = 0x10;
    public final static int COMMAND_KEY_MODIFIER = 0x20;
    
    public final static int LEFT_MOUSE_KEY = 10000;
    public final static int MIDDLE_MOUSE_KEY = 10001;
    public final static int RIGHT_MOUSE_KEY = 10002;
    public final static int SCROLLWHEEL_UP_KEY = 10003; 
    public final static int SCROLLWHEEL_DOWN_KEY = 10004;

}

id is one of the five values KEY_DOWN_ID, KEY_UP_ID, MOUSE_DOWN_ID, MOUSE_UP_ID, MOUSE_MOVE_ID, or SCROLLWHEEL_ID. MOUSE_MOVE_ID indicates that the mouse has moved but no other event has occurred.

Extra credit for handling other kinds of events, such as multiple fingers, gestures, sensors like an accelerometer, etc.

modifiers is a bit mask of SHIFT_MODIFIER, CONTROL_MODIFIER, etc. indicating which keyboard keys are currently pressed. These can be OR'ed together when more than one is held down when the key is pressed. Modifier value of 0 means no modifiers.

Note that in "LEFT_MOUSE_DOWN", the "left" refers to the left mouse button, and "down" is pressing that button down (as opposed to releasing the button, which is "up") - these do not have anything to do with the keyboard arrow keys which may be (confusingly) also called "left" and "down".

key is the same as java.awt.event.KeyEvent.getKeyCode(). For letters, numbers, and punctuation, it is the character's uppercase ASCII value, like 'A' or '?'. For special keys on the keyboard, like F1 or Page Up, it is one of the VK_ constants defined in java.awt.event.KeyEvent. For a mouse event, key is which button it is: LEFT_MOUSE_KEY, MIDDLE_MOUSE_KEY, etc.

x and y are the x,y position of the mouse. When an event is passed to a Behavior, (x,y) should be in the coordinate system of the group that the Behavior is attached to, not the coordinate system of the whole window. It is OK if the x,y values are not meaningful for keyboard events (i.e., they can be 0,0).

matches() returns true if two events match. This method is used to test whether an input event matches the start event or stop event of a Behavior. matches() compares only the id, modifiers, and key fields of the two events -- the x,y position does not matter. I have provided a preliminary implementation of matches, but it is not guaranteed to do what you need.

MoveBehavior

A move Behavior moves a graphical object around in its group. It has only one required constructor and no extra required methods:

public class MoveBehavior implements Behavior {
     public MoveBehavior ();
}

A move Behavior should start running only if the start event happens when the mouse is over a graphical object in its group. While it is running, it should use moveTo() to make the object follow the mouse. When the mouse goes outside the group, the Behavior should stop moving the object, so that it can't be dragged outside the group's clipping area. When the stop event occurs, the Behavior should stop moving the object. Aborting should put the object being moved back where it started.

ChoiceBehavior

A choice Behavior selects one or more graphical objects in a group.

public class ChoiceBehavior implements Behavior {
     public ChoiceBehavior (int type, boolean firstOnly);
     public List<GraphicalObject> getSelection ();

     public static final int SINGLE = 0;
     public static final int MULTIPLE = 1;

}

The two parameters to ChoiceBehavior affect what kind of selection it makes, as discussed on the homework4 page.

getSelection() returns a java.util.List containing all the currently-selected objects.

In order to be selectable by a choice Behavior, a graphical object must implement the Selectable interface:

public interface Selectable {
     public void setInterimSelected (boolean interimSelected);
     public boolean isInterimSelected ();
     public void setSelected (boolean selected);
     public boolean isSelected ();
}

These methods can be used by the graphical object to change its appearance. "Interim selected" means that a running choice Behavior is currently interim-selecting the object. Interim selection is always turned off when the Behavior stops. "Selected" means that the object was interim-selected when the choice Behavior stopped. For example, a graphical object implementing a radio button item might change how it is drawn based on whether it is interim-selected, selected, both or neither (see the example in TestAllBehaviors.java).

A choice Behavior should start running only if the start event happens while the mouse is over a graphical object in its group that implements Selectable. It should update the interim selection as the mouse moves around. Finally, when the stop event occurs, the Behavior should clear the interim selection and make the final selection.

You should create versions of your graphical objects that are Selectable, such as SelectableOutlineRect, SelectableText, SelectableGroup, etc. The user should be able to write constraints between properties of the object (like its position and color) and the ...selected properties. Note that it is up to the user of these objects to set constraints that depend on the selection state - by default nothing should happen to the objects when the select methods are called.

NewBehavior

A NewBehavior creates new instances of a class of graphical objects:

public abstract class NewBehavior implements Behavior {
     public NewBehavior (boolean onePoint, boolean rectlike);
     public abstract GraphicalObject make (int a, int b, int c, int d);
     public abstract void resize (GraphicalObject gobj, int a, int b, int c, int d);
}

make() creates a graphical object from point (a, b), with c,d being either another x,y point (for line-like objects) or a width,height (for rectangle-type objects). This method is declared abstract. It should be overridden in a subclass of NewBehavior, which decides which graphical object to create and how to interpret the (a,b,c, d) values.

resize() adjusts a graphical object created by make() with new points. (a,b) is the anchor point, which should be the same as was passed to make(). The values (c,d) follow the mouse cursor, so they will change with each mouse-move event.

If the onePoint parameter to the constructor is true, then the new Behavior needs only one point to create the object. It calls make(x, y, x, y) to create the object, then stops immediately after starting, never calling resize(). This is useful for fixed size objects, like text, where the size is determined by the font..

When a NewBehavior starts, it should call its own make() method to create a new instance of a graphical object. The NewBehavior should add the object returned by make() to the group, so it will appear on screen immediately. While the Behavior is running, it should resize the new object to follow the mouse (assuming onePoint is false). When it stops, it should leave the object where it is.

Subclasses of NewBehavior should exist for handling at least rectangle-like and line like objects, or else there can be subclasses for each kind of object to create.

Resources

Files:

All the files can be found in the following ZIP file. These can be added to your homework 3 project and you can start from there, if you want. If you find any bugs in these files, please post on Piazza, so we can coordinate fixes!


(Last revision of this page: 3/1/2020)

Back to Homework Overview
Back to 05-830 main page