We would very much like to enhance the debugging capabilities of Amulet. If you think of a facility that would be useful, please let us know. An article about the debugging facilities in Amulet is available from the Amulet web site.
debugger.h
file, because it is not included by default when you include amulet.h
. The most portable way to include the debugger header file is:
#include <am_inc.h>
#include DEBUGGER__H
8.3 Inspector
The Inspector is an interactive program that provides access to a large number of debugging features. At its most basic, it displays the slots of an object in a window. The values of most types of slots can be edited. The properties of the slots and the object can be inspected, as well as dependencies of any formulas in the slots. Traces and breakpoints can be set when slots are accessed or set. The Interactor's tracing mechanisms are also available from the Inspector.8.3.1 Invoking the Inspector
To pop up the Inspector on an object, you can put the cursor over the object, and hit the F1 key. The inspector will first print out the window and location in the window of the cursor to the transcript (console) window, and then a new top-level window will appear displaying all the slots of the object and their current values. If no object is under the cursor, then the window itself will be inspected. The Inspector first tries to find a primitve (leaf) object under the cursor, but if that fails, then it presents the front-most group object. If the correct object does not appear, it is usually best to get to the object by going up and down the owner/part tree, as will be explained below.debugger.h
) with an Am_Input_Char that you prefer to use for debugging. Assigning any of these with the null character, using Am_Input_Char()
, will mean that that function is not available.Am_Input_Char Am_Show_Inspector_Char;
// default = F1
Am_Input_Char Am_Show_Position_Char;
// default = F2
Am_Input_Char Am_Ask_Inspect_Char;
The inspector can also be invoked procedurally using either of the functions:// default = F3
// inspect the specific object
void Am_Inspect(Am_Object object);
// The next one takes the name of the object. This is useful from an interpreter.
void Am_Inspect(const char * name);
8.3.2 Overview of Inspector User Interface and Menus
If you single click with the left mouse button over a slot value, a cursor will appear and you can edit the slot's value. The editing keys are the same as for all other text interactors (Section 5.3.5.5.1 of the Interactors chapter). Currently, the inspector will not let you change the type of the value in the slot. Therefore, it uses the current type of the value to decide how to parse the input value. You can edit primitive values, like integers, floats and strings. Depending on whether your compiler supports bools as a primitive type, they will either print out as true and false or 1 and 0. Floating point values print out just like integers if there is no fraction part. You can use the slot properties pop-up window to find out the exact slot type.
For slots which contain named values, like styles (
Am_Red
, Am_Line_8
), objects (Am_Rectangle_123
), constraint names (windows_is_color
) and method names (rectangle_draw
), you can type in a new name. Amulet remembers the names of all built-in or user-defined objects, methods and formulas. If you create your own styles or wrappers, you can arrange for them to have names registered in the database using the ``registry'' mechanism defined in registry.h. For any wrapper object, you can register its name using the Am_Register_Name
procedure, such as: Am_Register_Name (my_color_object, "my_color");
Then, the user will be able to type in my_color
as the value of a slot.Am_Value_Lists
, and you cannot set the items of an Am_Value_List
.8.4 Accessing Debugging Functions Procedurally
Sometimes it might be useful to access the debugging functions from a program, instead of interactively from the Inspector. For example, you program might be crashing even before it fully starts up, so you cannot access the inspector. If you program gets past the Am_Initialize(), then you can still trace slot setting and print the values of the slots of objects. Also, some of these procedures might be executed from a debugger such as gdb that supports calling functions.// inspect the specific object
void Am_Inspect(Am_Object object);
// The next one takes the name of the object. This is useful from an interpreter.
void Am_Inspect(const char * name);
You can cause an object to be ``flashed'' so you can see where it is on the screen. If it is not visible, then this functions writes the reason to the specified stream:void Am_Flash (Am_Object o, ostream &flashout = cout);
The tracing functions provide significantly more features than are available interactively from the inspector. The tracing and breaking function takes an optional object, an optional slot, and an optional value. Whatever ones of these are supplied will control whether to trace or break. Thus, if only the object is supplied, then the trace or break will happen whenever any of the slots of that object are set. If only a value is supplied, then a trace or break will happen whenever any slot of any object is set to that value. If all three parameters are supplied, then a trace or break will happen only when that slot of that object is set to that value.void Am_Notify_On_Slot_Set (Am_Object object = Am_No_Object,
Am_Slot_Key key = 0,
Am_Value value = Am_No_Value);
void Am_Break_On_Slot_Set (Am_Object object = Am_No_Object,
Am_Slot_Key key = 0,
Am_Value value = Am_No_Value);void Am_Clear_Slot_Notify (Am_Object object = Am_No_Object,
Am_Slot_Key key = 0,
Am_Value value = Am_No_Value);
8.5 Hints on Debugging
This section lists some hints of procedures we have found useful for debugging certain situations that we have found occur more than once. If you know anything that should be added to this list, please let us know!
Am_START_WHERE_TEST
of the Interactor does not return an object when you expect it to. Debugging this usually involves setting breakpoints in your start where function.
Am_START_WHERE_TEST
of Interactors tries to be smart about which object to affect, based on the type of the object the Interactor is attached to. You can specifically tell the Interactor which to affect by setting the Am_START_WHERE_TEST
slot to one of the built-in functions (such as Am_Inter_In
or Am_Inter_In_Part
) or to a custom function.
Am_VALUE
slot of the widget itself. Although the Am_VALUE
slot of the command object usually provides the same value, it does not affect the widget to set this slot in the command object. For button-type widgets, the value should either be NULL, or the label or ID of the particular item to become the value (based on the value of the Am_LABEL
slot or Am_ID
slot of the command object for that item).
Am_FINAL_FEEDBACK_WANTED
slot to true.
obj.Valid()
tests, or instead of getting a slot value directly into a variable, use an intermediate Am_Value
. For example:
i = 3 / (int)obj.GV(SLOT); //crashes because returns 0 when not initialized
Am_Value v;
obj.GVM(SLOT, v); //remember to change macro to be GVM
if (v.Valid()) {
i = 3 / (int)v;
obj.Set_Single_Constraint_Mode(SLOT,false);
GV
or GVM
and not Get
or else no dependency will be established. You can select the constraint in the Inspector and check its dependencies. Also, it might be useful to trace or break when the slot's value changes and when the depended on slot's value changes, to make sure that they are really changing.