05-830, User Interface Software, Spring, 2013

Homework 3_Behavior: Behavior Object Design for Homework 3


This file describes the specifications for Behavior objects that you can use. However, you can feel free to deviate or augment this description. Unlike the last assignment, there are no structures or interfaces that you are required to use.


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.

start() is called when the start event occurs. It should do everything needed to start the Behavior and put it in a running state. start() may perform additional tests to decide whether or not the Behavior should be started.

running() is called whenever the mouse moves while the Behavior is running. It should update the state of the Behavior and determine whether the mouse is inside or outside the area of interest.

stop() is called when the stop event occurs. It should stop the Behavior and return it to an idle state.

cancel() is called when the cancel event occurs (usually ESC under AWT and F1 under Android, 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. I have provided a class WindowGroup that implements Group and displays its children in a Java window. You will write a subclass, InteractiveWindowGroup, which listens for Java input events on the window. Your subclass should have at least these methods

public class InteractiveWindowGroup extends WindowGroup {
     public void addBehavior (Behavior inter);
     public void removeBehavior (Behavior inter);
}

Note that the InteractiveWindowGroup represents the window. It is both a Group and a JFrame (awt) or Activity (Android). There will only be one InteractiveWindowGroup per window.

Whenever your InteractiveWindowGroup gets a Java input event, it should scan its collection of Behaviors and call start(), running(), and/or stop() as appropriate. The input event should be passed to start(), running() or stop() as an instance of the BehaviorEvent class.

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 each event to see if it is the stop event for that running Behavior, and if so, call stop on the Behavior, and otherwise call running on the Behavior. If it calls stop, then it should check using getState after stop returns to see if the Behavior is no longer running (if state == IDLE).

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 SHIFT_MODIFIER = 0x1;   //These are only used in Swing
    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; //These are only used in Swing
    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. It is OK not to support the SCROLLWHEEL events on Android.

For Android, Touch down/up events should be mapped to MOUSE down/up events with the LEFT_MOUSE_KEY value. Android keyboard events should map naturally to the KEY down/up events here.

Extra credit for handling other kinds of events, such as multiple fingers or gestures.

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. On Android, it is OK if these are always off (you don't have to support the modifiers at all under Android).

key is the same as java.awt.event.KeyEvent.getKeyCode() for awt and the equivalent for Android. 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 or the equivalent under Android. For a mouse event, key is which button it is: LEFT_MOUSE_KEY, MIDDLE_MOUSE_KEY, etc.

x and y are the last known x,y position of the mouse/finger. 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 doesn't matter.

MoveBehavior

A move Behavior moves a graphical object around in its group. It has only one required constructor and no 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.

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 TOGGLE = 1;
     public static final int MULTIPLE = 2;
}

The two parameters to ChoiceBehavior affect what kind of selection it makes, as discussed on the homework3 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 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.

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.

To demonstrate selection feedback, you will write a new Group:

public class SelectionHandles implements Group, Selectable {
     public SelectionHandles (Color color);
}

Usually, SelectionHandles will have exactly one child. The SelectionHandles group should keep its bounding box fit tightly around its child (but leaving enough room to draw handles so the size doesn't change when handles are drawn). Whenever SelectionHandles is selected or interim-selected, it should display a small, filled square at each corner of its child object.

NewBehavior

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

public abstract class NewBehavior implements Behavior {
     public NewBehavior (boolean onePoint);
     public abstract GraphicalObject make (int x1, int y1, int x2, int y2);
     public abstract void resize (GraphicalObject gobj, int x1, int y1, int x2, int y2);
}

make() creates a graphical object from point (x1, y1) to point (x2, y2). 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 (x1,y1) and (x2, y2) coordinates.

resize() adjusts a graphical object created by make() with new points. (x1,y1) is the anchor point, which should be the same as was passed to make(). The point (x2,y2) follows the mouse cursor, so it will be different.

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 icons.

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.

You will create two subclasses of NewBehavior:

These classes should have at least the following constructors:

public NewRectBehavior (Color color, 
                          int lineThickness);

public NewLineBehavior (Color color, 
                          int lineThickness);

Each class should also have a getParam() and setParam() method for each parameter in its constructor.

Note that your NewLineBehavior and NewRectBehavior don't have to create pure Line and Rect objects. You may want to create lines wrapped inside a SelectionHandles group, or perhaps a subclass of Line that implements Selectable and draws selection-handle feedback itself.

Resources

Files:

All the files can be found in one of these ZIP files. These can be added to your homework 2 project. Note that these files have not been compiled or debugged, so they may contain bugs (especially for Android).

hw3-files-swing.zip or hw3-files-android.zip (depending on which OS you are targeting)

Back to Homework Overview
Back to 05-830 main page