6. Widgets

Amulet provides a full set of widgets, including buttons, menus, scroll bars, and text input fields. Eventually, these will display themselves in different looks, corresponding to the various platforms. The built-in widgets have a large number of parameters to allow programmers to customize them, and the programmer can also create new kinds of widgets by writing new methods.

6.1 Introduction

Many user interfaces, spanning a wide variety of applications, have several elements in common. Menus and scroll bars, for example, are used so frequently that an interface designer would waste considerable time and effort recreating those objects each time they were required in an application.

The intent of the Amulet Widget set is to supply several frequently used objects that can be easily customized by the designer. By importing these pre-constructed objects into a larger Amulet interface, the designer is able to specify in detail the desired appearance and behavoir of the interface, while avoiding the programming that this specification would otherwise entail.

This document is a guide to using Amulet's Widgets. The objects were constructed using the complete Amulet system, and their descriptions assume that the reader has some knowledge of the components of this system: Opal, Interactors, and ORE.

All of the widgets (except the Am_Selection_Widget) are set up to operate with the left mouse button, and ignore the modifier keys. Thus, clicking on a scroll bar with SHIFT_CONTROL_LEFT_DOWN does the same as regular left down. The default widget start character is exported as Am_Default_Widget_Start_Char.

6.1.1 Current Widgets

Amulet currently supports the following widgets. These widgets are described in this chapter in detail, and summarized in chapter 8.

There are four dialog boxes that provide facilities many applications will find useful:

In addition, there are a set of command objects that can be put into widgets to perform many of the standard editing operations:

6.1.2 Customization

We have tried to make the widgets flexible enough to meet any need. Each widget has a large number of slots which control various properties of its appearance and behavior, which you can set to customize the look and feel. The designer may choose to leave many of the default values unchanged, while modifying only those parameters that integrate the object into the larger user interface.

The visual appearance and the functionality of a widget is affected by values set in its slots. When instances of widgets are created, the instances inherit all of the slots and slot values from the prototype object. The designer can then change the values of these slots to customize the widget. Instances of the custom widget will inherit the customized values. The slot values in a widget prototype can be considered ``default'' values for the instances.

6.1.3 Using Widget Objects

Include files necessary to use Amulet widgets are widgets.h for the widget object definitions, and standard_slots.h for the widget slot definitions. These files are included in amulet.h, providing a simple way to make sure all needed files are included. Programmers who are designing their own custom widget objects will also need widgets_advanced.h. For a complete description of Amulet header files and how to use them most effectively in your project, see Section 1.6 in the Overview chapter.

Widgets are standard Amulet objects, and are created and modified in the same way as any other Amulet object. The following sample code creates an instance of Am_Button, and changes the values of a few of its slots.

Am_Object my_button = Am_Button.Create("My Button")
  .Set (Am_LEFT, 10)  // set the position of the button
  .Set (Am_TOP, 10)
  .Set (Am_COMMAND,"Push Me");
       // a string in the Am_COMMAND slot specifies the button's label: see below

6.1.4 Application Interface

Like interactors, widgets interface to application code through command objects added as parts of the widgets. Please see Section 5.6 on Commands in the Interactor's chapter. In summary, instead of executing a call-back procedure as in other toolkits, Amulet widgets call the Am_DO_METHOD of the command object stored as the Am_COMMAND part of the widget.

6.1.4.1 Accessing and Setting Widget Values

In addition to the Am_DO_METHOD, each command also contains the other typical slots of command objects. In particular, the Am_VALUE slot of the command object is normally set with the result of the widget. Of course, the type of this value is dependent on the type of the widget. For scroll bars, the value will be a number, and for a checkbox panel, it will be a list of the selected items.

The Am_VALUE slot of the widget is also set with the current value of the widget. If you want to set the value of the widget (change the displayed value of the widget), you can set the value of the Am_VALUE slot of the widget to the correct value. Note: Set the Am_VALUE slot of the widget not of the command object, to change the value of the widget. It is also appropriate to put a constraint into the Am_VALUE slot of the widget if you want the value shown by the widget to track a slot of another object.

In some situations, the programmer might want to have a constraint dependent on the Am_VALUE slot. This constraint can perform side effects like updating an external data base or even setting slots in Amulet objects or creating or destroying new objects. Other times, the programmer will need to write an Am_DO_METHOD which will typically access the value in the command's Am_VALUE slot. An example of each of these methods can be found below. Of course, if you write your own Am_DO_METHOD and you want the widget to be undo-able, you will also need to write a corresponding Am_UNDO_METHOD, etc. See Section 5.6 in the Interactors chapter on commands for more information.

6.1.4.2 Commands in Widgets

All of the widgets are designed so the command objects are completely replaceable. Thus, you can put the commands from the library or your new commands into any widget. Alternatively, you can get the default commands in the widgets (which has all blank methods) and override the methods. For example:

  vscroll = Am_Vertical_Scroll_Bar.Create()
    .Set(Am_LEFT, 450)
    .Set(Am_TOP, 80)
    ;
  vscroll.Get_Part(Am_COMMAND)
		.Set(Am_DO_METHOD, my_do)
		.Set(Am_UNDO_METHOD, my_undo)
		;

6.1.4.3 Undoing Widgets

Internally, each widget is implemented using graphical objects and interactors. Each internal interactor has its own associated command objects, but these are normally irrelevant to the programmer, since the internal command objects will call the top-level widget command object automatically. This is achieved because the internal commands have their Am_IMPLEMENTATION_PARENT slot of the internal command objects to be the widget's command object, and then Amulet automatically does the right thing.

By default, when commands that are put into widgets are undone, the widget's internal commands are also undone. This means that the widget will typically go back to their original value when the user selects undo, redo or selective undo or repeat.

6.2 The Standard Widget Objects

This section describes the widgets in detail. Each object contains customizable slots, but the designer may choose to ignore many of them in any given application. Any slots not explicitly set by the application are inherited from the widget's prototype.

6.2.1 Slots Common to All Widgets

There are several slots the programmer can set which are used by all widgets in a similar way:

The command objects in all widgets have the following standard slots:

6.2.2 Border_Rectangle

The Am_Border_Rectangle has a raised or lowered edge of a lighter or darker shade of the Am_FILL_STYLE. It ignores the Am_LINE_STYLE. It looks pressed in if Am_SELECTED is true, and sticking out of the screen if Am_SELECTED is false. This widget has no interaction or response to the mouse.

6.2.3 Buttons and Menus

All of the buttons and menus operate fairly similarly.

For a single, stand-alone button, the Am_COMMAND slot can either be the string or object to display in the button, or it can be an Am_COMMAND object, in which case, the label of the widget is determined by the Am_LABEL slot of the Am_COMMAND part of the widget, which itself should be a string or object, as described below.

The various panel objects (that display a set of buttons) and the menus (that display a set of buttons) all take an Am_ITEMS slot which must contain an Am_VALUE_LIST. The items in this value list can be:

There are two basic ways to use the panel-type objects, including menus:

1. Each individual item has its own command object, and the Am_DO_METHOD of this command does the important work of the item. This would typically be how menus of operations like Cut, Copy, and Paste would be implemented.
2. The top-level panel itself has a command object and the individual items do not have a command object. For example, the Am_ITEMS slot of the widget contains an Am_VALUE_LIST of strings. In this case, the top-level command object's Am_DO_METHOD will be called, and it typically will look in its Am_VALUE slot to determine which item was selected. This method is most appropriate when the panel or menu is a list of values, like colors or fonts, and you do not want to create a command for each item.
Note that the top-level command object is not called if the individual item has a command object, unless you explicitly set the Am_IMPLEMENTATION_PARENT of the item's command to be the widget's command. It would be unusual, but is perfectly legal, to have a Am_Value_List that contains some commands and some strings.

6.2.3.1 Commands in Buttons and Menus

Slots of the command object used by buttons and menus are as follows. More details are available in Section 5.6 of the Interactor chapter.

6.2.3.2 Accelerators for Commands

If the Am_ACCELERATOR slot of a Command object for a menu item is set with an Am_Input_Char (see Section 5.3.3.1) or a string that can be converted into a character description, like "CONTROL_F7", then Amulet will automatically create an accelerator for that command in the window the widget is attached to. In addition, the menu item will show the accelerator using the ``short string'' form of Am_Input_Chars. This accelerator will only be active when the command is active.

The interface to the accelerator lists for windows is available in widget_advanced.h through the functions Am_Add_Accelerator_Command_To_Window and Am_Remove_Accelerator_Command_From_Window, which take the window and command object.

6.2.3.3 Am_Menu_Line_Command

Am_Menu_Line_Command is a special purpose type of command object provided by Amulet to draw horizontal dividing lines in menus. To add a horizontal line in a menu, simply include an instance of Am_Menu_Line_Command in the menu's Am_ITEMS list. An example of this can be found in section 6.2.3.7. Am_Menu_Line_Command has no customizable slots, and it is an inactive menu item.

6.2.3.4 Am_Button

The Am_Button object is a single stand-alone button. A button can have a text label, or can contain an arbitrary graphical object when drawn.

Special slots of Am_BUTTONs are:

6.2.3.5 Am_Button_Panel

An Am_Button_Panel is a panel of Am_Buttons, with a single interactor in charge of all the buttons. Since an Am_Button_Panel's prototype object is a Am_Map, all the slots that Am_Map uses are also used by Am_Button_Panel. See the Opal chapter for a description of Am_Map. Some Am_Map slots are described below along with slots specific to Am_Button_Panel.

Special slots of Am_Button_Panel are:

6.2.3.5.1 Example of Using a Button Panel
Each button in the panel is drawn with a text label or a graphical object inside it. An Am_Value_List in the Am_ITEMS slot tells the button panel what to put inside each button. If a string is specified, it is used as the button's label. If a graphical object is specified, it is drawn in the button. If a command object is specified, that command object's Am_DO_METHOD method is called each time the button is pressed, and the button's label or graphical object is obtained from the command object's Am_LABEL slot. The following code specifies a button panel with three buttons in it.

// a graphical object and custom do action, defined elsewhere:
extern Am_Object My_Graphical_Object;
extern void My_Custom_Do_METHOD (Am_Object command_obj);
Am_Object my_command;
// my button panel:
Am_Object My_Button_Panel = Am_Button_Panel.Create ("My Button Panel")
  .Set (Am_ITEMS, 
        Am_Value_List ()
          .Add ("Push me.")
          .Add (My_Graphical_Object)
          .Add (my_command = Am_Command.Create()
                  .Set (Am_LABEL, "Push me too.")
                  .Set (Am_DO_METHOD, My_Custom_Do_Method)));
The first button in the panel is drawn with the text label ``Push me.'' and does not have its own command object. The second button in the panel is drawn containing My_Graphical_Object drawn inside it, and also does not have its own command. The third button in the panel is drawn with the text label ``Push me too.'' and has its own command object associated with it.

When the third button is pressed, My_Custom_Do_Method is called, with the button's command object (my_command) as an argument. The command object's Am_VALUE slot will already have been set with either 0, if the button was not selected, or ``Push me too.'' if the button was selected. We assume that this command is not undoable since there is no custom Undo action to go with My_Custom_Do_Method.

If any of the other buttons in the panel are pressed, the do action of My_Button_Panel's command object (in its Am_COMMAND slot) will be called, with the command object as an argument. The Am_VALUE of the command object is set with the labels or objects corresponding to the currently selected buttons.

If you wanted the button panel's command to be invoked when the third button was pressed, you would have to set the third button's command object's Am_IMPLEMENTATION_PARENT slot to contain the button panel's command object. For example, after executing the following code, My_Other_Custom_Do_Method in the panel will be called when any of the buttons are selected.

Am_Object panel_command = My_Button_Panel.Get(Am_COMMAND);
panel_command.Set (Am_DO_METHOD, My_Other_Custom_Do_Method);
my_command.Set (Am_IMPLEMENTATION_PARENT, panel_command);

6.2.3.6 Am_Radio_Button_Panel

A radio button panel is a set of small buttons with items appearing either to the right or left of each button. Exactly one button from the set can be selected at any particular time, and the button stays selected after the user stops interacting with it. The radio button panel is often used to present a user with several different options, only one of which can be in effect at any particular time.

An Am_Radio_Button_Panel is essentially the same as an Am_Button_Panel, with a few exceptions. There are a few new slots, and some of the defaults of the other slots are different. All other slots not listed below act the same way as in an Am_Button_Panel. Since radio buttons always only allow a single selection, the Am_VALUE slot of the top-level Am_COMMAND is always set with either 0 or the ID or label of the selected item.

6.2.3.7 Am_Checkbox_Panel

A checkbox panel is a set of small buttons with items appearing either to the right or left of each button. Zero or more buttons from the set can be selected at any particular time, and the buttons stay selected after the user stops interacting with the panel. The checkbox panel is often used to present a user with several different options that can be in effect at the same time.

An Am_Checkbox_Panel is essentially the same as an Am_Radio_Button_Panel. It is drawn slightly differently, and the following slot is different:

Since multiple items can be selected in an Am_Checkbox_Panel, the Am_VALUE slot of the top-level Am_COMMAND contains an Am_VALUE_LIST of the labels or IDs of the selected items.

6.2.3.8 Am_Menu

An Am_Menu is a single menu panel, implemented as another form of Am_Button_Panel. A menu panel has a background rectangle behind it, and the items are drawn differently than in Am_Buttons.

The following slots of an Am_Menu differ from those in an Am_Button_Panel:

6.2.3.8.1 Simple Example
Here is an example of creating an Am_Menu object.

Am_Object my_menu = Am_Menu.Create("my_menu")
  .Set (Am_LEFT, 150)
  .Set (Am_TOP, 200)
  .Set (Am_ITEMS, Am_Value_List()
		  .Add ("Menu item")
		  .Add (Am_Menu_Line_Command.Create("my menu line"))
		  .Add (Am_Command.Create("item2")
		  	   .Set(Am_ACTIVE, false)
			   .Set(Am_LABEL, "Not active"))
		  .Add (Am_Command.Create("item2")
			   .Set(Am_LABEL, ("Active item"))
			   .Set(Am_ACCELERATOR, "CONTROL_a"));
my_window.Add_Part(my_menu);
The menu has three menu items with a line between the first and second items. The first item appears as ``Menu item'' in the menu, and has no corresponding command object. If that item is selected by the user, the do action of my_menu's command object will be called with ``Menu item'' in its Am_VALUE slot. Since there is no command object associated with the first menu item, there is no way to make it inactive without making the whole menu inactive.

The second menu item appears in the menu as ``Not active''. It will be grayed out, because the Am_ACTIVE slot of its corresponding button command object is set to false. This item cannot be chosen from the menu because it is inactive.

The third menu item appears in the menu as ``Active item ^a''. It does have a command object associated with it, so if it is selected by the user, that command's do action will be executed, and the widget's top level command will not be executed. The widget's top level command object is not called unless you set the individual button command object's Am_IMPLEMENTATION_PARENT slot to point to it. This command has an accelerator, so if the user hits control-a in my_window, the command will be executed.

6.2.3.9 Am_Menu_Bar

The Am_Menu_Bar is a menubar like you might find at the top of a window that has a horizontal row of items you can select, and each one pops down a menu of further options. Sometimes it is called a pull-down menu. Amulet's menu bar currently supports a single level of sub-menus (no pull-outs from the pull-downs). However, any menu item (either at the top level or a sublevel) can be an abitrary Amulet object, just like with other button-type objects.

The interface to menu bars is similar to other button widgets: the Am_ITEMS slot of the menu_bar object should contain an Am_Value_List. However, unlike other objects, the list must contain command objects. The label field of this command object serves as the top-level menubar item. In the command object should be an Am_ITEMS slot containing an Am_Value_List of the sub-menu items. This list can contain command objects, strings or Amulet objects, as with other menus and button panels. For example:

  my_menu_bar = Am_Menu_Bar.Create()
    .Set(Am_ITEMS, Am_Value_List ()
          .Add (Am_Command.Create("File_Command")
                .Set(Am_LABEL, "File")
                .Set(Am_DO_METHOD, my_file_do)
                .Set(Am_ITEMS, Am_Value_List ()
                     .Add ("Open...")
                     .Add ("Save As...")
                     .Add (Am_Command.Create("Quit_Command")
                           .Set(Am_DO_METHOD, my_quit)
                           .Set(Am_LABEL, "Quit")
                           .Set(Am_ACCELERATOR, "^q"))
                     )
                )
          .Add (Am_Command.Create("Edit_Command")
                .Set(Am_LABEL, "Edit")
                .Set(Am_DO_METHOD, my_edit_do)
                .Set(Am_ITEMS, Am_Value_List ()
                     .Add (undo_command.Create())
                     .Add ("Cut")
                     .Add ("Copy")
                     .Add ("Paste")
                     .Add (Am_Menu_Line_Command.Create("my menu line"))
                     .Add ("Find...")
                     )
                )
         )
If a sub-menu item has a command (like Quit or Undo above), then its Am_DO_METHOD is called when the item is chosen by the user. If it does not have a command object (like Cut and Paste above), then the command object of the main item is used (here, the do method called my_edit_do in the command object named Edit_Command will be called for Cut and Paste, and the Am_VALUE slot of the Edit_Command will be set to the string of the particular item selected). Note that because the first level value list must contain command objects, the command object stored in the menu_bar object itself will never be used unless the programmer explicitly sets the Am_IMPLEMENTATION_PARENT slot of a command to the menu_bar's command object. The Am_VALUE of whatever command object is executed will be set to the label or ID of the selected item. Menu bars can also contain accelerators, as shown by the Quit command in the example.

Am_Menu_Bars allow the top level item to be chosen (unlike, say the Macintosh), in which case its command object is called with its own label or ID as the Am_VALUE. The programmer should ignore this selection if, as usually is the case, pressing and releasing on a top-level item should do nothing.

Individual items can be made unselectable by simply setting the Am_ACTIVE field of the command object for that item to false. If the Am_ACTIVE field of a top-level command object is false, then the entire sub-menu is greyed out, although it will still pop up so the user can see what's in it.

Unlike regular menus and panels, the Am_Menu_Bar will not show the selected value after user lets up with the mouse. That is, you cannot have Am_FINAL_FEEDBACK_WANTED as true.

Slots that behave different for the Am_Menu_Bar are:

6.2.3.10 Am_Option_Button

An Option Button is a widget that acts like a menu, but displays only the current value. It looks like a button, with a little notch at the right, and when the user clicks on it, a menu pops up listing all the choices. If the user releases, the value does not change, but if the user moves the mouse and releases, the new selection is shown in the button. The parameters to an Am_Option_Button are the same as those to a Menu, except that Am_FINAL_FEEDBACK_WANTED is ignored, and Am_HOW_SET must stay Am_CHOICE_SET.

6.2.4 Scroll Bars

Am_Vertical_Scroll_Bar and Am_Horizontal_Scroll_Bar are widget objects that allow the selection of a single value from a specified range of values, either as a int or a float (see section 6.2.4.1). You specify a minimum and maximum legal value, and the scroll bar allows the user to pick any number in between. The user can click on the indicator and drag it to set the value. As the indicator is dragged, the value is updated continuously. If the user clicks on the arrows, in the scroll bar, the scroll bar increments or decrements the current value by Am_SMALL_INCREMENT. If the user clicks above or below the scroll bar, the value jumps by Am_LARGE_INCREMENT. Unfortunately, auto-repeat (repeatedly incrementing while the mouse button is held down) is not implemented yet. You can also adjust the indicator's size to show what percent of the entire contents is visible.

Like all other widgets, the Am_Vertical_Scroll_Bar and Am_Horizontal_Scroll_Bar store the value in the Am_VALUE slot of the widget and in the Am_VALUE slot of the Am_COMMAND object. As the value is changed by the user, the Am_DO_METHOD of the command is also continuously called. The Am_VALUE slot of the widget can also be set by a program to adjust the position of the scroll bar indicator.

The Am_Scrolling_Group provides a convenient interface for a scrollable area. It operates similarly to a regular Am_Group (see the Opal chapter), except that it optionally displays two scroll bars which the user can use to see different parts.

6.2.4.1 Integers versus Floats

There are four slots that control the operation of the scroll bars: Am_VALUE_1, Am_VALUE_2, Am_SMALL_INCREMENT, and Am_LARGE_INCREMENT. If all of these slots hold values of type integer, then the result stored into the Am_VALUE slot will also be an integer. If any of these values is a float, however, then the result will be a float. The default values are 0, 100, 1 and 10, so the default result is an integer. Note that the Inspector and cout display floats without decimal parts as integers, but the scroll bar still treats them as floats.

6.2.4.2 Commands in Scroll Bars

The command objects in scroll bars work similarly to other widget commands. The main difference is in the Am_VALUE slot.

The scroll bar command supplies undo methods which resets the Am_VALUE slot and the displayed value to the previous value. However, most applications do not allow scrolling operations to be undone, in which case, you should make sure that the scrolling command is not queued on the undo list (see the section on Undo in the Interactors manual).

For scrolling groups, the default is not to be undoable. If you want the scrolling group commands to be queued for undoing, set the Am_COMMAND slot of the scrolling group to be NULL.

6.2.4.3 Horizontal and vertical scroll bars

These are the default slots of Am_Vertical_Scroll_Bar. An Am_Horizontal_Scrollbar has the same defaults, except it's 200 pixels wide and 20 pixels high.

Here is a description of the customizable slots of a scroll bar:

6.2.4.4 Am_Scrolling_Group

An Amulet scrolling group is useful when you want to display something bigger than will fit into a window and allow the user to scroll around to see the contents. You can use the Am_Scrolling_Group just like a regular group, but the user will be able to scroll around using the optional vertical and horizontal scrollbars.

A scrolling group has two distinct rectangular regions. One is the region that is drawn on the screen, and contains scroll bars, and a rectangle with a visible portion of the group. This region is defined by the Am_LEFT, Am_TOP, Am_WIDTH and Am_HEIGHT of the Am_Scrolling_Group itself. The other region is called the inner region which is the size of all the objects, some of which might not be visible. This area is controlled by the Am_INNER_WIDTH and Am_INNER_HEIGHT slots.

By default, the Am_ACTIVE slots of the scroll bars are calculated based on whether the scroll bars are needed (whether any of the group is hidden in that direction). The percent-visible is also calculated based on the amount of the group that is visible. The Am_H_LARGE_INCREMENT and Am_V_LARGE_INCREMENT are also calculated based on the screen size.

6.2.4.4.1 Members of a Am_Scrolling_Group
You can add and remove members to a scrolling group using the regular Add_Part and Remove_Part methods (be sure to adjust the inner size of the group if the new members change it--you can arrange for this to happen automatically by putting an appropriate constraint into the Am_INNER_WIDTH and Am_INNER_HEIGHT slots, such as Am_Width_Of_Parts and Am_Height_Of_Parts.). However, when enumerating the parts of a Am_Scrolling_Group, do not use a Am_Part_Iterator, since this will also list the scroll bars. Instead, use the Am_Value_List stored in the Am_GRAPHICAL_PARTS slot of the group, which will only contain the objects you added. The Am_GRAPHICAL_PARTS slot can also be used for normal groups (instances of Am_Group and Am_Map), so you can write code that will operate on either scrolling groups or regular groups.

6.2.4.4.2 Am_Scrolling_Group Slots
6.2.4.4.3 Using a Scrolling Group
To use an Am_Scrolling_Group, simply create an instance of it, customize the Am_TOP, Am_LEFT, Am_WIDTH, and Am_HEIGHT slots of the group to define its size, set the Am_INNER_WIDTH and Am_INNER_HEIGHT based on the contents, and add graphical parts to the group.

6.2.4.4.4 Simple Example
Here is a simple example of using a scrolling group.

my_scrolling_group = Am_Scrolling_Group.Create("scroll_group")
    .Set(Am_LEFT, 10)
    .Set(Am_TOP, 10)
    .Set(Am_WIDTH, 200)
    .Set(Am_HEIGHT, 300))
    .Add_Part(Am_Rectangle.Create()
	      .Set(Am_LEFT, 0)
	      .Set(Am_TOP, 0)
	      .Set(Am_WIDTH, 15)
	      .Set(Am_HEIGHT, 15)
	      .Set(Am_FILL_STYLE, Am_Blue)
	      );
my_window.Add_Part(my_scrolling_group);
This creates a scrolling group with an area of 200 by 300 pixels, and an internal region 400 by 400 pixels (the default values are inherited since none were specified). The scrolling group is displayed at location 10,10 in my_window. It contains a single object, a blue square 15 pixels on a side, in the upper left corner of the inner region. The scrolling group will have a vertical scroll bar on the right side of the group, and a horizontal scroll bar on the bottom of the group, as specified by the defaults.

6.2.5 Am_Text_Input_Widget

The Am_Text_Input_Widget is used to get a single line of text input from the user, for example for filenames.

The widget has an optional label to the left of a text type-in field. The label is the value of the Am_LABEL field of the command object (and can be a string or arbitrary Amulet graphical object, or an empty string for no label). The user can click the mouse button in the field, and then type in a new value. The Am_VALUE of the command in the Am_COMMAND slot is set to the new string, and the command's Am_DO_METHOD is called. The command's default Am_UNDO_METHOD restores the Am_VALUE and the displayed string to its previous value. As the user types, if the string gets too long to be displayed, it scrolls left and right as appropriate so the cursor is always visible. The user can finish the typing by typing return on the keyboard, or by clicking outside the text input field.

The text input widget could also be used as an output-only text display by setting the Am_ACTIVE_2 slot (see Section 6.2.1) to false, which will disable the interactors.

The special slots of the Am_Text_Input_Widget are:

6.2.5.1 Command in the Text Input Widget

The Am_LABEL of the command in the Am_Text_Input_Widget is used as the label of the input field, so if you do not want a label, make the slot be the null string "". The Am_VALUE of the widget is set with the value the user types, and the Am_DO_METHOD is called.

6.2.5.2 Tabbing from Widget to Widget

A built-in interactor, Am_Tab_To_Next_Widget_Interactor, and its command object support tabbing from one Am_Text_Input_Widget to another in the same window.

If you add an instance of the Am_Tab_To_Next_Widget_Interactor to a window or group, then by default, hitting the TAB key will move from the one Am_Text_Input_Widget to the next, and SHIFT_TAB will move backwards. As the cursor is moved to the Am_Text_Input_Widget, a box is drawn around the widget to show it is selected, and the widget is started with the old contents of the widget selected in ``pending-delete'' mode, so that if the next character typed is a normal printing character, it will replace the old string. If another widget was operating when the TAB key was hit, then that widget is stopped, which will call its DO_METHOD. There is no way to distinguish leaving a Am_Text_Input_Widget because the user hit RETURN to confirm the value, versus clicking outside, versus hitting TAB to go to the next field. Therefore, it is recommended that you do not use the DO_METHOD of the command in the widget to anything if you are using the Am_Tab_To_Next_Widget_Interactor. The command used by this interactor is not undoable, so moving from field to field is not queued on the undo history.

Note: In toolkits such as Motif and MS Windows, you can TAB to other widgets besides text input widgets, for example to use the arrow keys to select which button to press. This is not yet supported in Amulet, so the TAB interactor just goes from text widget to text widget, skipping all other kinds of widgets.

The Am_Tab_To_Next_Widget_Interactor has a number of slots that can be used to customize the behavior:

6.2.6 Am_Selection_Widget

Am_Selection_Widget is used for selecting, moving, and resizing graphical objects.

Most graphical applications need to have "selection handles," which are small squares that show which object(s) are selected and which allow the objects to be moved and changed size. Surprisingly, most toolkits require each application to reimplement this basic functionality. Amulet supplies this behavior through the supplied Am_Selection_Widget object, which you simply can add to your application, and then its objects will be selectable and manipulatable

Slots that control the Am_Selection_Widget are:

6.2.6.1 Application Interface for Am_Selection_Widget

The Am_Selection_Widget you create should be added to a window or group in which you want the selection handles to appear. In general, it is a good idea to make the Am_OPERATES_ON group be a different group from the group that the Am_Selection_Widget is in. Typically, there will be a top level group, and the Am_OPERATES_ON group and the Am_Selection_Widget will be put into the top level group. For example:

  //scroller is the top-level scrolling group to put things in
  scroller = Am_Scrolling_Group.Create("scroller")
    .Set (Am_LEFT, 55)
    .Set (Am_TOP, 40)
    .Set (Am_INNER_WIDTH, 1000)  
    .Set (Am_INNER_HEIGHT, 1000)
    .Set (Am_INNER_FILL_STYLE, Am_White)
    .Set (Am_WIDTH, scroll_width_formula)   //width and height will be
    .Set (Am_HEIGHT, scroll_height_formula) //   based on window's
    ;
  //all objects that will be created and that can be selected and moved will be put into created_objs
  created_objs = Am_Group.Create("created_objs")
    .Set (Am_LEFT, 0)
    .Set (Am_TOP, 0)
    .Set (Am_WIDTH, 1000)  
    .Set (Am_HEIGHT, 1000)
    ;
  //the selection widget operates on the group created_objs
  my_selection = Am_Selection_Widget.Create("my_selection")
    .Set(Am_OPERATES_ON, created_objs)
  //put the scroller in the window
  my_window.Add_Part(scroller);
  //put the selection widget and the created_objs as parts of the scrolling group
  scroller.Add_Part(created_objs);
scroller.Add_Part(my_selection);

As mentioned above, you can access the list of selected objects in the Am_VALUE slot of the Am_Selection_Widget. You can also set this slot to change the set of selected objects. Be sure to only set this slot to an Am_Value_List, even if you want no objects or a single object selected. For example, to clear the selection, use:

    my_selection.Set(Am_VALUE, Am_Value_List());
There are also two command objects you can use to monitor the selection widget's activities. The Am_COMMAND part is used when the selection changes. The Am_VALUE of the Am_COMMAND object is set with the current selection, and its Am_DO_METHOD is called whenever the selection changes. The Am_IMPLEMENTATION_PARENT of this command is Am_NOT_USUALLY_UNDONE by default, since normally changing the selection is not undoable. If you want the selections to be undoable, set the Am_IMPLEMENTATION_PARENT slot of the Am_COMMAND of the selection widget to be NULL. This is done automatically by the undo dialog box (see Section 5.6.2.3.2) when the user clicks on the undo selections check box.

The other command is used when the user moves or grows an object. This command is in the Am_MOVE_GROW_COMMAND named part. You should probably not replace this command, because it has a built-in formula to make the label be correct based on the operation, but it is fine to override the various DO and UNDO methods. By default, the move and grow operations are undoable, if there is an undo handler attached to the window the selection widgets are in.

6.2.6.2 User Interface to Am_Selection_Widget

The selection widget operates in the standard way of the selection handles on the Macintosh and Windows. The user can click with the specified button (usually the left button) over an object to select it. Holding down the shift key while clicking will add or remove the object under the mouse to the selected set. Thus, to select multiple objects, you can click on them with the shift key held down. If you click in the background, all objects will be de-selected. Unfortunately, selecting objects by dragging out a region is not yet supported.

If you press down on an object and move the mouse, the object will be moved. If multiple objects are selected, they all will be moved. If you click on a selection handle, the object attached to the handle will be changed size. Note that it is currently not possible to grow multiple objects at the same time; only the object that the specific handle is attached to is grown.

6.3 Dialog boxes

Amulet provides three standard dialog box widgets with different appearances but similar operation to be used for simple messages and queries. We also provide several functions to make the dialog boxes easier to use. The dialog boxes are:

6.3.1 Support functions for Dialog Boxes

Several functions are available to make it easier to use dialog boxes in common situations.

void Am_Show_Alert_Dialog (Am_Value_List alert_texts, int x = 100, 
		           int y = 100, bool modal = false)
This routine brings up an alert dialog box at the location (x, y) on the main screen, and waits for the user to close it by clicking the "OK" button. The list alert_texts is a list of char* or Am_String values which will be displayed, one per line, above the "Okay" button. If modal is true, the dialog box will be run modally, otherwise it will be run non-modally. This routine does not return until the user clicks on either OK or Cancel.

Am_Value Am_Get_Choice_From_Dialog (Am_Value_List prompt_texts,
		 int x = 100, int y = 100, bool modal = false);
This routine brings up a choice dialog box at the location (x, y) on the main screen, and waits for the user to close it by clicking either the "OK" or "Cancel" button. The list prompt_texts is a list of char* or Am_String values which will be displayed, one per line, above the buttons. If modal is true, the dialog box will be run modally, otherwise it will be run non-modally. The return value will be a string, either "OK" or "Cancel" depending on which button the user pressed.

Am_Value Am_Get_Input_From_Dialog (Am_Value_List prompt_texts, 
				   Am_String initial_value = "", 
				   int x = 100, int y = 100,
				   bool modal = false)
This routine brings up an input dialog box at the location (x, y) on the main screen, and waits for the user to close it by clicking either the "OK" or "Cancel" button. The list prompt_texts is a list of char* or Am_String values which will be displayed, one per line, above the text input line and buttons. If modal is true, the dialog box will be run modally, otherwise it will be run non-modally. The return value will be a string, the value of the text widget, if the user clicks "OK" or presses RETURN to close the dialog box. The routine returns Am_No_Value if the user clicks "Cancel."

Am_Value Am_Show_Dialog_And_Wait (Am_Object the_dialog, 
				  bool modal = false)
This routine displays a preconfigured dialog box, waits for the user to complete interaction with it, and then returns its value. The only slots the dialog box changes before displaying the dialog box are its command's Am_DO_METHOD and Am_ABORT_METHOD. This routine is useful for reusing a commonly displayed dialog box without the overhead of reallocating and reinitializing the dialog box each time it is displayed. The dialog is added to Am_Screen if it is not already there, but it is not removed when the routine is finished.

6.3.2 Slots of dialog boxes

6.3.3 Am_Text_Input_Dialog slots

6.4 Supplied Command Objects

Many operations should work the same way across many different applications. In particular, graphical editing commands such as cut, copy and paste should always work in a standard fashion. To help with this, Amulet supplies a set of pre-built Command objects that you can simply add to your menus or buttons to perform standard operations. These commands come complete with the complete UNDO methods, enabling methods, labels and accelerators, but you can of course override any of these as desired.

Most of these commands operate on the currently selected objects, so they require that you pass in an instance of a selection widget (Section 6.2.6) in the Am_SELECTION_WIDGET slot of the command object. If you implement your own selection handles and do not use the selection widget, you still may be able to use the following commands, if your selection handles object provides the set of selected objects in the Am_VALUE slot, and allows that slot to be set to change the selection.

The supplied command objects are as follows. See the tables in the Summary chapter, Section 10.7 for a list of the slots to be set in each.

As an example of the use of many of these commands, here is part of the menu bar definition for testselectionwidgets:

menu_bar = Am_Menu_Bar.Create(``menu_bar'')
  .Set(Am_ITEMS, Am_Value_List ()
	 .Add (Am_Command.Create(``File_Command'')
	       .Set(Am_IMPLEMENTATION_PARENT, true) //top command not queued for undo
	       .Set(Am_LABEL, ``File'')
	       .Set(Am_ITEMS, Am_Value_List ()
                     .Add (Am_Quit_No_Ask_Command.Create())
		     )
	       )
	 .Add (Am_Command.Create(``Edit_Command'')
		 .Set(Am_LABEL, ``Edit'')
		 .Set(Am_IMPLEMENTATION_PARENT, true) //top command not queued for undo
		 .Set(Am_ITEMS, Am_Value_List ()
		      .Add (Am_Undo_Command.Create()) //these get the undo_handler from
		      .Add (Am_Redo_Command.Create()) //       menubar's window
		      .Add (Am_Show_Undo_Dialog_Box_Command.Create()
			    .Set(Am_UNDO_DIALOG_BOX, my_undo_dialog))
		      .Add (Am_Menu_Line_Command.Create())
		      .Add (Am_Graphics_Cut_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_Copy_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_Paste_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_Clear_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_Clear_All_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Menu_Line_Command.Create())
 		      .Add (Am_Graphics_Duplicate_Command.Create()
 			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Selection_Widget_Select_All_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      )
		 )
	   .Add (Am_Command.Create(``Arrange_Command'')
		 .Set(Am_LABEL, ``Arrange'')
		 .Set(Am_DO_METHOD, my_do)
		 .Set(Am_IMPLEMENTATION_PARENT, true) //top command not queued for undo
		 .Set(Am_ITEMS, Am_Value_List ()
		      .Add (Am_Graphics_To_Top_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_To_Bottom_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Menu_Line_Command.Create())
		      .Add (Am_Graphics_Group_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      .Add (Am_Graphics_Ungroup_Command.Create()
			    .Set(Am_SELECTION_WIDGET, my_selection))
		      )
		 )
	   )
  ;

6.4.1 Graphics Clipboard

The cut, copy, and paste commands operate on a clipboard object, which can be an arbitrary object whose Am_VALUE slot contains an Am_Value_List of objects. You can specify a particular clipboard to use by passing a clipboard object in the Am_CLIPBOARD slot of the command object. If this is NULL, Amulet uses the global Am_Global_Clipboard object. Note that Amulet does not yet interoperate with the standard Windows or Macintosh clipboards. The ``clipboard'' is currently just local to the Amulet application.

6.4.2 Am_Graphics_Set_Property_Command

The Am_Graphics_Set_Property_Command is designed to set properties like fill color, line style and fonts of graphical objects from menus or palettes. It iterates through all the selected objects setting a specified property to the value gotten from a widget (such as a palette or menu). If a selected object has the Am_CREATED_GROUP slot set to true (as do all groups created by the Am_Graphics_Group_Command), then the Am_Graphics_Set_Property_Command will recursively change the property of all of its parts. Of course, the Am_Graphics_Set_Property_Command is fully undoable and repeatable.

Because the command does not necessarily know how to get the correct value out of the palette or menu or how to update and read the property from the graphical object, a number of methods can be overridden in the command to control how the command gets and sets the value from the palette and from the graphical object. The slots that control the Am_Graphics_Set_Property_Command are:

The method should return (by setting the new_value parameter) the value that the widget currently is providing as the new value of the property. The default method uses the object the command is attached to as the widget, and gets that widget's Am_VALUE. If the contents of the Am_VALUE is an object, then that object's Am_SLOT_FOR_VALUE slot is accessed to get the value. Thus, if the widget is a button panel of where each item is a rectangle having the correct color, then the default method will correctly return the color of the item. Alternatively, if the widget is a menu, where each command in the menu has an Am_ID containing the correct value to use, the default method will return the correct value.
The method should return (by setting old_value) the current value of the property for object, which is used in case the command needs to be undone. The default method just gets the value of the Am_SLOT_FOR_VALUE slot of object.
The method should set object so its property now has value new_value. The default method just sets the Am_SLOT_FOR_VALUE slot of object to be new_value.

6.5 Starting, Stopping and Aborting Widgets

Normally, widgets start, stop and abort running in response to the user's input events, but sometimes it is convenient to explicitly start and stop a widget. The routines in this section are useful for this.

extern void Am_Start_Widget(Am_Object widget,
			    Am_Value initial_value = Am_No_Value);
Explicitly start a widget running. If already running, does nothing. If an initial value is provided, then the widget is started with this as its value. It is up to the programmer to make sure the initial_value is legal for the type of widget. If no initial_value is supplied, the widget is started with its current value, if any.

extern void Am_Abort_Widget(Am_Object widget_or_inter_or_command);
Explicitly abort a widget, interactor or command object. Often, this will be called with a command object, and the system will then find the associated widget or interactor and abort it. If that widget or Interactor is not running, then this does nothing. The function tries to make sure the command object passed in (or the command object associated with the widget or Interactor) is not entered into the command history.

extern void Am_Stop_Widget(Am_Object widget,
			   Am_Value final_value = Am_No_Value);
Explicitly stop a widget. If not running, raises an error. If final_value is supplied, then this is the value used as the value of the widget. If final_value is not supplied, the widget uses its current value. Commands associated with the widget will be invoked just as if the widget had completed normally.


Last Modified: 03:25pm EDT, May 24, 1996