When an Interactor or a widget (see the Widgets chapter) finishes its operation, it allocates a Command object and then invokes the `do' method of that Command object. Thus, the Command objects take the place of call-back procedures in other systems. The reason for having Command objects is that in addition to the `do' method, a Command object also has methods to support undo, help, and selective enabling of operations. As with Interactors, Amulet supplies a library of Command objects so that often programmers can use a Command object from the library without writing any code.
inter.h
for the main, top-level objects and procedures, idefs.h
for the definitions specific to input events, and inter_advanced.h
for what you might need if you are going to create your own custom Interactors. All the slots of the Interactor and Command objects are defined in standard_slots.h
. Some of the functions and types needed to customize the text editing Interactor are defined in text_fns.h
. For more information on the Amulet header files and how to use them, see Section 1.6 in the Overview chapter.
5.2 Overview of Interactors and Commands
The graphical objects created with Opal do not respond to input devices: they are just static graphics. In order to handle input from the user, you create an ``Interactor'' object and attach it to the graphics. The Interactor objects have built-in behaviors that correspond to the normal operations performed in direct manipulation user interfaces, so usually coding interactive interfaces is quick and easy using interactors. However, like programming with constraints, programming with Interactors requires a different ``mind set'' and the programming style is probably different than what most programmers are used to.Am_LEFT
and Am_TOP
slots of the object, and also calls the Am_DO_METHOD
method stored in the Command object attached to the Interactor. Therefore, there are multiple ways to use an Interactor, to give programmers flexibility in what they need to achieve.Am_COMMAND
, and Interactors set the Am_VALUE
and other slots in its command object, and then call the Am_DO_METHOD
method. This and other methods in the Command objects implement the functionality of the Interactors.
5.3 Standard Operation
We hope that most normal behaviors and operations will be supported by the Interactors and Command objects in the library. This section discusses how to use these. If you find that the standard operations are not sufficient, then you can override the standard methods, as described in Section 5.5.2. If you want an additional operation in addition to the regular operation, then you can just add a command object to the Interactor, as explained in Section 5.5. If neither of these is sufficient, you may need to create your own Interactor as discussed in Section 5.7.
5.3.1 Designing Behaviors
The first task when designing the interaction for your interface is to choose the desired behavior. The first choice is whether one of the built-in widgets provides the right interface. If so, then you can choose the widget from the Widgets chapter and then attach the appropriate Command object to the widget. The widgets, such as buttons, scroll bars and text-input fields, combine a standard graphical presentation with an interactive behavior. If you want custom graphics, or you want an application-specific graphical object to be moved, selected or edited with the mouse, then you will want to create your own graphics and Interactors.inter.h
):
Am_Choice_Interactor
. This is used to choose one or more from a set of objects. The user is allowed to move around over the objects (getting interim feedback) until the correct item is found, and then there will often be final feedback to show the final selection. The Am_Choice_Interactor
can be used for selecting among a set of buttons or menu items, or choosing among the objects dynamically created in a graphics editor.
Am_One_Shot_Interactor
. This is used whenever you want something to happen immediately when an event occurs, for example when a mouse button is pressed over an object, or when a particular keyboard key is hit. Like the Am_Choice_Interactor
, the Am_One_Shot_Interactor
can be used to select among a set of objects, but the Am_One_Shot_Interactor
will not provide interim feedback the object where you initially press will be the final selection. The Am_One_Shot_Interactor
is also useful in situations where you are not selecting an object, such as when you want to get a single keyboard key.
Am_Move_Grow_Interactor
. This is useful in all cases where you want a graphical object to be moved or changed size with the mouse. It can be used for dragging the indicator of a scroll bar, or for moving and growing objects in a graphics editor.
Am_New_Points_Interactor
. This Interactor is used to enter new points, such as when creating new objects. For example, you might use this to allow the user to drag out a rubber-band rectangle for defining where a new object should go.
Am_Text_Edit_Interactor
. This supports editing the text string of a text object. It supports a flexible key translation table mechanism so that the programmer can easily modify and add editing functions. The built-in mechanisms support basic text editing behaviors.
Am_Gesture_Interactor
. This Interactor supports free-hand gestures, such as drawing an X over an object to delete it, or encircling the set of objects to be selected.
Am_Rotate_Interactor
. This Interactor will support rotating graphical objects. It is not yet implemented.
Am_Animation_Interactor
. This Interactor will support animations and time-based events. It is not yet implemented.
Am_One_Shot_Interactors
then immediately execute their Command's DO
method and go back to waiting. Other types of Interactors, however, usually show interim feedback while waiting for a specific stop event (for example, left mouse button up). While Interactors are operating, the user might move the mouse outside the Interactor's operating area, in which case the Interactor stops working (for example, a choice Interactor used in a menu will turn off the highlighting if the mouse goes outside the menu). If the mouse goes back inside, then the Interactor resumes operation. If the abort_event is executed while an Interactor is running, then it is aborted (and the Command's DO
method is not executed). Similarly, if the stop event is executed while the mouse is outside, the Interactor also aborts. The operation is summarized by the following diagram.
Multiple Interactors can be running at the same time. Each Interactor keeps track of its own status, and for each input event, Amulet checks which Interactor or Interactors are interested in the event. The appropriate Interactor(s) will then process that event and return.
5.3.3 Parameters
Once the programmer has chosen the basic behavior that is desired, then the various parameters of the specific Interactor must be filled in. The next sections give the details of these parameters. Some, such as the start and abort events, are shared by all Interactors, and other parameters, such as gridding, are specific to only a few types of Interactors.standard_slots.h
and are described below. As an example, the following creates a choice Interactor called `Select It' assigned to the variable select_it
and sets its start event to the middle mouse button. See the ORE chapter for how to create and name objects.select_it = Am_Choice_Interactor.Create("Select It")
.Set(Am_START_WHEN, "MIDDLE_DOWN");
5.3.3.1 Events
One of the most important parameters for all Interactors are the input events that cause them to start, stop and abort. These are encoded as an Am_Input_Char
which are defined in idefs.h
. Normally, you do not have to worry about these since they are automatically created out of normal C strings, but you can convert a string into an Am_Input_Char
for efficiency, or if you want to set or access specific fields.char
to represent the events. It must be a C string or an Am_Input_Char
object.5.3.3.1.1 Event Slots
There are three slots of Interactors that can hold events: Am_START_WHEN, Am_ABORT_WHEN,
and Am_STOP_WHEN
.Am_START_WHEN
determines when the Interactor begins operating. The default value is Am_Default_Start_Char
which is "LEFT_DOWN"
with no modifier keys (see Section 5.3.3.1.3) but with any number of clicks (see Section 5.3.3.1.4). So by default, all interactors will operate on both single and double clicks.
Am_ABORT_WHEN
allows the Interactor to be aborted by the user while it is operating. The default value is "CONTROL_g"
. Aborting is different from undoing since you abort an operation while it is running, but you undo an operation after it is completed. All interactors can be aborted while they are running.Am_STOP_WHEN
determines when the Interactor should stop. The default value is Am_Default_Stop_Char
which is "ANY_MOUSE_UP"
so even if you change the start_when, you can often leave the stop_when as the default value.5.3.3.1.2 Event Values
In any of these slots, you can provide an Am_Input_Char
, a string in the format described below, or the special values true
or false
. The value true
matches any event, and false
will never match any event. You might use false in the Am_ABORT_WHEN
slot of an Interactor to make sure it is never aborted."A",
"z",
"["
, and "\""
(use the standard C++ mechanism to get special characters into the string). The various function and special keys are generally named the same thing as their label, such as "F1",
"R5",
"HELP",
and "DELETE"
. Sometimes, keys have multiple markings, in which case we usually use the more specific or textual marking, or sometimes both markings will work. Also, the arrow keys are always called "LEFT_ARROW",
"UP_ARROW",
"DOWN_ARROW",
and "RIGHT_ARROW"
. Note that keys with names made out of multiple words are separated by underscores. For keyboard keys, we currently only support operations on the button being pressed, and no events are generated when the button is released. You can specify any keyboard key with the special event "ANY_KEYBOARD"
(see Section 5.4.1 for how to find out which key was hit). You can find out what the mapping for a keyboard key is by running the test program testinput
, which is in the src/gem
directory. We have tried to provide appropriate mappings for all of the keyboards we have come across, but if there are keyboard keys on your keyboard that are not mapped appropriately, then please send mail to amulet@cs.cmu.edu
and we will add them to the next release.
For the mouse buttons, we support both pressing and releasing. The names of the mouse buttons are
"LEFT"
, "MIDDLE"
and "RIGHT"
(on a 2-button mouse, they are "LEFT"
and "RIGHT"
and on a 1-button mouse, just "LEFT"
), and you must append either "UP"
or "DOWN"
. Thus, the event for the left button down is "LEFT_DOWN"
. You can specify any mouse button down or up using "ANY_MOUSE_DOWN"
and "ANY_MOUSE_UP"
. On the Macintosh, you can generate the right mouse down event using OPTION-mouse down, and the middle mouse event using OPTION-SHIFT-mouse down. On the PC with a two-button mouse, there is no way to generate the middle button event.5.3.3.1.3 Event Modifiers
The modifiers can be specified in any order and the case of the modifiers does not matter. There are long and short prefix forms for each modifier. You can use either one in strings to be converted into Am_Input_Chars
. For example "CONTROL_f"
and "^f"
represent the same key. Note that the short form uses a hypen (it looks better in menus) and the long form uses an underscore (to be consistent with other Amulet symbols).
shift_
or SHFT-
One of the keyboard shift keys is being held down. For letters, you can also just use the upper case. Thus, "F"
is equivalent to "SHIFT_f"
. However, do not use shift to try to get the special characters. Therefore "Shift_5"
is not the same as "%".
For alphabetic characters only, the Caps Lock key produces a shift
modifier.
control_
or ^
The control key is being held down.
meta_
or MET-
The meta key is the diamond key on Sun keyboards, the EXTEND-CHAR
key on HPs, the Command (Apple) key on Macintosh keyboards, and the ALT
key on PC keyboards. On other Unix keyboards, it is generally whatever is used for ``meta'' by Emacs and the window manager. Note that on the Macintosh, the Option key with the mouse button generates middle and right button events, and with keyboard keys, just returns whatever is at that point in the font. Similarly, the default text interactor uses the Meta key on Unix to generate characters at the top of the font (see Section 5.3.5.5.1).
any_
This means that you don't care which modifiers are down. Thus "any_f"
matches "shift_f"
as well as "F"
and "meta_control_shift_f"
. Note that "ANY_KEYBOARD"
or "ANY_MOUSE_DOWN"
also specifies any modifiers.
Am_Double_Click_Time
(which is defined in gem.h), which defaults to 250, and is measured in milliseconds. (On the Macintosh, Am_Double_Click_Time
is ignored, and the system constant for double click time is used instead, which is set with the Mouse control panel.)On the PC, Amulet detects single and double clicks, and on Unix and the Mac, Amulet will detect up to five clicks. The multiple clicks are named by preceding the event name with the words
"DOUBLE_"
, "TRIPLE_"
, "QUAD_"
, and "FIVE_"
. For example, "double_left_down"
, or "shift_meta_triple_right_down".
When the user double clicks, a single click event will still be generated first. For example, for the left button, the sequence of received events will be "LEFT_DOWN"
, "LEFT_UP"
, "DOUBLE_LEFT_DOWN"
, "DOUBLE_LEFT_UP"
. The "ANY_
" prefix can be used to accept any number of clicks, so "ANY_LEFT_DOWN
" will accept single or multiple clicks with any modifier held down.
Am_Input_Char
is defined in idefs.h
. It is a regular C++ object (not an Amulet object). It has constructors from a string or from the various pieces:
Am_Input_Char (const char *s);//from a string like "META_LEFT_DOWN
"
Am_Input_Char (short c = 0, bool shf = false,
bool ctrl = false,
bool meta = false, Am_Button_Down down = Am_NEITHER,
Am_Click_Count click = Am_NOT_MOUSE,
bool any_mod = false);It can be converted to a string, to a short string, to a long (which is only useful for storing the
Am_Input_Char
into a slot of an object) or to a character (which returns 0 if it is not an normal ascii character). An Am_Input_Char
will also print to a stream as a string. If ic
is an Am_Input_Char:
ic.As_String(char *s);
convert to a string by writing into s
, which should be at least Am_LONGEST_CHAR_STRING
characters long.
ic As_Short_String(char *s);
Convert to a short string like ``^C'' such as might be used in a menu. s
should be at least Am_LONGEST_CHAR_STRING
characters long.
(long)ic;
convert ic
into a long, for storing it into a slot.
char c = ic.As_Char();
Returns a char if ic represents a simple ascii character, otherwise returns \0.
cout << ic;
you can print an Am_Input_Char
directly, in which case it prints the same as As_String
.
ic = Am_Input_Char::Narrow (obj.Get(SLOT));
can be used to convert a slot holding a long
into an Am_Input_Char
.
Am_Input_Char
are:
typedef enum { Am_NOT_MOUSE = 0, //When not a mouse button.
Am_SINGLE_CLICK = 1, //Also for mouse moved, with Am_NEITHER.
Am_DOUBLE_CLICK = 2, Am_TRIPLE_CLICK = 3,
Am_QUAD_CLICK = 4, Am_FIVE_CLICK = 5, Am_MANY_CLICK = 6,
Am_ANY_CLICK = 7 // when don't care about how many clicks
} Am_Click_Count;
typedef enum { Am_NEITHER = 0, Am_BUTTON_DOWN = 1,
Am_BUTTON_UP = 2, Am_ANY_DOWN_UP = 3} Am_Button_Down;
short code; // the base code.
bool shift; // whether these modifier keys were down
bool control;
bool meta;
bool any_modifier; //true if don't care about modifiers
Am_Button_Down button_down; // whether a down or up transition.
// For keyboard, only support down.
Am_Click_Count click_count; // 0==not mouse, otherwise # clicks
select_it
Interactor defined above in Section 5.3.3 select the object my_rect
, the following code could be used:
Interactors can be added as parts to any kind of graphical object, including primitives (like rectangles and strings), groups, and windows. You can add multiple Interactors to any object, and they can be interspersed with graphical parts for groups and windows. Interactors can be removed or queried with the standard object routines for parts. If you make instances of the object to which the Interactor is attached, then an instance will be made of the Interactor as well (see the ORE chapter). For example:
my_rect.Add_Part(select_it);
Am_Slot_Key INTER_SLOT = Am_Register_Slot_Name ("INTER_SLOT");
my_rect.Add_Part(INTER_SLOT, select_it);
//named par
t
rect2 = my_rect.Create();
It is very common for a behavior to operate over the parts of a group, rather than just on the object itself. For example, a choice Interactor might choose any of the items (parts) in a menu (group), or a move_grow Interactor might move any of the objects in the graphics window. Therefore, the slot //rect2 will have its own which is an instance of select_it
Am_START_WHERE_TEST
can hold a function to determine where the mouse should be when the start-when event happens for the Interactor to start. The built-in functions for the slot (from inter.h
) are as follows. Each of these returns the object over which the Interactor should start, or NULL if the mouse is in the wrong place so the Interactor should not start.
Am_Inter_In_Object_Or_Part
: If the interactor is attached to a group-like object (a Am_Window
, Am_Screen
, Am_Group
or Am_Scrolling_Group
), then looks for a part of that object for the mouse to be in. Otherwise, tests whether the mouse is directly in the object the mouse is attached to. This is the default Am_START_WHERE_TEST
for most interactors.
Am_Inter_In_Text_Object_Or_Part
: If the interactor is attached to a group-like object (a Am_Window
, Am_Screen
, Am_Group
or Am_Scrolling_Group
), then looks for a part of that object of type Am_Text
for the mouse to be in. Otherwise, tests whether the mouse is directly in the object the mouse is attached to and that object is a Am_Text
. This is the default Am_START_WHERE_TEST
for Am_Text_Edit_Interactor
s.
Am_Inter_In
: If the mouse is inside the object the Interactor is part of, this returns that object.
Am_Inter_In_Part
: The Interactor should be part of a group or window object. This tests if the mouse is in a part of that group or window object, and if so, returns the part of the group or window the mouse is over.
Am_Inter_In_Leaf
: This is useful when the Interactor is part of a group or window which contains groups which contain groups, etc. It returns the lowest level object the mouse is over. If you want Am_Inter_In_Leaf
to return a group rather than a part of the group, set the Am_PRETEND_TO_BE_LEAF
slot of the group to be true
.
Am_Inter_In_Text
: If the mouse is inside the object the Interactor is part of, and that object is an instance of Am_Text
, then returns that object. This is useful for Am_Text_Interactor
s.
Am_Inter_In_Text_Part
: If the mouse is in a part of the object the Interactor is part of, and that the part the mouse is over is an instance of Am_Text
, then returns that part. This is useful for Am_Text_Interactor
s.
Am_Inter_In_Text_Leaf
: If the mouse is in a leaf of the object the Interactor is part of, and that leaf part is an instance of Am_Text
. This is useful for Am_Text_Interactor
s.
my_group
that the user clicks on. Since the interactor is also a part of the group, an instance of the interactor will be created whenever an instance is made of the group, as explained in Section 3.6.2.
Am_Slot_Key INTER_SLOT = Am_Register_Slot_Name ("INTER_SLOT");
my_group.Add_Part(INTER_SLOT, Am_Move_Grow_Interactor.Create()
.Set(
Am_START_WHERE_TEST
, Am_Inter_In_Part));
my_group.Add(rect);
my_group.Add(rect2);
//now the interactor will move either rect or rect2
group2 = my_group.Create();
If none of these functions returns the object you are interested in, then you are free to define your own function. It should be a method of type //group2 will have its own interactor as well as instances of rect and rect2
Am_Where_Method
, and should return the object the Interactor should manipulate, or Am_No_Object
if none. For example:Am_Define_Method(Am_Where_Method, Am_Object, in_special_obj_part,
(Am_Object /* inter */,
Am_Object object, Am_Object event_window,
Am_Input_Char /*ic*/, int x, int y)) {
Am_Object val = Am_Point_In_Part(object, x, y, event_window);
if (val.Valid() && (bool)val.Get(MY_SPECIAL_SLOT)) return val;
else return Am_No_Object;
}
Note that this means that the Interactor may actually operate on an object different from the one to which it is attached. For example, Interactors will often be attached to a group but actually modify a part of that group. With a custom Am_START_WHERE_TEST
function, the programmer can have the Interactor operate on a completely independent object.5.3.3.3 Active
It is often convenient to be able to create a number of Interactors, and then have them turn on and off based on the global mode or application state. The Am_ACTIVE
slot of an Interactor can be set to false
to disable the Interactor, and it can be set to true
to re-enable the Interactor. By default, all Interactors are active. Setting the Am_ACTIVE
slot is more efficient than creating and destroying the Interactor. The Am_ACTIVE
slot can also be set with a constraint that returns true
or false
.5.3.3.4 Am_Inter_Location
Some interactors require a parameter to describe a location and/or size of an object. Since the coordinate system of each object is defined by its group, just giving a number for X and Y would be meaningless without also supplying a reference object. Therefore, we have introduced a wrapper type, called Am_Inter_Location
, which encapsulates the coordinates with a reference object. As described in the Opal manual, some objects are defined by their left, top, width and height, and others by two end points, and this information is also included in the Am_Inter_Location
. The methods on an Am_Inter_Location
(from inter.h
) are:class Am_Inter_Location {
public:
Am_Inter_Location ();
// empty
//create a new one. If as_line, then a,b is x1, y1 and c,d is x2, y2.
// If not as_line, then a,b is left, top and c,d is width, height
Am_Inter_Location (bool as_line, Am_Object ref_obj,
int a, int b, int c, int d);
//change the values of an existing one
void Set_Location (bool as_line, Am_Object ref_obj,
int a, int b, int c, int d, bool make_unique = true);
//change just the first coordinate of an existing one
void Set_Location (bool as_line, Am_Object ref_obj,
int a, int b, bool make_unique = true);
//return all the values
void Get_Location (bool &as_line, Am_Object &ref_obj,
int &a, int &b, int &c, int &d) const;
//return just the coordinates, the reference object, whether it is a line or not
void Get_Points (int &a, int &b, int &c, int &d) const;
Am_Object Get_Ref_Obj () const;
void Get_As_Line (bool &as_line) const;
//copy from or swap with another Am_Inter_Location
void Copy_From (Am_Inter_Location& other_obj, bool make_unique = true);
void Swap_With (Am_Inter_Location& other_obj, bool make_unique = true);
//translate the coordinates so they now are with respect to dest_obj
bool Translate_To(Am_Object dest_obj);
Am_Inter_Location Copy() const;
//make a new one like me
virtual void Print_Name (ostream& os);
//print my contents on the stream
};
5.3.4 Top Level Interactor
Am_Interactor is used to build new, custom interactors. This object won't do anything if you simply instantiate it and add it to a window.Am_Interactor
's slots. Most of these are advanced features and are discussed in Section 5.4.
5.3.5 Specific Interactors
All of the interactors and command objects are summarized in Chapter 10, Summary of Exported Objects and Slots. The next sections discuss each one in detail.
5.3.5.1 Am_Choice_Interactor
The Am_Choice_Interactor
is used whenever the programmer wants to choose one or more out of a set of objects, such as in a menu or to select objects in a graphics window. The standard behavior allows the programmer to choose whether one or more objects can be selected, and special slots called Am_INTERIM_SELECTED
and Am_SELECTED
of these objects are set by default. Typically, the programmer would define constraints on the look of the object (e.g. the color) based on the values of these slots. Note that Am_INTERIM_SELECTED
and Am_SELECTED
are set in the graphical object the Interactor operates on, not in the Interactor itself.5.3.5.1.1 Special Slots of Choice Interactors
Two slots of choice Interactors can be set to customize its behavior:
Am_HOW_SET
: This controls whether a single or multiple values will be selected. Legal values are from the following type:
typedef enum (Am_CHOICE_SET, Am_CHOICE_CLEAR, Am_CHOICE_TOGGLE,
Am_CHOICE_LIST_TOGGLE } Am_Choice_How_Set;
Am_CHOICE_SET
: the object under the mouse becomes selected, and the previously selected object is de-selected (useful for single selection menus and radio buttons). Unlike Am_CHOICE_TOGGLE,
clicking on an already-selected object leaves it selected.
Am_CHOICE_CLEAR
: the object under the mouse becomes de-selected. This is rarely useful.
Am_CHOICE_TOGGLE
: if the object under the mouse is selected, it becomes deselected, otherwise it becomes selected and any previous object become de-selected. This is useful when you want zero or one selection (the user is able to turn off the selection).
Am_CHOICE_LIST_TOGGLE
: if the object under the mouse is selected, then it is de-selected, otherwise it becomes selected, but other objects are left alone. This allows multiple selection, and is useful for check boxes.
Am_HOW_SET
slot is Am_CHOICE_TOGGLE
.
Am_FIRST_ONE_ONLY
: If false (the default), then the selection is free to move from one item in the group to another, as in menus. If true, then only the initial object the mouse is over can be manipulated, and the user must release outside and then press down in another object to change objects. This is how radio button and check box widgets work on most systems.
Am_Choice_Interactor
As the Interactor moves over various graphical objects, the
Am_INTERIM_SELECTED
slot of the object is set to true for the object which is under the mouse, and false for all other objects. Typically, the graphical objects that the Interactor affects will have a constraint to the Am_INTERIM_SELECTED
slot from the Am_FILL_STYLE
or other slot. At any time, the Interactor can be aborted by typing the key in Am_ABORT_WHEN
(the default is "control_g
"). When the Am_STOP_WHEN
event occurs, the Am_INTERIM_SELECTED
slot is set to false
, and the Am_HOW_SET
slot of the Interactor is used to decide how many objects are allowed to be selected (as explained above). The objects that should end up being selected have their Am_SELECTED
slot set to true
, and the rest of the objects have their Am_SELECTED
slot set to false
. Also the Am_VALUE
slot of the Interactor and the Am_VALUE
slot of the command object in the Am_COMMAND
slot of the Interactor will contain the current value. If Am_HOW_SET
is not Am_CHOICE_LIST_ TOGGLE
, then the Am_VALUE
slot will either contain the selected object or Am_No_Object
(NULL
). If Am_HOW_SET
is Am_CHOICE_LIST_TOGGLE
, then the Am_VALUE
slot of the Command object will contain a Am_Value_List
containing the list of the selected objects (or it will be the empty list).Am_Choice_Interactor
simply resets the Am_SELECTED
slots of the selected object(s) and the Am_Value
of the Interactor and the command object to be as they were before the Am_Choice_Interactor
was run. Redo restores the values. 5.3.5.1.3 Simple Example
See the file testinter.cc for lots of additional examples of uses of Interactors and Command objects. The following Interactor works on any object which is directly a part of the window. Due to the constraints, if you press the mouse down over any rectangle created from rect_proto
that is in the window, it will change to having a thick line style when they mouse is over it (when it is ``interim-selected''), and they will turn white when the mouse button is release (and it becomes selected).
Am_Define_Style_Formula (rect_line) {
if ((bool)self.GV (Am_INTERIM_SELECTED)) return thick_line;
else return thin_line;
}
Am_Define_Style_Formula (rect_fill) {
if ((bool)self.GV (Am_SELECTED)) return Am_White;
else return self.GV (Am_VALUE);
//the real color
}
rect_proto = Am_Rectangle.Create ("rect_proto")
.Set (Am_WIDTH, 30)
.Set (Am_HEIGHT, 30)
.Set (Am_SELECTED, false)
.Set (Am_INTERIM_SELECTED, false)
.Set (Am_VALUE, Am_Purple)
//put the real color here
.Set (Am_FILL_STYLE, rect_fill)
.Set (Am_LINE_STYLE, rect_line)
;
select_inter = Am_Choice_Interactor.Create("choose_rect")
.Set (Am_START_WHERE_TEST, Am_Inter_In_Part);
window.Add_Part (select_inter);
5.3.5.2 Am_One_Shot_Interactor
The Am_One_Shot_Interactor
is used when you want something to happen immediately on an event. For example, you might want a command to be executed when a keyboard key is hit, or when the mouse button is first pressed. The parameters and default behavior for the Am_One_Shot_Interactor
are the same as for a Am_Choice_Interactor
, in case you want to have an object be selected when the start_when event happens. The programmer can choose whether one or more objects can be selected, and the slots Am_INTERIM_SELECTED
and Am_SELECTED
of these objects are set by the Am_One_Shot_Interactor
the same was as the Am_Choice_Interactor
.Am_One_Shot_Interactor
are identical to those for the Am_Choice_Interactor
(see above).5.3.5.2.1 Simple Example
In this example, we create a Am_One_Shot_Interactor
which calls the Am_DO_METHOD of the change_setting_command
(which is
do_change_setting
) when any keyboard key is hit in the window. The change_setting_command
's Am_UNDO_METHOD (which is undo_change_setting
) will be used to undo this action. The programmer would write the methods for do and undo.
Am_Object change_setting_command = Am_Command.Create()
.Set(Am_DO_METHOD, do_change_setting)
.Set(Am_UNDO_METHOD, undo_change_setting);
Am_Object how_set_inter =
Am_One_Shot_Interactor.Create("change_settings")
.Set(Am_START_WHEN, "ANY_KEYBOARD")
.Add_Part(Am_COMMAND, change_setting_command)
;
window.Add_Part (how_set_inter);
5.3.5.3 Am_Move_Grow_Interactor
The Am_Move_Grow_Interactor
is used to move or change the size of graphical objects with the mouse. The default methods in the Am_Move_Grow_Interactor
directly set the appropriate slots of the object to cause it to move or change size. For rectangles, circles, groups and most other objects, the default methods set the Am_LEFT, Am_TOP, Am_WIDTH
and Am_HEIGHT
. For lines (more specifically, any object whose Am_AS_LINE
slot is true
), the methods may instead set the Am_X1, Am_Y1, Am_X2
and Am_Y2
slots.5.3.5.3.1 Special Slots of Move_Grow Interactors
Am_GROWING
: If false or zero, then object is moved without changing its size (or for lines, without changing the orientation or length). If true
or non-zero, then adjusts the size (or a single end-point for a line). The default is false
.
Am_AS_LINE
: If false
or zero, then treats the object as a rectangle and adjusts the Am_LEFT, Am_TOP, Am_WIDTH
and Am_HEIGHT
slots. If true
or non-zero, then if the object is being changed size, then sets the Am_X1, Am_Y1, Am_X2
and Am_Y2
slots (lines can be moved by setting their Am_LEFT
and Am_TOP
slots). The default is a formula that looks at the value of the Am_AS_LINE
slot of the object the Interactor is modifying.
Am_FEEDBACK_OBJECT
: If NULL
(0) (the default), then the actual object moves around with the mouse. If this slot contains an object, however, then that object is used as an interim-feedback object, and it moves around with the mouse, and the actual object is moved or changed size only when the stop-when event happens (e.g., when the mouse button is released). Don't forget to add the feedback object to a group or window in addition to adding it as the Am_FEEDBACK_OBJECT
. While the feedback object is moving around, the original object simply stays in its original position. See Section 5.4.3 about using a window as the feedback object.
Am_WHERE_ATTACH
: This slot controls what part of the object is attached to the mouse as the object is manipulated. The options are defined by the enum type Am_Move_Grow_Where_Attach
in inter.h
. They are:
Am_ATTACH_WHERE_HIT
: (This is the default.) The mouse is attached where the mouse is pressed down. If growing the object, then checks which edge the mouse is closest to, and grows from there.
Am_ATTACH_CENTER
: The center of the object. This is illegal if growing the object.
Am_ATTACH_NW, Am_ATTACH_N, Am_ATTACH_NE, Am_ATTACH_E,Am_ATTACH_SE, Am_ATTACH_S, Am_ATTACH_SW, Am_ATTACH_W
: The mouse is attached at this corner or at the center of this side of the object.
Am_ATTACH_END_1, Am_ATTACH_END_2
: Only available for lines. End_1 is the end defined by Am_X1
and Am_Y1
.
Am_MINIMUM_WIDTH, Am_MINIMUM_HEIGHT
: When growing, these are the minimum legal size. Default is 0.
Am_MINIMUM_LENGTH
: Minimum length when growing lines. Default is 0.
Am_Move_Grow_Interactors
and Am_New_Point_Interactors
. The first is to provide a method, and the second is the provide the gridding origin and multiples:
Am_GRID_METHOD
: (Default is NULL
(0)). If supplied, this should be a method of the type Am_Custom_Gridding_Method
. This function will be given the current x and y and should return the new x and y to use. This kind of gridding is also useful for snapping, ``gravity,'' and keeping the object being dragged inside a region. For example:
Am_Define_Method(Am_Custom_Gridding_Method, void, keep_inside_window,
(Am_Object inter, int x, int y,
int& out_x, int & out_y)) { ... }
//see the file
samples/space/space.cc
for the complete code of this function
Am_GRID_X, Am_GRID_Y
: If Am_GRID_PROC
is not supplied, then these slots can hold the number of pixels the mouse skips over. Default is 0.
m_GRID_ORIGIN_X, Am_GRID_ORIGIN_Y
: These can hold the offset in pixels from the edge of the window for the origin of the gridding. Default is 0.
Am_Move_Grow_Interactor
Am_Move_Grow_Interactor
is operating, it calls the various internal methods. The default operation of these methods is as follows. If this is not sufficient for your needs, then you may need to override the methods, as explained in Section 5.5.2.
If the Interactor's
Am_GROWING
slot is set to true
, the interactor grows the object, otherwise the interactor moves the object. If the Interactor's Am_AS_LINE
slot is false
, the object is moved or grown by setting its Am_LEFT, Am_TOP, Am_WIDTH
and Am_HEIGHT
slots. If the Interactor's Am_AS_LINE
slot is true
, the object is moved or grown by setting its Am_X1, Am_Y1, Am_X2
and Am_Y2
slots. If there is a feedback object in the Am_FEEDBACK_OBJECT
slot then its size is set to the size of the object being manipulated, and its Am_VISIBLE
slot is set to true
. Then it is moved or its size is changed with the mouse. Otherwise, the object itself is manipulated. At any time while the Interactor is running, the abort event can be hit (default is "control-g
") to restore the object to its original position and size. When the stop event happens, then the feedback object is made invisible, and the object is moved or changed size to the final position.Am_Move_Grow_Interactor
simply resets the object to its original size and position. Redo undoes the undo. When selectively repeating the operation, there are two possible interpretations: change the object by the same absolute values, or change by the same relative amounts. We chose to use the absolute values, so repeating the move or grow will put the object in the same place the original object was moved or grown to. Similarly, selective undo will always return the object to where the original object was before the move or grow. 5.3.5.3.4 Simple Example
See the file testinter.cc
for additional examples that use Interactors and Command objects. The following Interactor will move any object in the window when the middle button is held down.
Am_Object move_inter = Am_Move_Grow_Interactor.Create("move_object")
.Set (Am_START_WHERE_TEST, Am_Inter_In_Part)
.Set (Am_START_WHEN, "MIDDLE_DOWN");
window.Add_Part (move_inter);
5.3.5.4 Am_New_Points_Interactor
The Am_New_Points_Interactor
is used for creating new objects. The programmer can specify how many points are used to define the object (currently, only 1 or 2 points are supported), and the Interactor lets the user rubber-band out the new points. It is generally required for the programmer to provide a feedback object for a Am_New_Points_Interactor
so the user can see where the new object will be. If one point is desired, the feedback will still follow the mouse until the stop event, but the final point will be returned, rather than the initial point. Gridding can be used as with a Am_Move_Grow_Interactor
. To create the actual new objects, the programmer provides a call-back function in the Am_CREATE_NEW_OBJECT_METHOD
slot of the Interactor.5.3.5.4.1 Special Slots of
Am_New_Points_Interactors
Am_AS_LINE
: If true, then creates the new object as a line, and sets the Am_X1, Am_Y1
, etc. slots of the feedback object. If false, the default, then creates the new object as a rectangle, and sets the Am_LEFT,
Am_TOP
, etc. of the feedback object.
Am_FEEDBACK_OBJECT
: Object to rubber band to show where the new object will be.
Am_HOW_MANY_POINTS
: The number of points that are desired. Lines, rectangles, etc. are normally defined by two points, which is the default. Currently, the only supported values are 1 and 2.
Am_MINIMUM_WIDTH, Am_MINIMUM_HEIGHT, Am_MINIMUM_LENGTH
: Same as for Am_Move_Grow_Interactor
(Section 5.3.5.3.1).
Am_ABORT_IF_TOO_SMALL
: If true, and if the size is less than the minimum, then no object will be created (if the stop event happens while the object is less than the minimum, then the Interactor aborts). If false (the default), then an object is created with the minimum size.
Am_FLIP_IF_CHANGE_SIDES
: If true, then if the cursor goes above and/or to the left of the original point, the object is flipped. If false, then the new object is pegged at its minimum size. This is only relevant if Am_AS_LINE
is false.
Am_GRID_X, Am_GRID_Y, Am_GRID_ORIGIN_X, Am_GRID_ORIGIN_Y, Am_GRID_PROC
: Same as for Am_Move_Grow_Interactor
(Section 5.3.5.3.2).
Am_CREATE_NEW_OBJECT_METHOD
: Set with a method to create the object; see next section.
Am_New_Point_Interactor
Am_New_Point_Interactor
is operating, it calls the various internal methods. The default operation of these methods is as follows. If this is not sufficient for your needs, then you may need to override the methods, as explained in Section 5.5.2.
While the Interactor is operating, the appropriate slots of the feedback object are set, as controlled by the parameters described above. If the user hits the abort key while the Interactor is running (
"control_g"
by default), the feedback object is made invisible and the Interactor aborts. If the user performs the Am_STOP_WHEN
event (usually by releasing the mouse button), then the Am_CREATE_NEW_OBJECT_METHOD
is called. (If there is no procedure, then nothing happens.) The method must be of type Am_Create_New_Object_Method which is defined as:// type of method in the
Am_CREATE_NEW_OBJECT_METHOD
slot of Am_New_Points_Interactor.
// Should return the new object created.
// ** old_object is Valid if this is being called as a result of a Repeat undo call, and means that a new
// object should be created like that old_object.
Am_Define_Method_Type(Am_Create_New_Object_Method, Am_Object,
(Am_Object inter, Am_Inter_Location location,
Am_Object old_object));
The Am_Inter_Location type is explained in Section 5.3.3.4. (Note: this interface may change when we support more than 2 points). After creating the new object and adding it as a part to some group or window, the procedure should return the new object.
The default undo and selective undo methods remove the object from its owner, and the default redo method adds it back to the owner again. The default repeat method calls the Am_CREATE_NEW_OBJECT_METHOD
again passing a copy of the original object, and the method is expected to make a new object like the old one. If the create has been undone, then when redo or repeat is no longer possible (determined by the type of undo handler in use--Section 5.5), and the saved objects are automatically destroyed.
Note: if you use the
Am_Create_New_Object_Method
for something other than creating objects, then do not have a Am_Create_New_Object_Method
return the affected object, because the buily-in undo methods may automatically delete the object. For example, in space.cc
, a Am_New_Points_Interactor
is used to draw the phaser which deletes objects, and this is handled in the command's DO method. It would be an error to do this from the Am_Create_New_Object_Method
since the Undo method might delete the object.5.3.5.5 Am_Text_Edit_Interactor
The Am_Text_Edit_Interactor
is used for single-line, single-font editing of the text in Am_Text
objects. (Support for multi-line, multi-font text editing will be in a future release.) The default behavior is to directly set the Am_TEXT
and Am_CURSOR_INDEX
slots of the Am_Text
object to reflect the user's changes. Most of the special operations and types used by the Am_Text_Edit_Interactor
are defined in text_fns.h
.Am_START_WHEN
event occurs, the Interactor puts the text object's cursor where the start event occurred. Subsequent events are sent to the editing method in the Am_TEXT_EDIT_METHOD
slot which modifies the Am_Text
object. When the stop_when event happens, the cursor is turned off and the command object's DO_METHOD method is called. The stop_when event is not entered into the string.Am_RUNNING_WHERE_OBJECT
is true, meaning the Interactor will run no matter where the user moves the cursor. If this slot is set to be a particular object, leaving that object causes the Interactor to hide the text object's cursor until the user moves back into the object.Am_Text_Edit_Interactor
's Am_START_WHERE_TEST
slot is set to the value Am_Inter_In_Text_Object_Or_Part
. Am_Text_Edit_Interactor
s only work properly on Am_Text
objects, so one of the text tests should be used for the Am_START_WHERE_TEST
slot.5.3.5.5.1 Special Slots of Text Edit Interactors
Am_WANT_PENDING_DELETE
: If this is true (the default is false), then if the user double-clicks in the string, then the entire string is selected. The next character to be typed (unless it is a cursor movement) will delete the entire string.
Am_TEXT_EDIT_METHOD
: This is a method of type Am_Text_Edit_Method
(in inter.h
) which is defined as:
Am_Define_Method_Type(Am_Text_Edit_Method, void,
(Am_Object text, Am_Input_Char ic, Am_Object inter));
Am_TEXT
field given the input character ic
. It can also modify the Am_CURSOR_INDEX
slot of the object, but shouldn't change other slots. The default function: Am_Default_Text_Edit_Method
, uses the Am_Edit_Translation_Table
specified in the Interactor's Am_EDIT_TRANSLATION_TABLE
slot to provide the basic editing operations (see the description of the Am_EDIT_TRANSLATION_TABLE
slot below). If the input character doesn't match any operation in the translation table, the default edit function does the following:
' '
(SPACE) and '~'
are inserted into the string before the cursor.
Am_EDIT_TRANSLATION_TABLE
: This is an Am_Edit_Translation_Table
(defined in the file text_fns.h
), a table that maps input characters to Am_Text_Edit_Operations
. Am_Edit_Translation_Table::Default_Table()
defines the following mappings:
CONTROL_h, BACKSPACE, DELETE
: delete character before cursor
CONTROL_w, CONTROL_BACKSPACE, CONTROL_DELETE
: delete word before cursor
CONTROL_d
: delete character after cursor
CONTROL_u
: delete the entire string
CONTROL_k
: delete string from cursor to end of line
CONTROL_b, LEFT_ARROW
: move cursor one character to the left
CONTROL_f, RIGHT_ARROW
: move cursor one character to the right
CONTROL_a
: move cursor to beginning of line
CONTROL_e
: move cursor to end of line
CONTROL_y
: insert the contents of the X cut buffer at the cursor position
CONTROL_c
: copy the current string into the X cut buffer
Am_STOP_WHEN
character, but pass the mouse down event on so it can do other actions as well.
Am_Edit_Text_Interactor
Am_TEXT
slot in the Am_OLD_VALUE
slot of the Interactor, and it moves the cursor to the location specified by where the Interactor start event occurred. All subsequent events are passed to the Am_Text_Edit_Method
specified in the Interactor's Am_TEXT_EDIT_METHOD
slot. An abort event causes the original text object's text to be restored, and the object's Am_CURSOR_INDEX
is set to Am_NO_CURSOR.
When the stop event occurs, the Command object's Am_VALUE
slot is set to the new value of the text object's Am_TEXT
slot, and the text object's Am_CURSOR_INDEX
is set to Am_NO_CURSOR
. The stop_when event is not entered into the string.Undo and selective undo restore the text object to its previous value, and redo undoes the undo. Repeat sets the text object to have the string that the user edited it to.
Am_Gesture_Interactor
records a gesture, which is the path traced out by the mouse (or other pointing device) while the interactor is running. Beginning with its start event, the gesture interactor records the location of every input event until the interactor stops or aborts. When the interactor stops, it saves the gesture as a list of points (a Am_Point_List
) in its Am_POINT_LIST
slot. This behavior (which is the default) is also useful for a free-hand drawing tool, which would retrieve the point list and install it in a Am_Polygon
object to create a curve duplicating the mouse path.The real power of the gesture interactor, however, lies in its ability to recognize and classify gestures into categories defined by the programmer. The categories are defined by a
Am_Gesture_Classifier
object installed in the gesture interactor's Am_CLASSIFIER
slot. (The procedure for creating a classifier is explained in Section 5.3.5.6.1) When a classifier is installed, the gesture interactor attempts to classify each gesture into one of the categories. If a gesture is successfully recognized, the name of its category (a Am_String
) is stored in the Am_VALUE
slots of the interactor and its command object. If a gesture is unrecognized -- that is, if it is too different from the prototypical gestures in each category -- then Am_VALUE
is set to 0.
In addition, to simplify applications where gestures represent commands (such as cut, copy, or paste), the gesture interactor can take a list of command objects in its
Am_ITEMS
slot. Before the interactor invokes its command object, it first searches the Am_ITEMS
list for a command object whose Am_LABEL
is identical to Am_VALUE
(which is the gesture's name if it was recognized and NULL if not). The first matching command object in the list is invoked instead of the interactor's command object. If no command object in Am_ITEMS
matches the gesture, then the interactor's command object is invoked instead. Thus, a gesture interactor can be configured much like a menu widget or button panel widget, by supplying a list of commands.Am_FEEDBACK_OBJECT
slot to a graphical object which has a Am_POINT_LIST
slot. As the interactor records a gesture, it updates the feedback object's point list. The usual feedback object is a Am_Polygon
with no fill style, which draws an unfilled, black curve as the user gestures. Here is some code that creates a feedback object:Am_Polygon.Create()
Note: the feedback object must be added to a window or group, or it will never be drawn.
.Set (Am_FILL_STYLE, 0);
Am_CLASSIFIER
: default (0).
Am_ITEMS
: default (0).
Am_COMMAND
of the interactor: a command that gets the list of points from the interactor's Am_POINT_LIST
slot and uses it.
Am_FEEDBACK_OBJECT
: a Am_Polygon
with no fill style which is part of a group or window.
Am_CLASSIFIER
: a Am_Gesture_Classifier
object.
Am_ITEMS
: a Am_Value_List
of command objects whose labels correspond to the category names in the classifier.
Am_COMMAND
of the interactor: a command that handles the case when the gesture is not recognized.
Am_FEEDBACK_OBJECT
: a Am_Polygon
with no fill style which is part of a group or window.
Am_CLASSIFIER
: a Am_Gesture_Classifier
object.
Am_ITEMS
: default (0).
Am_COMMAND
of the interactor: the ``DO'' method will access the command's Am_VALUE
, which will be the name of the gesture's category, or 0 if the gesture was unrecognizable.
Am_FEEDBACK_OBJECT
: a Am_Polygon
with no fill style which is part of a group or window.
In Amulet, gesture classifiers are trained in a standalone application, called Agate. Agate may be found in
samples/agate
under the Amulet root directory. To create a classifier using Agate, add a class for each different type of gesture, giving a unique name to each class. For instance, a drawing program might have a class called ``line'' which contains straight-line gestures, and a class called ``circle'' which contains looping gestures. To demonstrate examples for a gesture class, select the class, then draw its gestures in the large empty area at the bottom of the Agate window. To produce the most forgiving classifier, try to include examples with varying size, orientation, and direction (except where your gesture classes rely on such information for uniqueness), and provide 10 to 20 examples for each class. At any point while training a classifier, you can switch from Train mode to Recognize mode in order to test the classifier. In Recognize mode, the classifier attempts to recognize the gesture you draw, highlighting the class to which it most likely belongs.Am_Gesture_Classifier
and load the classifier file into it. A classifier can be loaded in either of two ways: by specifying the filename in the constructor:Am_Gesture_Classifier my_classifier (``my-classifier-file.cl'');
or by opening the file as a stream and using >>
:Am_Gesture_Classifier my_classifier;
ifstream in(``my-classifier-file.cl'');
in >> the_classifier;
After the Am_Gesture_Classifier object is initialized with a classifier, it can be installed into the Am_CLASSIFIER
slot of a gesture interactor:gesture_interactor.Set (Am_CLASSIFIER, my_classifier);
5.3.5.6.2 Special Slots of Gesture Interactors
Am_CLASSIFIER
: a Am_Gesture_Classifier
wrapper, which defines the classes of gestures that can be recognized. If this slot is 0 (the default), then gestures are left uninterpreted.
Am_FEEDBACK_OBJECT
: a graphical object that displays a trace of the gesture as it is drawn. The interactor controls the feedback object by setting its Am_POINT_LIST
slot, so typically a Am_Polygon
object is used. The feedback object must be part of a visible group and/or window in order to be seen.
Am_ITEMS
: a list of Am_Command
objects whose Am_LABEL
slots correspond to the gesture names in the classifier. When the interactor recognizes a gesture matching one of these command objects, the Am_DO_METHOD
of the matching command object is invoked instead of the interactor's own command object.
Am_POINT_LIST
: set by the interactor with the list of points in the gesture. This slot is updated while the interactor is running, reflecting the points that have been traced out up to that moment. This slot can be accessed by the DO methods to get the actual points used in the gesture.
Am_VALUE
: set by the interactor with the name (a Am_String
) of the recognized gesture. If the gesture is unrecognizable, this slot is set to 0.
Am_MIN_NONAMBIGUITY_PROB
: the minimum allowable probability that a recognized gesture has been correctly classified. Whenever the interactor classifies a gesture in category X, it calculates the probability that the gesture actually belongs to X. If this estimate is lower than Am_MIN_NONAMBIGUITY_PROB
, then the classification is rejected, and the gesture is considered unrecognized. Intuitively, this parameter determines whether the gesture interactor will refuse to recognize gestures in the ``gray areas'' where gesture classes overlap, and controls how large the unrecognizable areas should be. The default for this slot is 0, so even the most ambiguous gesture is given a classification. If a stricter classifier is desired, values between 0.90 and 0.99 are reasonable. See Rubine's thesis for a full explanation.
Am_MAX_DIST_TO_MEAN
: the maximum allowable Mahalanobis distance that a recognized gesture may lie from the mean feature vector of its class. Intuitively, this parameter determines whether the gesture interactor will reject gestures that are dissimilar from all gesture classes, and controls the degree of dissimilarity required for rejection. Larger values of this slot accept more outlying gestures, but the special value of 0 (the default) turns off distance detection, so even the most outlying gesture is given a classification. If a stricter classifier is desired, values between 50 and 200 are reasonable.
Am_MIN_X
, Am_MIN_Y
, Am_MAX_X
, Am_MAX_Y
: the bounding box of the path.
Am_TOTAL_LENGTH
: the total length of the path, computed as the sum of the lengths of its segments.
Am_TOTAL_ANGLE
: the total angle traversed by the path, in radians, computed as the sum of the signed angles of its segments (treated as vectors). For example, a zig-zag path which turns left 90 degrees, then turns right 90 degrees, has zero total angle.
Am_ABS_ANGLE
: the total absolute angle of the path, in radians, computed as the sum of the abolute values of the segment angles.
Am_SHARPNESS
: the sum of the squares of the angles traversed, in radians. (Thus, a path which smoothly turns has a lower sharpness value than a path which makes an abrupt turn.)
Am_START_X
, Am_START_Y
: the starting point on the path.
Am_INITIAL_SIN
, Am_INITIAL_COS
: the sine and cosine of the initial segment of the path, i.e., the vector from the first point to the second point.
Am_DX2
, Am_DY2
: the horizontal and vertical differences between the last two points.
Am_MAGSQ2
: the length of the last segment on the path.
Am_END_X
, Am_END_Y
: the last point.
Am_Gesture_Interactor
While the Interactor is running, it appends the points visited by the user's mouse to the
Am_Point_List
in its Am_POINT_LIST
slot. If a feedback object has been provided, it also appends the points to the feedback object's Am_POINT_LIST
slot. If the user hits the abort key while the Interactor is running ("control_g"
by default), the feedback object is made invisible and the Interactor aborts.Am_STOP_WHEN
event (usually by releasing the mouse button), then the interactor attempts to recognize the gesture using the classifier in its Am_CLASSIFER
slot. If the gesture is successfully recognized, then its name is stored in Am_VALUE
. Otherwise, if Am_CLASSIFIER
is 0, or if the gesture cannot be recognized because it is too ambiguous (by Am_MIN_NONAMBIGUITY_PROB
) or too different from the known gestures (by Am_MAX_DIST_TO_MEAN
), then 0 is stored in Am_VALUE
. Am_ITEMS
slot for a command object whose Am_LABEL
matches Am_VALUE
(which, recall, is either the gesture name or 0 if the gesture was unrecognized). If a matching command is found, the interactor invokes its DO method; otherwise, it invokes the DO method of the command in the interactor's Am_COMMAND
slot.5.4 Advanced Features
5.4.1 Output Slots of Interactors
As they are operating, Interactors set a number of slots in themselves which you can access from the Command's DO
procedure, or from constraints that determine slots of the object. The slots set by all Interactors are:
Am_START_OBJECT:
Set with the object returned by the Am_START_WHERE_TEST
each time the Interactor starts. This might be useful, for example, if there are two types of ``handles'' connected to objects that are to be modified: one for moving and one for growing, distinguished by the value of the IS_A_MOVING_HANDLE
slot. Then you might have a formula in the Am_Growing
slot of a Am_Move_Grow_Interactor
as follows:
Am_Define_Formula (bool, grow_or_move) {
Am_Object start_object;
start_object = self.GV(Am_START_OBJECT);
if ((bool)start_object.GV(IS_A_MOVING_HANDLE)) return false;
else return true;
}
Am_START_CHAR:
The initial Am_Input_Char
that started the Interactor. This is most useful when the Am_START_WHEN
slot is something like Am_ANY_KEYBOARD
, or Am_ANY_MOUSE_DOWN
. For example, you might put a constraint in the Am_As_Line
slot that depends on which mouse button starts the Interactor. In the following, a line is created when the SHIFT
key is held down, otherwise to a rectangle is created:
Am_Define_Formula (bool, as_line_if_shift) {
Am_Input_Char start_char =
Am_Input_Char::Narrow(self.GV(Am_START_CHAR));
if (start_char.shift) return true;
else return false;
}
Am_FIRST_X, Am_FIRST_Y:
The initial X and Y locations of the mouse when the Interactor started. These are in the coordinate system of the window the initial event was in.
Am_WINDOW:
The window the Interactor is currently running in. Like graphical objects, this slot shows which window the Interactor is currently attached to. For Interactors that run over multiple windows (see Section 5.4.3), this slot is continuously updated with the current window.
Am_CURRENT_OBJECT:
The current object the Interactor is working on. This will be object returned by Am_START_WHERE_TEST
when the Interactor starts running, and then by the Am_RUNNING_WHERE_TEST
while the Interactor is running. This is most useful for Am_Choice_Interactors
where the object the Interactor is running over changes as the Interactor runs. Most interactors use Am_RUNNING_WHERE_TEST
of true
, in which case the Am_CURRENT_OBJECT
is set to the window.
5.4.2 Priority Levels
When an input event occurs in a window, Amulet tests the Interactors attached to objects in that window in a particular order. Normally, the correct Interactor is executed. However, there are cases where the programmer needs more control over which Interactors are run, and this section discusses the two slots which control this:
Am_PRIORITY
: The priority of this Interactor. The default is 1.0
.
Am_RUN_ALSO
: If true
, then let other Interactors run after this one is completed. The default is false
.
The priority of the Interactor is stored in the
Am_PRIORITY
slot and can be any positive or negative number. When an Interactor starts running, the priority level is increased by a fixed amount (defined by Am_INTER_PRIORITY_DIFF
which is 100.0
and is defined in inter_advanced.h
). This makes sure that Interactors that are running take priority over those that are just waiting. If you want to make sure that your Interactor runs before other default Interactors which may be running, then use a priority higher than 101.0
. For example, the debugging Interactor which pops up the inspector (see Section 5.6) uses a priority of 300.0
.
For Interactors with the same priority, the Interactor attached to the front and leaf most graphical object will take precedence. This is implemented using the slots
Am_OWNER_DEPTH
and Am_RANK
of the graphical objects which are maintained by Opal. What this means is that an Interactor attached to a part has priority over an Interactor attached to the group that the part is in, if they both have the same value in the Am_PRIORITY
slot. Note that this determinations does not take into account which objects the Interactor actually affects, just what object the Interactor is a part of. Thus, if Interactor A is attached to group G and has a Am_START_WHERE_TEST
of Am_Inter_In_Part
, and Interactor B is attached to part P which is in G and has a Am_START_WHERE_TEST
of Am_Inter_In
, then the Interactor on B will take precedence by default, even though both A and B can affect P.Am_RUN_ALSO
slot set to true
(the default is false
), then other Interactors will also get to process the current event. Thus, the run-also Interactor operates without grabbing the input event. This might be useful for Interactors that just want to monitor the activity in a window, say to provide a ``tele-pointer'' in a multi-user application. In this case, you would want the Interactor with Am_RUN_ALSO
set to true
to have a high priority.Am_RUN_ALSO
slot set to false
accepts the current event, the system will continue to search to find if there are any other Interactors with Am_RUN_ALSO
slot set to true
that have the same priority as the Interactor that is running. These are also allowed to process the current event.5.4.3 Multiple Windows
A single Interactor can handle objects which are in multiple windows. Since an Interactor must be attached to a single window, graphical object or group, a special mechanism is needed to have an Interactor operate across multiple windows. This is achieved by using the Am_MULTI_OWNERS
slot. The value of this slot can be:
false
or zero: which means that this slot is ignored, and the Interactor only works on the window of the object it is attached to. This is the default.
true
(or any non-zero integer): the Interactor operates on all windows created using Amulet, now or in the future.
Am_Value_List
containing a list of objects or windows, which means that the Interactor works in all the windows of the objects. This list must include the ``main'' window of the object that the interactor is part of.
Am_START_WHERE_TEST
slot must search for objects in all of the appropriate windows. It might use the value of the Am_WINDOW
slot of the Interactor, which will contain the window of the current event. This is normally sufficient for Am_Choice_Interactors
, Am_One_Shot_Interactors
, and Am_Text_Edit_Interactors
that want to operate on objects in multiple windows. For these interactors, the Am_MULTI_OWNERS
slot can be either a list of windows or a list of objects in those windows. Be sure to include the window to which the interactor is attached.Special features are built-in to support interactors that might want to move an object from one window to another, such as
Am_Move_Grow_Interactors
, Am_New_Points_Interactors
, and Am_Gesture_Interactors
. In this case, the Am_MULTI_OWNERS
slot should contain a list of objects which should serve as the owners of the graphical objects as they are moved from one window to another. Then, the interactor will automatically search the Am_MULTI_OWNERS
list for an object in the window that the cursor is currently in, and if found, then the object being moved (returned by the Am_START_WHERE_TEST
) will change to have that object as its owner.Often the feedback objects should be in a different owner than the ``real'' objects being moved. In this case, you can set the
Am_MULTI_FEEDBACK_OWNERS
slot with a list of owner objects for the feedback object. In this case, the feedback object of the interactor (specified in the Am_FEEDBACK_OBJECT
slot of the interactor) will automatically be changed to be in the object of the appropriate window. If the Am_MULTI_FEEDBACK_OWNERS
slot is NULL
, then the owners in the Am_MULTI_OWNERS
slot are used for the feedback object as well.The
Am_FEEDBACK_OBJECT
can also be a top-level window object, in which case the various types of interactor objects will move the object around on the screen. This is particularly useful when you want to be sure to see the feedback even when the cursor is not over an Amulet window. For move-grow interactors when using a window as the feedback, you should use something like Am_ATTACH_NW
for the Am_WHERE_ATTACH
slot.Examples of using multi-window interactors are in the test file
inter/testinter.cc
.
Am_RUNNING_WHERE_OBJECT
slot. This slot should contain either a graphical object or true
, which means anywhere (so the Interactor never goes outside). The default for most Interactors for this slot is true
, but for Choice Interactors, this slot contains a constraint that makes it have the same object as where the Interactor starts. To refine where the Interactor should be considered outside, the programmer can also supply a value for the Am_RUNNING_WHERE_TEST
slot, which defaults to Am_Inter_In
except for choice Interactors, where it contains a constraint that uses the same function as the Am_START_WHERE_TEST
. We have found that programmers rarely need to specify the running where of Interactors.
5.4.5 Starting, Stopping and Aborting Interactors
Interactors normally start, stop and abort due to actions by the user, but it is sometimes useful for the programmer to be able to explicitly control the Interactors. The following functions are useful for controlling this. If you have a widget, you would use the corresponding widget functions instead (Am_Start_Widget, Am_Stop_Widget, and Am_Abort_Widget -- Section 5.5).
extern void Am_Abort_Interactor(Am_Object inter);
Am_Abort_Interactor
causes the Interactor to abort (stop running). The command associated with the Interactor is not queued for Undo.extern void Am_Stop_Interactor(Am_Object inter,
Am_Object stop_obj = Am_No_Object,
Am_Input_Char stop_char = Am_Default_Stop_Char,
Am_Object stop_window = Am_No_Object, int stop_x = 0,
int stop_y = 0);
Am_Stop_Interactor explicitly stops an interactor as if it had completed normally (as if the stop event had happened). The command associated with the interactor is queued for Undo, if appropriate. If the interactor was not running, Am_Stop_Interactor raises an error. If the stop_obj
parameter is not supplied, then Am_Stop_Interactor uses the last object the interactor was operating on (if any). stop_char is the character sent to the Interactor's routines to stand in for the final event of the Interactor. If stop_window
is not supplied, then will use stop_obj
's window, and stop_x
and stop_y
will be stop_obj
's origin. If stop_window
is supplied, then it should be the window that the final event is with respect to, and you must also supply stop_x
and stop_y
as the coordinates in the window at which the interactor should stop.extern void Am_Start_Interactor(Am_Object inter,
Am_Object start_obj = Am_No_Object,
Am_Input_Char start_char = Am_Default_Start_Char,
Am_Object start_window = Am_No_Object, int start_x = 0,
int start_y = 0);
Am_Start_Interactor is used to explicitly start an Interactor. If the interactor is already running, this does nothing. If start_obj
is not supplied, then will use the inter
's owner. If start_window
is not supplied, then uses start_obj
's and sets start_x
and start_y
to start_obj
's origin. start_char is the initial character to start the interactor with. If start_window
is supplied, then you must also supply start_x
and start_y
as the initial coordinate (with respect to the window) for the Interactor to start at.Am_START_WHEN
slot be NULL, and then start it explicitly using the Am_Start_Interactor procedure. Similarly, the Am_STOP_WHEN
slot can be NULL for an Interactor that should never stop by itself.5.4.6 Support for Popping-up Windows and Modal Windows
It is often useful to be able to pop up a window, and wait for the user to respond. This can eliminate having to chain a number of DO methods together when you just want to ask the user a question. Amulet provides low-level functions to support this. If the window to ask the question is one of a few standard types, you might alternatively use the built-in dialog boxes described in Section 6.3 of the widgets chapter.
The way the low level routines are used is that the programmer calls
Am_Pop_Up_Window_And_Wait
passing in the window to wait on, and then that window contains a number of widgets, at least one of which (usually the ``OK'' button) calls Am_Finish_Pop_Up_Waiting
as part of its DO method. The value passed to Am_Finish_Pop_Up_Waiting
is then returned by the Am_Pop_Up_Window_And_Wait
call.extern void Am_Pop_Up_Window_And_Wait(Am_Object window,
Am_Value &return_value,
bool modal = true);
Am_Pop_Up_Window_And_Wait
sets the visible of the window to true, and then waits for a routine in that window to call Am_Finish_Pop_Up_Waiting on that window. Returns the value passed to Am_Finish_Pop_Up_Waiting by setting the return_value
parameter. If modal
is true, the default, then the user will only be able to work on this one window, and all other windows of this application will be frozen (input attempts to other windows will beep). Note that input can still be directed to other applications, unlike modal dialog boxes on the Macintosh. extern void Am_Finish_Pop_Up_Waiting(Am_Object window,
Am_Value return_value);
Am_Finish_Pop_Up_Waiting sets window
's Am_VISIBLE
to FALSE, and makes the Am_Pop_Up_Window_And_Wait
called on the same window return with the value passed as the return_value
. Although there is no default, it is acceptable to pass Am_No_Value
as the return value.Am_Value val;
Am_Pop_Up_Window_And_Wait(my_query_window, val);
if (val.Valid()) ...
else ...;
5.5 Customizing Interactor Objects
Amulet allows the programmer to customize the behavior of Interactors at multiple levels. As described in the previous sections, many aspects of the behavior of Interactors can be controlled through the parameters of the Interactors. We believe that in almost all cases, programmers will be able to create their applications by using these built-in parameters of the pre-defined types of Interactors. If you are happy with the standard behavior, but want some additional actions to happen when the interactor is finished, then you can attach a custom Command object to the interactor, as described in Section 5.5.1. If you want behavior similar to a standard Interactor,. but slightly different, then you might want to override some of the standard methods that implement the Interactor's behavior, as described in Section 5.5.2. However, there might be rare cases when an entirely new type of Interactor is required, as described in Section 5.5.3. For example, in Garnet which had a similar Interactor model, none of the applications created using Garnet needed to create their own Interactor types. However, when the Garnet group wanted to add support for Gesture recognition, this required writing a new Interactor. Since Amulet is designed to support investigation into new interactive styles and techniques, new kinds of Interactors may be needed to explore new types of interaction beyond the conventional direct manipulation styles supported by the built-in Interactors. In summary, we feel you should only need to create a new kind of Interactor when you are supporting a radically different interaction style.
5.5.1 Adding Behaviors to Interactors
If you want the standard behavior of an interactor plus some additional behavior, you can override the methods of the Command object in the Interactor. (See Section 5.6 for a complete discussion of Command objects.) The command object in the Interactors all have empty methods, so you can override the methods without concern. The methods that you can override include:
Am_START_DO_METHOD
: Called once each time the Interactor starts running.
Am_INTERIM_DO_METHOD
:Called each time there is an input event.
Am_ABORT_DO_METHOD
: Called when the Interactor should abort.
Am_DO_METHOD
: Called when the stop action happens and the Interactor should terminate normally.
Am_Object_Method
: which just takes the command object as a parameter.
Am_Define_Method(Am_Mouse_Event_Method, void, my_mouse_method,
(Am_Object inter_or_cmd, int mouse_x, int mouse_y,
Am_Object ref_obj, Am_Input_Char ic));
Am_Current_Location_Method
: which takes the command object, the object_modified, which is usually the object that the Interactor is modifying, and the description of the current input event position (see Section 5.3.3.4). This type of method can only be used for move_grow and new_point interactors. An example:
Am_Define_Method(Am_Current_Location_Method, void, my_do_method,
(Am_Object inter, Am_Object object_modified,
Am_Inter_Location data)) { ... }
Am_Current_Location_Method
cannot be used for command objects in widgets, only for the command objects in Interactors. The command objects in widgets only support Am_Object_Method
.In addition to the information passed as parameters to the command object, slots of the command object and of the Interactor are set by the Interactor and may be of use to the methods. For all command objects, the following slots are available:
In the command objects:
Am_OWNER
: While the methods are running, the Am_OWNER
of the command is the Interactor, so you can use Get_Owner() to access the slots. However, the command object is no longer part of the Interactor at Undo time, so all of the UNDO
methods should use the Am_SAVED_OLD_OWNER
slot instead.
Am_SAVED_OLD_OWNER
: Set with the Interactor. This stays the Interactor the command was attached to, so you can use this in Undo methods.
5.5.1.1 Available slots of Am_Choice_Interactor and Am_One_Shot_Interactor
Am_OLD_INTERIM_VALUE
which is set with an object or NULL which is the previously interim selected object.
Am_INTERIM_VALUE
which is set with the newly selected object.
Am_VALUE
which is set with the final result which may be NULL, an object (if only a single object can be selected) or a Am_Value_List
of objects.
Am_OLD_VALUE
which is set with the previous value of Am_VALUE
for use if the command is undone.
Am_OBJECT_MODIFIED
which is the object being moved or changed size.
Am_INTERIM_VALUE
which contains a Am_Inter_Location
of the current position and size of the object.
Am_OLD_VALUE
which holds a copy of the old (original) value as an Am_Inter_Location
, used in case the Interactor is aborted or later undone.
Am_VALUE
(only available to the DO and various UNDO methods) which contains the final Am_Inter_Location
used to set the position and size of the object.
Am_TOO_SMALL
(a bool) which is set by the interactor if the size is currently smaller than the minimum allowed. The default method turns off the feedback if Am_TOO_SMALL
is true.
Am_INTERIM_VALUE
which contains a Am_Inter_Location
for the current position and size of the feedback object.
m_VALUE
(only available in the DO and various UNDO methods) which holds the object which was created as a result of this Command.
Am_OBJECT_MODIFIED
- the object being edited, which will be an instance of Am_Text
.
Am_OLD_VALUE
- the original string for the object, as a Am_String
, in case the user aborts or calls Undo.
Am_VALUE
(only available to the DO and various UNDO methods) which is the new (final) string for the object.
Am_START_DO_METHOD
: Called once each time the Interactor starts running.
Am_INTERIM_DO_METHOD
:Called each time there is an input event.
Am_ABORT_DO_METHOD
: Called when the Interactor should abort.
Am_DO_METHOD
: Called when the stop action happens and the Interactor should terminate normally.
Am_Object_Method
, Am_Mouse_Event_Method, or Am_Current_Location_Method
, as defined in Section 5.5.1.
Remember that if you override the various DO methods, this will remove the standard behavior of the Interactors, so there may be some of the behavior you may want to re-implement in your code. For example, the
Am_ABORT_DO_METHOD
and the Am_DO_METHOD
take care of making the feedback object (if any) invisible. The Am_ABORT_DO_METHOD
is also responsible for restoring the object to its original state.5.5.3 Entirely New Interactors
This section gives an overview of the how to build an entirely new type of Interactor. As said above, we believe this almost never be necessary. You may need to look at the source code for one of the built-in Interactors to see how they operate in detail.Am_CURRENT_STATE
(see the state machine figure in Section 5.3.2). The different Interactors are distinguished by having different functions for these messages. All of the messages are of type Am_Inter_Internal_Method
(defined in inter_advanced.h
). Most of these methods set up some slots of the Interactor, and then call the appropriate Interactor DO method. The specific internal methods you need to write for a new type of Interactor are stored in the following slots:
Am_INTER_START_METHOD
: This is called when the Interactor should first start running. Typically, the method would initialize various fields and then call the Am_START_DO_METHOD of the Interactor and then the Command object. It will then call the Am_INTERIM_DO_METHOD of the Interactor and then the Command object on the first point.
Am_RUNNING_WHERE_OBJECT
while the Interactor is running. Typically it will call the Am_ABORT_DO_METHOD of the Interactor and then the Command object.
Am_RUNNING_WHERE_OBJECT
while the Interactor is running. Typically it will call the Am_START_DO_METHOD followed by the Am_INTERIM_DO_METHOD of the Interactor and then the Command object.
Am_INTER_STOP_METHOD
: This is called when the Interactor stops (finishes). Typically it will set some slots in the Command object and then call the Am_DO_METHOD
of the Interactor and then the Command object.
Am_INTER_OUTSIDE_STOP_METHOD
: This is called when the user executes the stop event while the Interactor is outside. For all the built-in Interactors, this method is not needed because the default is to call the Am_INTER_ABORT_METHOD
.
Am_COMMAND
which contains a Am_Command
object. For all widgets and Interactor commands, the default methods are empty, so you can freely supply any method you want. There are also a set of command objects supplied in the library which you might be able to use in applications, as described in Section 6.4 of the Widgets chapter. If you create your own custom command objects, be sure to create UNDO methods, as described in Section 5.6.2.
The top-level definition of a Command object is:
Am_Command = Am_Root_Object.Create ("Am_Command")
.Set (Am_DO_METHOD, NULL)
.Set (Am_UNDO_METHOD, NULL)
.Set (Am_REDO_METHOD, NULL)
.Set (Am_SELECTIVE_UNDO_METHOD, NULL)
.Set (Am_SELECTIVE_REPEAT_SAME_METHOD, NULL)
.Set (Am_SELECTIVE_REPEAT_ON_NEW_METHOD, NULL)
.Set (Am_SELECTIVE_UNDO_ALLOWED, Am_Standard_Selective_Allowed)
.Set (Am_SELECTIVE_REPEAT_SAME_ALLOWED, Am_Standard_Selective_Allowed)
.Set (Am_SELECTIVE_REPEAT_NEW_ALLOWED,
Am_Standard_Selective_New_Allowed)
.Set (Am_ACTIVE, true)
.Set (Am_LABEL, "A command")
.Set (Am_SHORT_LABEL, 0)
.Set (Am_ACCELERATOR, 0) //if 0 then uses Am_LABEL
// event to also execute this
.Set (Am_VALUE, 0)
.Set (Am_OLD_VALUE, 0)
//usually for undo
.Set (Am_OBJECT_MODIFIED, 0)
.Set (Am_SAVED_OLD_OWNER, NULL)
.Set (Am_IMPLEMENTATION_PARENT, 0)
;
Most Command objects supply a Am_DO_METHOD
procedure which is used to actually execute the Command. It will typically also store some information in the Command object itself (often in the Am_VALUE
and Am_OLD_VALUE slots) to be used in case the Command is undone. The Am_UNDO_METHOD
procedure is called if the user wants to undo this Command, and usually swaps the object's current values with the stored old values. The Am_REDO_METHOD
procedure is used when the user wants to undo the undo. Often, it is the same procedure as the Am_UNDO_METHOD
. The various SELECTIVE_
methods support selective undo and repeat the command, as explained in Section 5.6.2.3. The Am_ACTIVE
slot controls whether the Interactor or widget that owns this Command object should be active or not. This works because widgets and Interactors have a constraint in their active field that looks at the value of the Am_ACTIVE
slot of their Command object. Often, the Am_ACTIVE
will contain a constraint that depends on some state of the application, such as whether there is an object selected or not. The Am_LABEL
slot is used for Command objects which are placed into buttons and menus to show what label should be shown for this Command. If supplied, the Am_SHORT_LABEL
is used in the Undo dialog box to label the command. For commands in button and menu widgets, the Am_ACCELERATOR
slot can contain an Am_Input_Char
which will be used as the accelerator to perform the command (see Section 6.2.3.2 of the Widgets chapter). For commands in button widgets, the widgets use the command to determine the value to return when the button is hit. If the Am_ID
slot is set, then this value, which can be of any type, is used. If Am_ID
is 0, then the value of the Am_LABEL
slot is returned.
Various slots are typically set by the DO method for use by the UNDO methods. The Am_VALUE
slot is set with the value for use. You have to look at the documentation for each Interactor or Widget to see what form the data in the Am_VALUE
slot is. The DO method typically also sets the Am_OBJECT_MODIFIED
slot with the object (or a Am_Value_List
of objects) that the Command affects. The Am_SAVED_OLD_OWNER
slot is set by the Interactors and Widgets to contain the Interactor and widget itself. Finally, the Am_IMPLEMENTATION_PARENT supports the hierarchical decomposition of commands, as described in the next section (Section 5.6.1).
As mentioned above in Section 5.5.1, command objects which are used in Interactors also support the Am_START_DO_METHOD
, Am_INTERIM_DO_METHOD
, and the Am_ABORT_DO_METHOD
.
5.6.1 Implementation_Parent hierarchy
Normal objects are part of two hierarchies: the prototype-instance hierarchy and the part-owner hierarchy. The Command objects has an additional hierarchy defined by the Am_IMPLEMENTATION_PARENT
slot. Based on the Ph.D. research of David Kosbie, we allow lower-level Command objects to invoke higher-level Command objects. For example, the Command object attached to a move-grow Interactor which is allowing the user to move a scroll bar indicator calls the Command object attached to the scrollbar itself.http://www.cs.cmu.edu/~amulet/papers/commandsCHI.html
).Am_DO_METHOD
of the lowest level command associated with the Interactor or widget, and then looks in the Am_IMPLEMENTATION_PARENT
slot of that command. If that slot contains a command object, then the Am_DO_METHOD
of that command is also called, and so on. Internally, all the widgets use this mechanism to chain commands together, and you can use it also to execute multiple commands on an event.Am_IMPLEMENTATION_PARENT
chain are queued. When the user requests an undo or selective undo or redo, then all the commands in the Am_IMPLEMENTATION_PARENT
chain are undone in the same order as they were originally executed (from child to parent). This is in case lower-level commands set state which is used by higher-level commands.Am_IMPLEMENTATION_PARENT
hierarchy is not usually the same as the part-owner hierarchy. Unfortunately, it seems to be difficult or impossible for Amulet to deduce the parent hierarchy from the part-owner hierarchy, which is why the programmer must explicitly set the Am_IMPLEMENTATION_PARENT
slot when appropriate. Of course, the built-in widgets (like the scroll bar) have the internal Command objects set up appropriately.Am_IMPLEMENTATION_PARENT
slot for the Command object in the `OK' button widget inside a dialog box, so the OK widgets's action will automatically call the dialog box's Command object. Another example is that for the button panel widget (see the Widgets chapter), you can have a Command object for each individual item and/or a Command object for the entire panel. If you want the individual item's Command to be called and the top-level Command to be called, then you would make the top-level Command be the Am_IMPLEMENTATION_PARENT
of each of the individual item Commands.5.6.2 Undo
All of the Command objects built into the Interactors and widgets automatically support full undo, redo and selective undo and repeat. This means that the default Am_DO_METHOD
procedures store the appropriate information. Built-in ``undo-handlers'' know how to copy the command objects when they are executed, save them in a list of undoable actions, and execute the undo, redo and selective methods of the commands. Thus, to have an application support undo is generally a simple process. You need to create an undo-handler object and attach it to a window, and then have some button or menu item in your application call the undo-handler's method for undo, redo, etc.5.6.2.1 Enabling and Disabling Undoing of Individual Commands
If there are operations in the application that are not undoable, for example like File Save, then you should have the Am_UNDO_METHOD
and Am_REDO_METHOD
slots as null. As explained below, there are special methods that determine whether the command is selective undoable and repeatable.Am_IMPLEMENTATION_PARENT
slot of the top-level command to be the constant value Am_NOT_USUALLY_UNDONE
, which is defined in inter.h
. (All commands whose Am_IMPLEMENTATION_PARENT
slot is null are assumed to be top-level commands and are queued for undo.)5.6.2.2 Using the standard Undo Mechanisms
There are three styles of undo supplied by Amulet. These are described in this and the next sections. Section 5.6.2.3 discusses how programmers can implement other undo mechanisms. The undo mechanisms are implemented using Am_Undo_Handler
objects.
The three kinds of undo supplied by Amulet are:
Am_Single_Undo_Object
. This handler supports undoing a single command. The last operation can be undone, and the last undone operation can be redone. As soon as another operation is performed, the previous Command is discarded so it can no longer be undone or redone.
Am_Multiple_Undo_Object
. This handler supports undoing an arbitrary number of Commands, all the way back to the first command. This is implemented by saving all the commands executed since the application is started, so the list can grow quite long. If a command is undone, then it can be redone, but only the last undone command is saved. Thus, after undoing a series of commands, after undoing the last undo (redo), there is nothing available to redo. The Undo itself is not part of the command history.
Am_Multiple_Undo_Object
.
Am_UNDO_HANDLER
slot of a window. For example:
my_win = Am_Window.Create("my_win")
.Set (Am_LEFT, ...)
...
.Set (Am_UNDO_HANDLER, Am_Multiple_Undo_Object.Create("undo"))You can put the same undo-handler object into the
Am_UNDO_HANDLER
slot of multiple windows, if you want a single list of undo actions for multiple windows (for example, for applications which use multiple windows). Now, all the Commands executed by any widgets or Interactors that are part of this window will be automatically registered for undoing.Next, you need to have a widget that will allow the user to execute the undo and redo. The
Am_Undo_Command
object provided by widgets.h encapsulates all you typically need to support undo, and the Am_Redo_Command
supports redo (see Section 5.6 of the widgets chapter). There is also a dialog box provided to support selective undo (next section).
If the built-in undo and redo commands are not sufficient, then you can easily create your own. The command's
Am_ACTIVE
slot should depend on whether the undo and redo are currently allowed. The undo_handler objects provide the Am_UNDO_ALLOWED
slot to tell whether undo is allowed. This slot contains the command object that will be undone (in case you want to have the label of the Undo Command show what will be undone). The Am_REDO_ALLOWED
slot of the undo object tells whether redo is allowed and it also will contain a command object or NULL. To actually perform the undo or redo, you call the method in the Am_PERFORM_UNDO
or Am_PERFORM_REDO
slots of the undo object. These methods are of type Am_Object_Method.5.6.2.3 The Selective Undo Mechanism
In addition to the regular multiple-undo mechanism, Amulet now supports a novel selective undo mechanism. This allows the user to select previously executed commands from the history and undo or repeat them. See the conference paper mentioned above for a complete description.5.6.2.3.1 Supporting Selective Undo in your own Command Objects
Regular undo and redo operate on the original command object, and remove or add it to the undo list. However, selective undo and repeat create a copy of the original command object, and then add this copy to the top of the undo list. Thus, using the regular undo method or selective undo of the most recent command should both have the same effect to the application, they have very different affects on the undo list. (Thus, you can implement an undo like in the Emacs editor, where all undo's are added to the command history, by simply always using selective undo instead of the ``regular'' undo.) This copying is automatically handled by the default undo handlers (including renaming the command to have ``Undo'' or ``Repeat'' in front of the name).
Am_SELECTIVE_UNDO_ALLOWED
: This method determines whether the command can be undone given the current context. For example, if the command was a change color, this method might check whether the object operated on is still visible. This method is of type Am_Selective_Allowed_Method
which takes the command object and returns a boolean. Most commands will find the default method, Am_Standard_Selective_Allowed
, is sufficient for their needs, if they store the information into the standard slots. This method checks whether the object or list of objects in the Am_OBJECT_MODIFIED
slot of the command object is valid and still visible in the window. However, some commands, like deleting, are undoable when the object is not visible, so these commands will need a custom Am_SELECTIVE_UNDO_ALLOWED
method.
Am_SELECTIVE_UNDO_METHOD
: This method performs the undo on the original object or objects. The method is of the type Am_Object_Method
and just takes the command object to be undone.
Am_SELECTIVE_REPEAT_SAME_ALLOWED
: This determines whether the command can be repeated on the original object. The type of the method is Am_Selective_Allowed_Method
, and the default method is Am_Standard_Selective_Allowed
, which is the same as for the Am_SELECTIVE_UNDO_ALLOWED
method.
Am_SELECTIVE_REPEAT_SAME_METHOD
: This does the command again. The method is of the type Am_Object_Method
and just takes the command object to be undone.
Am_SELECTIVE_REPEAT_NEW_ALLOWED
: This returns true if the command can be selectively redone on a new object or list of objects (typically, the current selection). The type of the method is Am_Selective_New_Allowed_Method
, which takes the command object, the new object or list of objects to be operated on, and returns a boolean. The default method is Am_Standard_Selective_New_Allowed
, which determines whether the new object is valid and visible.
Am_SELECTIVE_REPEAT_ON_NEW_METHOD
: This performs the repeat on the new object, and is of type Am_Selective_Repeat_New_Method
, which takes the command object and the new object or objects selected.
Am_Standard_Selective_Return_True
: can be used in the Am_SELECTIVE_UNDO_ALLOWED
and Am_SELECTIVE_REPEAT_SAME_ALLOWED
slots to always return true (the command is always allowed).
Am_Selective_Allowed_Return_False
: same as above, except to always return false (the command is never allowed)
Am_Undo_Dialog_Box
object which implements one form of undo dialog box. This is exported from undo_dialog.h
(or undo_dia.h
on the PC). You can see an example of this dialog box in the test file testselectionwidgets.cc (or testselw.cpp on the PC).This dialog box is not part of the standard system, so you have to initialize it explicitly using
Am_Initialize_Undo_Dialog_Box()
. You can then create an instance of the Am_Undo_Dialog_Box
, and set its required slots, which are:
Am_UNDO_HANDLER_TO_DISPLAY
: Set with the undo handler from the main window.
Am_SELECTION_WIDGET
: Set with the selection widget (see Section 6.2.6 of the Widgets chapter) for the main window.
Am_SCROLLING_GROUP
: If this is set with a scrolling group, then the Am_Undo_Dialog_Box
will allow scrolling in that window to be queued for undoing or redoing.
Am_Show_Undo_Dialog_Box_Command
from undo_dialog.h
. This command requires that the dialog box object be put into the Am_UNDO_DIALOG_BOX
slot of the command object.For example, the set up of the undo dialog box in the test program testselectionwidgets.cc is:
Am_Initialize_Undo_Dialog_Box();
my_undo_dialog = Am_Undo_Dialog_Box.Create("My_Undo_Dialog")
.Set(Am_LEFT, 550)
.Set(Am_TOP, 200)
.Set(Am_UNDO_HANDLER_TO_DISPLAY, undo_handler)
.Set(Am_SELECTION_WIDGET, my_selection)
.Set(Am_SCROLLING_GROUP, scroller)
.Set(Am_VISIBLE, false)
;
Am_Screen.Add_Part(my_undo_dialog); //don't forget to add the db to the screen
menu_bar = Am_Menu_Bar.Create("menu_bar")
.Set(Am_ITEMS, Am_Value_List ()
...
.Add(Am_Show_Undo_Dialog_Box_Command
.Create()
.Set(Am_UNDO_DIALOG_BOX, my_undo_dialog))
;
Am_Undo_Handler
and supply values in the following slots:
Am_REGISTER_COMMAND
, a method to be called to register a newly executed Command. This method should correctly handle the case when the command has an Implementation Parent command object, in which case the command will usually not be queued directly since it will be queued indirectly when the top-level command in the implementation-parent chain is queued. The method in this slot is of type Am_Register_Command_Method
.
Am_PERFORM_UNDO
, a method of type Am_Object_Method
to be called to execute the undo of the next command to be undone.
Am_PERFORM_REDO
, a method of type Am_Object_Method
to be called to execute the redo of the last undone Command.
Am_UNDO_ALLOWED
, should contain a formula which returns a command object or NULL, to say whether undo is currently allowed and if so, on which command object it will be performed.
Am_REDO_ALLOWED
, should contain a formula which returns a command object or NULL, to say whether redo (undo the undo) is currently allowed and if so, on which command object it will be performed.
Am_Handler_Selective_Undo_Method
to be called to perform the selective undo.
Am_Selective_Allowed_Method
which returns true if the specified command can be selectively repeated.
Am_Selective_New_Allowed_Method
and returns true if the specfied command can be repeated on the specified new object or objects.
Am_SELECTIVE_REPEAT_ON_NEW_METHOD
which contains a method of type Am_Handler_Selective_Repeat_New_Method
which performs the repeat on new.
cout
) whenever an ``interesting'' Interactor or Command event happens. Amulet supplies many options for controlling when printout occurs, as described below. You typically would set these in the Inspector, but you can also set these parameters in your code.
typedef enum { Am_INTER_TRACE_NONE, Am_INTER_TRACE_ALL,
Am_INTER_TRACE_EVENTS, Am_INTER_TRACE_SETTING,
Am_INTER_TRACE_PRIORITIES, Am_INTER_TRACE_NEXT,
Am_INTER_TRACE_SHORT } Am_Inter_Trace_Options;
void Am_Set_Inter_Trace(); //prints current status
void Am_Set_Inter_Trace(Am_Inter_Trace_Options trace_code);
void Am_Set_Inter_Trace(Am_Object inter_to_trace);
void Am_Clear_Inter_Trace();By default, tracing is off. Each call to
Am_Set_Inter_Trace
adds tracing of the parameter to the set of things being traced (except for Am_INTER_TRACE_NONE
which clears the entire trace set). The options for Am_Set_Inter_Trace
are:
Am_Set_Inter_Trace
is called with no parameters, it prints out the current tracing status.
Am_INTER_TRACE_NONE
: If Am_Set_Inter_Trace
is called with zero or Am_INTER_TRACE_NONE
, then it sets there to be nothing be traced. This is the same as calling Am_Clear_Inter_Trace
.
Am_INTER_TRACE_ALL
: Traces everything.
Am_INTER_TRACE_EVENTS
: Only prints out the incoming events, and not what happens as a result of these events. When you trace anything else, Amulet automatically also adds Am_INTER_TRACE_EVENTS
to the set of things to trace, so you can tell the event which causes things to be updated.
Am_INTER_TRACE_SETTING
: This very useful option just shows which slots of which objects are being set by Interactors and Commands. It is very useful for determining why an object slot is being set.
Am_INTER_TRACE_PRIORITIES
: This prints out changes to the priority levels.
Am_INTER_TRACE_NEXT
: This turns on tracing of the next Interactor to be executed. This is very useful if you don't know the name of the Interactor to be traced.
Am_INTER_TRACE_SHORT
: This prints out only the name of the Interactors which are run.