This is the specification for Homework 4 in JavaScript. The specifications for Java 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 JavaScript.
Note that since the mouse is being used by the application and toolkit, it is fine to remove support for the waitForUser() function, or restrict it to only work when you click in the black message area.
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 inherit from the Behavior class, defined in Behavior.js:
class Behavior { constructor(group=UNDEFINED, startEvent = new BehaviorEvent("LEFT_MOUSE_DOWN"), stopEvent = new BehaviorEvent("LEFT_MOUSE_UP"), abortEvent = new BehaviorEvent("ESC")) { this.group = group; this.startEvent = startEvent; this.stopEvent = stopEvent; this.abortEvent = abortEvent; this.state = IDLE; this.affectedObject = null; // will be an object in the group } start (event) { } running (event) { } stop (event) { } cancel (event) { } }
The states of each behavior are defined in Behavior.js as follows:
const IDLE = 0; const RUNNING_INSIDE = 1; const RUNNING_OUTSIDE = 2;
All the events are BehaviorEvents, see discussion below.
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.
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 JavaScript mouse and keyboard events and makes the appropriate state changes to the Behaviors. Typically, this will be a replacement for the TestHarness and/or the TopGraphics that handles events and allows Behavior objects to be attached to the window so that they are processing events (and detached if they are not needed).
Whenever your handler gets an input event, it convert it to be a BehaviorEvent and then should scan its collection of Behavior objects 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.
class BehaviorEvent { constructor(eventOrString) { if (typeof(eventOrString)==='string') this.BehaviorEventFromString(eventOrString); else if (eventOrString instanceof UIEvent) this.BehaviorEventFromUIEvent(eventOrString); else console.error("wrong type of parameter to create a BehaviorEvent"); }
The BehaviorEvent provides a convenient way to talk about events. It can be created using either a string or an instance of the JavaScript UIEvent class or its subclasses like MouseEvent and KeyboardEvent. The string version will be like: "LEFT_MOUSE_DOWN" or "CONTROL_K" or "SHIFT_F1". When a BehaviorEvent is created from a string, it will leave fields like the x,y as 0,0, but when created from a UIEvent version, all the fields can be specified and later accessed. Fields of a BehaviorEvent will include at least:
It is fine to use the internal JavaScript encodings for the id and key. You should handle at least all the keyboard keys and modifiers, the left and right mouse buttons down, up and click (and dblclick if you want), mouse move events, and the mouse wheel up scrolling up and down.
Extra credit for handling other kinds of events, such as multiple fingers, gestures, sensors like an accelerometer, 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.
(Note that the Behavior.js file currently includes the required behavior definitions, but you should move these to their own files, like MoveBehavior.js.)
A move Behavior moves a graphical object around in its group. It has no extra parameters beyond regular behaviors:
class MoveBehavior extends Behavior { }
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.
A choice Behavior selects one or more graphical objects in a group. It has a few extra parameters beyond the top-level Behaviors:
const SELECT_SINGLE = 0; const SELECT_MULTIPLE = 1; class ChoiceBehavior extends Behavior { constructor(group=UNDEFINED, startEvent = new BehaviorEvent("LEFT_MOUSE_DOWN"), stopEvent = new BehaviorEvent("LEFT_MOUSE_UP"), abortEvent = new BehaviorEvent("ESC"), selectType = SELECT_SINGLE, firstOnly = true) { } }
The two parameters to ChoiceBehavior affect what kind of selection it makes, as discussed on the homework4 page.
getSelection() returns the current list of selected objects. Returns an empty list if nothing selected. Even in SELECT_SINGLE mode, always returns a list, which then will have 0 or 1 element in the list.
When the choice behavior is running on a group, the behavior will set special attributes of the elements of that group. In particular, it will set the interimSelected attribute when that the behavior is running and that object is under the mouse. For example, if the group is being used as a menu, then the menu item under the mouse will be interimSelected = true. When the mouse is released (and the stop action is called), then the object under the mouse as the end of the behavior will become selected or not. For example, if moving over an object representing a checkbox, it may become checked or unchecked, corresponding to selected = true and false. In JavaScript, you can just set an attribute with a value, to cause that attribute to appear (so the choice behavior can just call r1.selected = true; to get a rectangle r1 to have a selected attribute.)
Constraints in the graphical object can be used with these attributes to change its appearance. interimSelected 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 interimSelected 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 interimSelected, selected, both or neither (see the example in TestAllBehaviors.js). 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.
A choice Behavior should start running only if the start event happens while the mouse is over a graphical object in its group. 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.
A NewBehavior creates new instances of a class of graphical objects:
class NewBehavior extends Behavior { constructor(group=UNDEFINED, startEvent = new BehaviorEvent("LEFT_MOUSE_DOWN"), stopEvent = new BehaviorEvent("LEFT_MOUSE_UP"), abortEvent = new BehaviorEvent("ESC"), onePoint = false, asLine = false, makeObjFunction = function(a,b,c,d) {return null}, interimFeedbackObj = null) { }
asLine controls whether the new object will be rectangle-like or line-like, and the interimFeedbackObj should be consistent with that. For example, if asLine is true, then interimFeedbackObj might be a dotted line.
makeObjFunction allows the user to provide a function to create the new objects. If asLine is false, then a,b,c,d will be x,y,width,height. If asLine is true, then they will be x1,y1,x2,y2.
If the onePoint parameter to the constructor is true, then the new Behavior needs only one point to create the object. It calls the makeObjFunction to create the object immediately on the startEvent, with a,b as the initial x,y, and c,d as 0,0, independent of asLine. It then stops immediately after starting. This is useful for fixed size objects, like text, where the size is determined by the font..
If the onePoint parameter to the constructor is false, then when a NewBehavior starts, it should add the interimFeedbackObj to the behavior's group, and set the initial position as the startEvent position. It will set the other end continously based on the runningEvent's position, until it stops successfully or aborts. If it stops successfully, then it calls the makeObjFunction.
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!
Back to Homework Overview
Back to 05-830 main page