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.8 in the Overview chapter.
All of the Interactors are highly parameterized so that you can control many aspects of the behavior simply by setting slots of the Interactor object. For example, you can easily specify which mouse button or keyboard key starts the interactor. In order to affect the graphics and connect to application programs, each Interactor has multiple protocols. For example, the ``Move-Grow'' interactor, for moving graphical objects with the mouse, explicitly sets the 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.
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, redo, selective undo and redo, help, and enabling of operations. Each Interactor and Widget has a Command object as the part named 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.
Amulet currently supports two undo models, fully described in Section 5.5.2. The first is a simple single undo, like on the Macintosh. The second is a sophisticated new undo model which provides undo, redo (undo the undo), and selective undo and repeat of any previous command. The sections about the various Interactors discuss their default operation for undo, redo and repeat.
The first step in programming an Interactor is to pick one of the fundamental built-in styles of behavior that is closest to the interaction you want. The current choices are (these are exported in 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_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.
The parameters are set as normal slots of the objects. The names of the slots are defined in 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");
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.
Note: Do not use a C++ char
to represent the events. It must be a C string or an Am_Input_Char
object.
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. (If you want an interactor to start immediately, use the Am_Start_Interactor
function described in Section 5.4.5.)
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. If Am_ABORT_WHEN
is false
, then the interactor will never abort. (To explicitly abort an interactor from while it is running, use the Am_Abort_Interactor
function described in Section 5.4.5.)
Am_Input_Char
,
true
or false
. The value true
matches any event, and false
will never match any event. For example, you might use false
in the Am_ABORT_WHEN
slot of an Interactor to make sure it is never aborted. Note: It doesn't work to make the Am_START_WHEN
be true
, use Am_Start_Interactor()
instead.
Am_Value_List
of Am_Input_Chars
or strings, in which case the action happens when any of the characters match. For example, the following will start when either the F11 function key or control-y is hit:
inter.Set(Am_START_WHEN, Am_Value_List().Add(``F11'').Add(``^Y''));
Am_Event_Method
(defined in inter.h) which returns true if the interactor should start. For example, such a method might be:
//return true for all characters except `a'
Am_Define_Method(Am_Event_Method, bool, my_check_char,
(Am_Object inter, Am_Object event_window,Am_Input_Char ic)) {
if (ic == Am_Input_Char(``ANY_KEYBOARD'') && ic != Am_Input_Char(``a''))
return true;
else
return false;
}
"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.Most fonts have special characters in the ``high'' part (char values between 128 and 255). In order to allow users to reach these, you can type the character with the META key held down. Amulet converts this to set bit 8 of the char before sending it to the font. This allows entering of European characters. Amulet also supports entering Japanese characters. See Section 5.3.5.5.1 for a discussion of text editing, including how to enter Japanese. Unfortunately, there is currently no way to use the Japanese characters as the start, stop or abort events of an interactor.
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.
Am_Input_Chars
. For example "CONTROL_f"
and "^f"
represent the same key. Note that the short form uses a hyphen (it looks better in menus) and the long form uses an underscore (to be consistent with other Amulet symbols).The currently supported modifiers are:
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, as mentioned above, 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_START_WHEN
slot can be <button>_CLICK
or <button>_DRAG
(instead of ``_DOWN''), e.g., ``MIDDLE_CLICK
''. _CLICK
triggers on the down, but doesn't happen until the up, and then only if you don't move more than Am_Minimum_Move_For_Drag
pixels (default = 3). You can set Am_Minimum_Move_For_Drag
to change the value. _DRAG
triggers after a DOWN and after you move Am_Minimum_Move_For_Drag
pixels. It is fine (and common) to have two interactors with the same Am_START_WHERE_TEST
, one with _DRAG
and one with _CLICK
, and only one will run. For example, the following will select any component of my_group
when the left button is clicked, but move any component if the left button is dragged:my_group.Add_Part(Am_One_Shot_Interactor.Create(``click_only'')
.Set(Am_START_WHEN, ``LEFT_CLICK''))
.Add_Part(Am_Move_Grow_Interactor.Create(``drag_only'')
.Set(Am_START_WHEN, ``LEFT_DRAG''));
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
Am_Value
(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.
(Am_Value)ic;
convert ic
into an Am_Value
, 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
.
bool b = ic.Valid();
returns true if ic is non-zero.
bool b = Am_Input_Char::Test(value);
returns true if the Am_Value
parameter is of type Am_Input_Char
.
Am_Input_Char
are: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;
enum Am_Button_Down { Am_NEITHER = 0, Am_BUTTON_DOWN = 1,
Am_BUTTON_UP = 2, Am_ANY_DOWN_UP = 3,
Am_BUTTON_CLICK = 4, Am_BUTTON_DRAG = 5 };
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
Add_Part
method of objects. For example, to make the 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);
my_rect.Add_Part(select_it);
rect2 = my_rect.Create(); //rect2 will have its own inter which is an instance of select_itIt 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
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.7.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();
//group2 will have its own interactor as well as instances of rect and rect2If 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
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,
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.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
. If you want the interactor to work on some objects and not others, you can use the Am_INACTIVE_COMMANDS
slot of the objects, as explained in Section 5.4.7.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);
// create based on the values in an object
Am_Inter_Location (const Am_Object& object);
//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);
bool operator== (const Am_Inter_Location& test); //regular equals test
bool operator!= (const Am_Inter_Location& test); //not equals
bool operator>= (const Am_Inter_Location& test); // A >= B is A contains B
bool operator&& (const Am_Inter_Location& test); // test overlapping
Am_Inter_Location Copy() const; //make a new one like me
};
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
These are the default values of Am_Interactor
's slots. Most of these are advanced features and are discussed in Section 5.4.
Am_SET_SELECTED
of choice and one-shot interactors that if set with false, will prevent the setting of the Am_SELECTED
and Am_INTERIM_SELECTED
slots.
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. (Often, if you want objects to be selectable and editable, you can use the Am_Selection_Widget
described in the Widgets chapter. You would use a Am_Choice_Interactor
when you want to define your own custom way to select objects, or to create your own kind of menu.
The standard behavior of this interactor 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.
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:
// type of the Am_HOW_SET slot for Choice Interactors
Am_Define_Enum_Long_Type(Am_Choice_How_Set)
const Am_Choice_How_Set Am_CHOICE_SET (0);
const Am_Choice_How_Set Am_CHOICE_CLEAR (1);
const Am_Choice_How_Set Am_CHOICE_TOGGLE (2);
const Am_Choice_How_Set Am_CHOICE_LIST_TOGGLE (3);
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_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_SET_SELECTED
: Normally, the Am_Choice_Interactor
sets the Am_SELECTED
and Am_INTERIM_SELECTED
slots of the objects clicked on. If the Am_SET_SELECTED
slot of the Interactor is set to false, then this behavior is prevented, and no slots of the objects are set (the Am_VALUE
of the Interactor and the command are still set, and the Do method is still called however).
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.Get (Am_INTERIM_SELECTED)) return thick_line;
else return thin_line;
}
Am_Define_Style_Formula (rect_fill) {
if ((bool)self.Get (Am_SELECTED)) return Am_White;
else return self.Get (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");
window.Add_Part (select_inter);
//To allow multiple objects to be selected, set the Am_HOW_SET slot:
select_inter.Set(Am_HOW_SET, Am_CHOICE_LIST_TOGGLE);
Am_Choice_Interactor
The default undo of the 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.
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
.
The slots for the Am_One_Shot_Interactor
are identical to those for the Am_Choice_Interactor
(see above).
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")
.Set_Part(Am_COMMAND, change_setting_command)
;
window.Add_Part (how_set_inter);
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.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
(Am_No_Object
) (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 values of the Am_Move_Grow_Where_Attach
enumeration type, defined 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_WHERE_HIT_WHERE_ATTACH
: When the value of the Am_WHERE_ATTACH
slot is Am_ATTACH_WHERE_HIT
, this read-only slot contains the actual side of the object that is going to be changed, which will be one of the values of the Am_Move_Grow_Where_Attach
enumeration (as above).
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, const Am_Object& ref_obj, int x, int y,
int& out_x, int & out_y)) { ... }
//see the filesamples/space/space.cc
for the complete code of this function
Am_GRID_X
, Am_GRID_Y
: If Am_GRID_METHOD
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.
The gridding method can be used for many different effects, including:
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_WHEN, "MIDDLE_DOWN");
window.Add_Part (move_inter);
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.
The default Undo method of the 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.
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. To see a good example of the use of the Am_New_Points_Interactor, see samples/examples/example1.cc.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_Points_Interactor
Am_New_Points_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.// 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. 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.
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
.
When the 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 method in the Am_TEXT_CHECK_LEGAL_METHOD slot is called (if any) to see if the string is legal. If so, the cursor is turned off and the command object's DO_METHOD method is called. If not legal, then the return value of the Am_TEXT_CHECK_LEGAL_METHOD tells the interactor what to do. The stop_when event is not entered into the string.
The default 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.
Notice that 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.
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_TEXT_CHECK_LEGAL_METHOD
: See Section 5.3.5.5.2.
Am_TEXT_CHECK_LEGAL_METHOD
should be a method of type Am_Text_Check_Legal_Method
(defined in inter.h) which can be used to test whether the typed string is legal or not. The method has the form:Am_Define_Method(Am_Text_Check_Legal_Method, Am_Text_Abort_Or_Stop_Code,
check_num_method, (Am_Object &text, Am_Object & inter)) { ... }It is passed the Am_Text object being edited, and the method is expected to test the string in the
Am_TEXT
slot of the text
parameter. The method may also modify the string to change the value. Often, you might want to pop up an error dialog box explaining the error inside of the method. This method should return one of the following values (which are of type Am_Text_Abort_Or_Stop_Code):Am_TEXT_OK
: the string is OK, the interactor will stop normally.
Am_TEXT_ABORT_AND_RESTORE
: the string is bad, abort the Interactor and go back to the original value.
Am_TEXT_KEEP_RUNNING
: the string is bad, keep the interactor running so the user can try again to get a legal value.
Am_TEXT_STOP_ANYWAY
: the string is bad, but stop anyway. This is operationally the same as returning Am_TEXT_OK
.
Am_TEXT_CHECK_LEGAL_METHOD
, the following is the method from the example samples/examples/example2.cc that checks whether the string is 9 characters or not. It pops up an error dialog if not, and requires the user to continue typing:Am_Define_Method(Am_Text_Check_Legal_Method, Am_Text_Abort_Or_Stop_Code,
check_soc_number, (Am_Object &text, Am_Object& /*inter */)){
Am_String str = text.Get(Am_TEXT);
int len = strlen(str);
if (len == 9) return Am_TEXT_OK;
else {
Am_POP_UP_ERROR_WINDOW(``Social Security Number must be 9 digits.'')
return Am_TEXT_KEEP_RUNNING;
}
}
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 Am_TEXT_CHECK_LEGAL_METHOD
is called, if any, and the interactor is continued, aborted or stopped as specified by the return value of the method. If everything is OK, 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 (an 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.
By default, the gesture interactor provides no visible feedback while the user is tracing out a gesture. To provide feedback, set the 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);
The list below summarizes the three most common ways to use a gesture interactor, showing how the interactor's slots should be initialized in each case.
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.
After training a classifier, save it to a file. Classifiers can be saved either with or without the examples that constructed them, depending on a checkbox in Agate's Save-File dialog. If you want to change the classifier later, then you should save it with examples, otherwise editing will be impossible. If you want to make the saved classifier as small as possible (perhaps to distribute with your application), then you can save it without examples. Either kind of file can be read into an Amulet program and used in a gesture interactor.
To use a saved classifier in an Amulet program, create an instance of the wrapper 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);
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 zigzag 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 absolute 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
Otherwise, when the user performs the 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
.
After classifying the gesture, the interactor looks for a command object to invoke. First, it searches the list in its 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.
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.Get(Am_START_OBJECT);
if ((bool)start_object.Get(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.Get(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.
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 Am_RUN_ALSO
slot can also be set with a single interactor or an Am_Value_List of interactors, to only let those interactors run after this one.
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
.
If the Interactor that accepts the event has its 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.
Furthermore, if an Interactor that has 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.
Am_RUN_ALSO
can be set to an interactor or a list of interacters instead of a boolean. When the Am_RUN_ALSO
slot is an interactor then only that interactor is permitted to run along with the interactor that has already started. Likewise with a list, only interactors in the list are permitted to run. All other interactors are not allowed to run.
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.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 Am_WINDOW
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.
If you want an interactor to never start by itself, you can have the Am_START_WHEN
slot be NULL
or false
, and then start it explicitly using the Am_Start_Interactor procedure. Similarly, the Am_STOP_WHEN
slot can be NULL
or false
for an Interactor that should never stop by itself. You might also set its Am_RUN_ALSO
slot and its Am_PRIORITY
so other interactors can also run (see Section 5.4.2). For example, the following interactor will be always running:
inter = Am_Move_Grow_Interactor.Create(``always running'')
.Set(Am_STOP_WHEN, false)//
never stop
.Set(Am_ABORT_WHEN, false) //never abort
.Set(Am_RUN_ALSO, true); //let other interactors run also
obj.Add_Part(inter);
Am_Start_Interactor(inter); //make inter start running now.
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. It is legal to call Am_Pop_Up_Window_And_Wait from inside a DO method, so a modal window might pop up another modal window.
extern void Am_Finish_Pop_Up_Waiting(Am_Object window,
Am_Value return_value);Am_Finish_Pop_Up_Waiting sets window's
Am_VISIBLE
slot 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 ...;
Am_ACTIVE
slot. Often this slot will be controlled by a formula. (If an interactor is running, setting the slot will not abort it: use the functions Am_Abort_Interactor()
or Am_Stop_Interactor()
-- Section 5.4.5).Am_MOVE_INACTIVE
: If this slot is true
, then the object cannot be moved by an Am_Move_Grow_Interactor
(or by the Am_Selection_Widget).
Am_GROW_INACTIVE
: If this slot is true
, then the object cannot be changed size by an Am_Move_Grow_Interactor
(or by the Am_Selection_Widget).
Am_SELECT_INACTIVE
: If this slot is true
, then the object cannot be selected by an Am_Choice_Interactor
(or by the Am_Selection_Widget).
Am_TEXT_EDIT_INACTIVE
: If this slot is true, then this object cannot be text edited by a Am_Text_Edit_Interactor
.
Am_CHECK_INACTIVE_COMMANDS
slot to false. An easier way is to just not set any object's Am_INACTIVE_COMMANDS slot.As an example, to disable moving of all lines, but allow moving for all other objects, as is done in samples/circuit/circuit.cc, the following is part of the definition of the line prototype:
line_proto = Am_Line.Create(``Wire'')
....
//make so can't move or grow lines
.Add_Part(Am_INACTIVE_COMMANDS, Am_Command.Create(``Line_MG_Inactive'')
.Set(Am_MOVE_INACTIVE, true)
.Set(Am_GROW_INACTIVE, true)
.Set(Am_COPY_INACTIVE, true)
.Set(Am_DUPLICATE_INACTIVE, true)
.Set(Am_CUT_INACTIVE, true));
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:
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.
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.
The main event loop in Amulet takes each input event and looks at the sorted list of Interactors with each window, and then asks each Interactor in turn if they want to handle the input event. This is done by sending the Interactor one of the messages listed below, based on the current state of the Interactor, held in the slot 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 7.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) //if 0 then uses Am_LABEL.Set (Am_ACCELERATOR, 0) // event to also execute this
.Set (Am_ID, 0) // if non-zero, this value is returned to identify the command instead of the label
.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 7.2.4.3 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.
The Am_SAVED_OLD_OWNER
slot is set by the Interactors and Widgets to contain the Interactor and Widget itself. This slot is also very useful in methods and constraints for getting the widget that a command was invoked from. For example, for a command in a menu bar, the Am_SAVED_OLD_OWNER
slot will contain the menu bar 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
.
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.
This novel model for Command objects is more completely described in a conference paper which is available from the Amulet WWW site (http://www.cs.cmu.edu/~amulet/papers/commandsCHI.html
).
When a command is queued for undo, all the commands along the 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.
The advantage of this design is that the low-level Command objects do not need to know how they are being used, and can just operate normally, and the higher-level Command objects will update whatever is necessary. Note that the 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.
You might use the 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.
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.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. Unfortunately this has no effect in Amulet V3. Due to a known bug, the built-in undo-handlers do not grey out the disabled command. This will be fixed in a future release.
If there are operations that should not go on the undo list at all, for example like scrolling, there is an easy way to specify this. Simply set the 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.)
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).
We use the terminology from Windows and the Macintosh, where ``undo'' means to undo the command, ``redo'' means to undo-the-undo (make the command no longer be undone), and ``repeat'' to do the command again. The mechanism supports the conventional undo and redo, as described above. In addition, the user can select a command from a visible list. At this point, the user can ask that the command be undone, it can be repeated on the original objects, or repeated on the currently selected objects. For example, for a command that changes an object that was originally blue to be red, undo would clearly restore the object to be blue, and redo would make it red again. Imagine the object was later turned green. Selective undo of the original change-color command would restore the object to be blue, and selective repeat would make it be red again. Since the selective commands add a new command to the history list, the user might undo the selective operation itself, which would make the object be green again. If a new object was selected, selectively repeating the original command on this new object would make it be red.
All of the built-in commands support the complete selective undo mechanism. The next section describes what you need to implement to create your own commands that support selective undo. Next is described the commands you would add to your application to allow access to the selective undo features.
When a selective undo or repeat operation is requested by the end user, the standard undo handler first makes a copy of the command object, and then executes the appropriate methods. This means that the methods are free to set local data into the commands to support possible subsequent undo of the command.
The messages in each command that support selective undo are:
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_Selective_Allowed_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_SELECTIVE_REPEAT_NEW_ALLOWED
slot to always return true
.
Am_SELECTIVE_REPEAT_NEW_ALLOWED
slot to always return false
.
Am_Undo_Dialog_Box
object which implements one form of undo dialog box. This is exported from undo_dialog.h
. You can see an example of this dialog box in the test file testselectionwidgets.cc.
You can 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 7.2.7 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 specified 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.