7. 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.

7.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 behavior 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.

Dialog boxes and windows that use the widgets can often be created interactively using the Gilt interface builder, described in a different chapter.

7.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 some 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:

7.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.

7.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.8 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

7.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. For lots of examples of using widgets and commands in widgets, see the samples (in amulet/samples/*) especially the examples in samples/examples/example*.cc.

7.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.

7.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 (see Section 7.4) 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(Am_COMMAND)
		.Set(Am_DO_METHOD, my_do)
		.Set(Am_UNDO_METHOD, my_undo)
		;
Normally, the command objects should be parts of the widgets, so that if an instance is made of the widget, an instance will also be made of the command object. However, the widgets will still work if the command is just Set into the Am_COMMAND slot:

vscroll.Set_Part(Am_COMMAND, my_command); //command objects should be parts

7.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.

7.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.

7.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:

7.2.2 Widget Looks

The Am_WIDGET_LOOK slot controls which look-and-feel is used to draw the widget. Possible values are Am_MOTIF_LOOK, Am_WINDOWS_LOOK, or Am_MACINTOSH_LOOK, or Am_NATIVE_LOOK which is whatever is the current machine. The default value for this slot is a formula which depends on the Am_WIDGET_LOOK slot of the Am_Screen object, which makes it easy to change all the looks at once. You can override the look locally for an individual widget just by overriding the slot. You can use the following function to change the look of all the widgets at the same time:

void Am_Set_Default_Look( Am_Widget_Look inLook = Am_NATIVE_LOOK );
or else just set the slot of Am_Screen:

Am_Screen.Set(Am_WIDGET_LOOK, new_look);
The default look and feel is the native look for the machine currently being used. When in compiled with debugging enabled, the key sequence CONTROL_SHIFT_META_L will change the look among the three values, so you can see what your application would look like in the three looks.

7.2.3 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.

7.2.4 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.

7.2.4.1 Item Lists and Labels of Commands

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:

7.2.4.2 Commands in Buttons and Menus

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.

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.

7.2.4.3 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 following function can be used if you want two or more windows to have the same accelerator keys. This might be used, for example, if the main window has the menubar, but you still want the accelerators to work in other windows, which don't have a menubar in them.

void Am_Share_Accelerators(Am_Object &source_window,
Am_Object &new_window);
The low-level interface to the accelerator lists for windows is available in widget_advanced.h through the functions Am_Check_Accelerator_Char_For_Window, Am_Add_Accelerator_ Command_To_Window and Am_Remove_Accelerator_Command_From_Window, which take the window and command object.

7.2.4.4 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.

7.2.4.5 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:

7.2.4.6 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:

7.2.4.6.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 four 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))
          .Add (Am_Quit_No_Ask_Command.Create()));
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. The fourth button uses the standard, built-in quit command, which comes pre-set with the methods and label.

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 either of the first two 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);

7.2.4.7 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.

7.2.4.8 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.

7.2.4.9 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:

7.2.4.9.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.

7.2.4.10 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 arbitrary 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:

7.2.4.10.1 Checked items
Items in a menu or menubar can be checked, which will display a check mark to the left of the item. This is achieved by setting the Am_CHECKED_ITEM slot of the command. It is the responsibility of the programmer to insure that only the proper items are checked. Amulet does not un-check one item if a different item is checked! And Amulet does not check an item if the user selects it--the application should do this with the command's DO method. For example, the following code is used by Gilt for the check marks on the current Look and Feel.

//return true if the current look matches the value in my widget look slot
Am_Define_Formula (bool, look_is_me) {
  Am_Value my_look = self.Get(Am_WIDGET_LOOK);
  Am_Value current_look = Am_Screen.Get(Am_WIDGET_LOOK);
  return my_look == current_look;
}
Am_Define_Method( Am_Object_Method, void, set_look, (Am_Object cmd) ) {
  Am_Value my_look = cmd.Get(Am_WIDGET_LOOK);
  Am_Set_Default_Look(my_look);
}
... the rest of the menu items
    .Set(Am_ITEMS, Am_Value_List ()
              .Add (Am_Command.Create(``Motif_Command'')
                 .Set(Am_LABEL, ``Motif Look'')
                 .Add(Am_WIDGET_LOOK, Am_MOTIF_LOOK)
                 .Add(Am_CHECKED_ITEM, look_is_me)
                 .Set(Am_DO_METHOD, set_look)
                 .Set(Am_IMPLEMENTATION_PARENT, true)) //not undoable
              .Add (Am_Command.Create(``Win_Command'')
                 .Set(Am_LABEL, ``Windows Look'')
                 .Add(Am_WIDGET_LOOK, Am_WINDOWS_LOOK)
                 .Add(Am_CHECKED_ITEM, look_is_me)
                 .Set(Am_DO_METHOD, set_look)
                 .Set(Am_IMPLEMENTATION_PARENT, true)) //not undoable
              .Add (Am_Command.Create(``Mac_Command'')
                 .Set(Am_LABEL, ``Macintosh Look'')
                 .Add(Am_WIDGET_LOOK, Am_MACINTOSH_LOOK)
                 .Add(Am_CHECKED_ITEM, look_is_me)
                 .Set(Am_DO_METHOD, set_look)
                 .Set(Am_IMPLEMENTATION_PARENT, true)) //not undoable
...

7.2.4.11 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.

7.2.4.12 Am_Pop_Up_Menu_Interactor

The Am_Pop_Up_Menu_Interactor is actually a one shot interactor combined with a pop-up menu. It is intended for when a menu should pop up when a mouse button is hit, such as how the right button works under Windows95. The Am_Pop_Up_Menu_Interactor takes the regular Am_One_Shot_Interactor slots, plus Am_FILL_STYLE, Am_ITEMS (which should contain an Am_Value_List as in other panels), and Am_WIDGET_LOOK which affect the sub-menu. The Am_COMMAND in the interactor is called if item doesn't have a command. Am_VALUE of the interactor is set with selection from menu.

7.2.5 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. 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_Vertical_Up_Down_Counter widget is like a vertical scroll bar without the middle: it just has the up and down arrows.

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.

7.2.5.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.

7.2.5.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.

7.2.5.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:

7.2.5.4 Am_Vertical_Up_Down_Counter

The Am_Vertical_Up_Down_Counter widget is like a vertical scroll bar without the middle: it just has the up and down arrows. Unlike for scroll bars, for a Am_Vertical_Up_Down_Counter, the Am_VALUE_1 and Am_VALUE_2 fields can be Am_No_Value which means there is no maximum and minimum values.

7.2.5.5 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.

7.2.5.5.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.

7.2.5.5.2 Am_Scrolling_Group Slots
7.2.5.5.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.

7.2.5.5.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.

7.2.6 Text Input Widgets

There are three different text input widgets, that all look the same, but have somewhat different properties. The Am_Text_Input_Widget is used to get a single line of text input from the user, for example for filenames. The Am_Number_Input_Widget (see Section 7.2.6.3) will only allow numbers to be entered, and the Am_Password_Input_Widget works like a Am_Text_Input_Widget but displays *'s instead of the text typed.

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 7.2.1) to false, which will disable the interactors.

The special slots of the Am_Text_Input_Widget are:

7.2.6.1 Text Check Legal Method

The Am_TEXT_CHECK_LEGAL_METHOD slot of any of the text widgets can contain a method of type Am_Text_Check_Legal_Method, which will check the value typed by the user when the user types RETURN or tries to exit the field by clicking outside or by tabbing. The general form of the method is described in Section 5.3.5.5.2 in the Interactors chapter. Note that the object passed to the method will still be the interactor, and the widget is the owner of the interactor. The method can explicitly set the value of the widget. For example, the built-in Am_Number_Input_Filter_Method used by the Am_Number_Input_Widget sets the widget's value to the number converted from the string. For example:

Am_Define_Method(Am_Text_Check_Legal_Method, Am_Text_Abort_Or_Stop_Code,
                  my_filter, ( Am_Object &text, Am_Object& inter) ) {
  Am_String str = text.Get(Am_TEXT);
  Am_Object widget = inter.Get_Owner();
  char *ptr;
  long i = strtol(s, &ptr, 10);
  if (ptr < s+len) {
    Am_POP_UP_ERROR_WINDOW(``Value must be a number.'')
    return Am_TEXT_ABORT_AND_RESTORE;
  }
  else {
     widget.Set(Am_VALUE, result);
     return Am_TEXT_OK;
  }
}

7.2.6.2 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.

7.2.6.3 Am_Number_Input_Widget

The Am_Number_Input_Widget works similarly to the Am_Text_Input_Widget except you can only input numbers.

Special slots are:

7.2.6.4 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:

7.2.7 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. There are lots of parameters to the selection widget, but most programs will only use a few of these. See the file samples/examples/example1.cc for a simple example.

Slots that control the Am_Selection_Widget are:

7.2.7.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.

The Am_Selection_Widget looks at the Am_INACTIVE_COMMANDS slot of the graphical object under the mouse to see if that object is selectively disabled for selection, moving or growing. The Am_INACTIVE_COMMANDS slot can contain an instance of Am_Command, with the slots Am_MOVE_INACTIVE, Am_GROW_INACTIVE, and Am_SELECT_INACTIVE. See also Section 5.4.7.

7.2.7.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. You can also click in the background and drag out a region to select all the objects wholely enclosed in the region.

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.

7.2.7.3 Highlighting one Handle of a Am_Selection_Widget

Sometimes it is useful to know which end of a line the user selected on, or which corner of a box. If this is desired, the Am_Selection_Widget can be configured to supply this information. Simply set the Am_SELECT_CLOSEST_POINT_STYLE slot with some color, and the handle closest to the mouse click will be shown in this different color. The Am_SELECT_CLOSEST_POINT_OBJ will be set to the object under the mouse, which will be selected (the Am_VALUE of the selection_widget is not sufficient because sometimes multiple objects are selected, and the Am_SELECT_CLOSEST_POINT_OBJ will then contain the one object last clicked on). The Am_SELECT_CLOSEST_POINT_WHERE slot is set with the handle that is turned color. The method in the Am_SELECT_CLOSEST_POINT_METHOD slot can be set to customize how the closest point is picked.

7.2.7.4 Multiple Am_Selection_Widgets

Multiple Am_Selection_Widgets can be connected together so there is only one selection across multiple windows. (Actually, multiple Am_Selection_Widgets can be in the same window, but this is rarely a good idea.) As shown in the test file amulet/src/widgets/testdragdrop.cc, the Am_MULTI_SELECTIONS slot of each of the Am_Selection_Widgets should be set with an Am_Value_List containing all of the selection widgets. Multiple selections must all be in the same window, however, and the Am_VALUE of each selection widget will reflect the selections in that window. For example:

  Am_Object select1 = Am_Selection_Widget.Create (``select1'')
    ...   ;
  Am_Object select2 = Am_Selection_Widget.Create (``select2'')
    ...  ;
  Am_Object select3 = Am_Selection_Widget.Create (``select3'')
    ...  ;

  Am_Value_List select_list = Am_Value_List ()
                           .Add (select1).Add (select2).Add (select3);
  select1.Set (Am_MULTI_SELECTIONS, select_list);
  select2.Set (Am_MULTI_SELECTIONS, select_list);
  select3.Set (Am_MULTI_SELECTIONS, select_list);
The Am_MULTI_SELECTIONS slot will link all the selection widgets together. A selected object dragged from one widget can be dropped on any of the other widgets. Widgets can be linked as long as they are in the same program. Widgets in separate windows or widgets that are nested within one another will work correctly.

7.2.7.5 Drag and Drop

Amulet's drag and drop feature has been added as an automatic feature to the normal selection widget. Drag and drop permits the following activities:

Note: Amulet's drag-drop facility is not integrated with any of the compound document software that is available on some platforms. Specifically, our drag-drop has its own implementation and is not hooked into OLE or OpenDoc.

The manner in which the object is dropped onto a selection widget is determined by a method in the Am_DROP_TARGET_TEST slot. A drop test is a method of type Am_Where_Method which looks like:

Am_Define_Method(Am_Where_Method, Am_Object, my_where_test
		      (Am_Object inter, Am_Object widget,
		       Am_Object event_window, int x, int y)) {
  ... }
The widget parameter refers to the selection widget that is the current target of the drag, the inter parameter is the interactor that is moving the object (this can normally be ignored). The event_window, x, and y parameters are the current location of the mouse pointer. The purpose of the where_test is to provide a means for the widget to reject a possible source. If the where_test is ``true,'' which it is by default, then all objects are accepted. If the where_test method exists and it returns Am_No_Object, the drop is rejected. If the method returns an object, then that object is tested for a Am_DROP_TARGET command which will be used for further processing. See below for discussion of Am_DROP_TARGET and Am_Drop_Target_Command.

A generic where method is provided in Amulet called Am_In_Target. This command returns the topmost object that contains a Am_DROP_TARGET slot. This method is useful when the selection widget contains other objects that can serve as drop targets.

Objects other than selection widgets can also serve as drop targets. In such cases the position data is lost but value is transferred by way of a special ``drop target command'' stored in the object. The drop command must be an instance of the type Am_Drop_Target_Command and must be stored in the slot Am_DROP_TARGET of the object that is the potential target.

The Am_Drop_Target_Command has several method slots:

The first slot is the test method which a target uses to accept or reject a value. The method type Am_Drop_Target_Interim_Do_Method has the form:

Am_Define_Method_Type(Am_Drop_Target_Interim_Do_Method, bool,
		      (Am_Object& command_obj, const Am_Value& value))
The command parameter is a self reference to the command object and the value parameter is the object being dragged. The test method should return true or false depending on whether the object is acceptable to the target. The source of the object can be determined by looking at the object's owner.

If the object is accepted, the system will set the Am_INTERIM_SELECTED slot to true. If the mouse passes outside of the target, or the target rejects the object, it will be made false. It is also cleared when the mouse button is released. This slot can be used to change the object's look to show that it is accepting the source.

Once an object is accepted by a target (and dropped by releasing the mouse) Amulet will call the Am_DO_METHOD on the Drop_Target_Command. The do method has an extra parameter from the usual Am_Object_Method.

The value parameter is the object being dropped. The do method may do whatever it wants with the value. Provided the command has a NULL or valid Am_IMPLEMENTATION_PARENT, the command will be queued on the undo handler. There the Am_UNDO_METHOD and Am_REDO_METHOD may be called when appropriate.

For drop target commands that are stored on groups for a selection widget (the group of a selection widget is the object pointed to by the Am_OPERATES_ON slot), the DO, UNDO, and REDO methods are ignored. Instead, normal dragging behavior is performed if the TEST method returns true.

7.3 Dialog boxes

Amulet provides a number of 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. If you need to create custom dialog boxes, you can probably use the Gilt interface builder to interactive lay out the widgets. A good example of the use of dialog boxes is in the file samples/examples/example2.cc.

All of these dialog boxes can be displayed as modal or not. A modal dialog box does not let any other interaction happen until the dialog box is dismissed. If modal = false, then the dialog box, and other interactions can all happen, at the user's discretion.

The built-in dialog boxes are:

7.3.1 Support functions for Dialog Boxes

Several functions are available to make it easier to use dialog boxes in common situations. These routines cache the dialog boxes so they can be reused efficiently.

void Am_Show_Alert_Dialog (Am_Value_List alert_texts,
int x = Am_AT_CENTER_SCREEN,
int y = Am_AT_CENTER_SCREEN,
bool modal = false);
void Am_Show_Alert_Dialog (Am_String alert_text, 
                           int x = Am_AT_CENTER_SCREEN,
                           int y = Am_AT_CENTER_SCREEN,
                           bool modal = true);
This routine brings up an alert dialog box at the location (x, y) on the main screen. The special value Am_AT_CENTER_SCREEN is converted into the center of the screen. The routine 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 "OK" 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. The second form just takes a single line of text.

Am_Value Am_Get_Choice_From_Dialog (Am_Value_List prompt_texts,
                                    int x = Am_AT_CENTER_SCREEN,
int y = Am_AT_CENTER_SCREEN,
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 = Am_AT_CENTER_SCREEN,
int y = Am_AT_CENTER_SCREEN,
                                   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."

// these are called automatically from the Am_Open_Command, Am_Save_Command and
// Am_Save_As_Command
extern Am_String Am_Show_File_Dialog_For_Open(Am_String initial_value='''');
extern Am_String Am_Show_File_Dialog_For_Save(Am_String initial_value='''');
Eventually, these routines will display a standard scrolling list of files. Currently they just use a text-input widget, and can be used for forward compatibility.

To display a custom dialog box, you can use Am_Pop_Up_Window_And_Wait, described in Section 5.4.6. Be sure your custom dialog boxes use the method Am_Default_Pop_Up_Window_Destroy_Method in the Am_DESTROY_WINDOW_METHOD of the dialog box's window, so the dialog box will exit correctly if the user closes it using the window manager's close box.

The macro Am_POP_UP_ERROR_WINDOW takes something that can be passed to cout and displays it in a single line of an Alert dialog box. To use this function, you need to include strstream in your code. For example:

// strstream is needed for Am_POP_UP_ERROR_WINDOW
#include STR_STREAM__H
...
Am_POP_UP_ERROR_WINDOW("Accelerator " << i << " already in use by " << cmd);

7.3.2 Slots of dialog boxes

7.3.3 Am_Text_Input_Dialog slots

7.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 large 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. Good examples of using these are in src/widgets/testselectionwidget and samples/examples/example1.cc.

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

Most of the built-in commands can be disabled for a particular object by setting the appropriate slot of a command object in the Am_INACTIVE_COMMANDS slot of the object. See also Section 5.4.7.

The supplied command objects are as follows. See the tables in the Summary chapter, Section 11.9 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 Gilt:

Am_Object grid_command = Am_Cycle_Value_Command.Create("grid")
                          .Set(Am_LABEL_LIST, Am_Value_List()
                                .Add("Turn Grid On")
                                .Add("Turn Grid Off"));
Am_Object open_commmand;
menu_bar = Am_Menu_Bar.Create("menu_bar")
    .Set(Am_SELECTION_WIDGET, sel_widget)
    .Set(Am_ITEMS, Am_Value_List ()
         .Add (Am_Command.Create("File_Command")
               .Set(Am_LABEL, "File")
               .Set(Am_IMPLEMENTATION_PARENT, true) //not undoable
               .Set(Am_ITEMS, Am_Value_List ()
                   .Add (open_command = Am_Open_Command.Create()
                      .Set(Am_HANDLE_OPEN_SAVE_METHOD, use_file_contents))
                   .Add (Am_Save_As_Command.Create()
                      .Set(Am_HANDLE_OPEN_SAVE_METHOD, contents_for_save))
                   .Add (Am_Save_Command.Create()
                      .Set(Am_HANDLE_OPEN_SAVE_METHOD, contents_for_save))
                   .Add (Am_Command.Create("Save ")
                           .Set(Am_LABEL, "Generate c++")
                           .Set(Am_ACTIVE, true)
                           .Set(Am_DO_METHOD, savecppcmd))
                   .Add (Am_Quit_No_Ask_Command.Create())))
         .Add (Am_Command.Create("Edit_Command")
               .Set(Am_LABEL, "Edit")
               .Set(Am_IMPLEMENTATION_PARENT, true) //not undoable
               .Set(Am_ITEMS, Am_Value_List ()
                    .Add (Am_Undo_Command.Create())
                    .Add (Am_Redo_Command.Create())
                    .Add (Am_Menu_Line_Command.Create())
                    .Add (Am_Graphics_Cut_Command.Create())
                    .Add (Am_Graphics_Copy_Command.Create())
                    .Add (Am_Graphics_Paste_Command.Create())
                    .Add (Am_Graphics_Clear_Command.Create())
                    .Add (Am_Graphics_Clear_All_Command.Create())
                    .Add (Am_Menu_Line_Command.Create())
                    .Add (Am_Graphics_Duplicate_Command.Create())
                    .Add (Am_Selection_Widget_Select_All_Command.Create())
                    .Add (Am_Menu_Line_Command.Create())
                    .Add (Am_Command.Create()
                          .Set(Am_LABEL, "Properties...")
                          .Set(Am_ACTIVE, Am_Active_If_Selection)
                          .Set(Am_DO_METHOD, customize_selected)
                          .Set(Am_UNDO_METHOD, undo_set_properties)
                          .Set(Am_REDO_METHOD, undo_set_properties))))
           .Add (Am_Command.Create("Arrange_Command")
                 .Set(Am_LABEL, "Arrange")
                 .Set(Am_IMPLEMENTATION_PARENT, true) //not undoable
                 .Set(Am_ITEMS, Am_Value_List ()
                      .Add (Am_Graphics_To_Top_Command.Create())
                      .Add (Am_Graphics_To_Bottom_Command.Create())
                      .Add (Am_Menu_Line_Command.Create())
                      .Add (Am_Graphics_Group_Command.Create())
                      .Add (Am_Graphics_Ungroup_Command.Create())
                      .Add (Am_Menu_Line_Command.Create())
                      .Add (grid_command)

7.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.

7.4.2 Commands for Open and Save

Three commands are provided that invoke the standard, built-in load and save mechanism (see Section 3.12). A good example of the use of these commands is in the file samples/examples/example1.cc.

The Am_Open_Command takes a method in the Am_HANDLE_OPEN_SAVE_METHOD slot of type Am_Handle_Loaded_Items_Method which takes the items loaded from the file and puts them in the window. For example,

Am_Define_Method (Am_Handle_Loaded_Items_Method, void, use_file_contents,
		  (Am_Object command, Am_Value_List &contents)) {
  Am_Object created_objs = command.Get_Object(Am_SAVED_OLD_OWNER)
    .Get_Object(Am_WINDOW).Get(CREATED_OBJS);
  Am_Value_List current = created_objs.Get (Am_GRAPHICAL_PARTS);
  Am_Object item;
  //first delete all of the current contents of the window
  for (current.Start (); !current.Last (); current.Next ()) {
    item = current.Get ();
    item.Destroy ();
  }
  //now add the new objects
  for (contents.Start (); !contents.Last (); contents.Next ()) {
    item = contents.Get ();
    created_objs.Add_Part (item);
  }
}
The Save and Save As commands take a method in the same slot (Am_HANDLE_OPEN_SAVE_METHOD) of type Am_Items_To_Save_Method which generates the list of objects to be saved to a file. For example:

//This method should return the list of objects to save
Am_Define_Method (Am_Items_To_Save_Method, Am_Value_List,
contents_for_save, (Am_Object command)) {
  Am_Object created_objs = command.Get_Object(Am_SAVED_OLD_OWNER)
    .Get_Object(Am_WINDOW).Get(CREATED_OBJS);
  Am_Value_List obs_to_save = created_objs.Get (Am_GRAPHICAL_PARTS);
  return obs_to_save;
}
These commands use the Am_Show_File_Dialog_For_Open and Am_Show_File_Dialog_For_Save dialog boxes, which temporarily are just text input strings.

Under Unix and the PC, applications are often invoked with a file to open as the parameter. This is supported using the function Am_Standard_Open_From_Filename which takes the command object used for open and the string. This is why the instance of Am_Open_Command is saved in a variable in the menu_bar example above. For example:

  if (argc > 1) {
    Am_String s = (char *)argv[1];
    Am_Standard_Open_From_Filename(open_command, s);
  }

7.4.3 Cycle_Value_Command

The Am_Cycle_Value_Command can be used to have a widget whose label changes each time it is clicked on. The list of labels is supplied as an Am_Value_List in the Am_LABEL_LIST slot. The Am_VALUE of the command is set with the index of the current label in the list (from 0). If the list has 2 values, then this will be a toggle, like ``Turn Grid On'' <-> ``Turn Grid Off'' with the Am_VALUE taking on 0 and 1, as shown by the grid_command in the menu bar example above. Undoing returns the Am_VALUE and label to the original values. You can also explicitly set the Am_VALUE slot of the command to change the current state. Typically, a constraint would depend on the Am_VALUE of the command. Examples of the use of this command are in src/widgets/testselectionwidget (turning grid on and off) and samples/circuit/circuit.cc (turning animation on and off) and src/widgets/testwidgets.

BUG: The Am_VALUE slot takes on a string value temporarily before settling down with the correct value, so constraints on the value have to be prepared for this. (E.g., Get the value into an Am_Value, and check the type, and ignore strings).

7.4.4 Commands to help with Gestures

Freehand gestures (see Section 5.3.5.6) usually create objects or perform some command on the objects (like Cut). Some built-in commands will help implement these.

Am_Gesture_Select_And_Do_Command must be passed a Am_SELECTION_WIDGET or find one in the widget the command is in. The Am_LABEL of the command will be the string name of the gesture defined in Agate. It then looks for all the graphical objects in the bounding box of the gesture, and causes those objects to be selected. The Am_IMPLEMENTATION_PARENT of the Am_Gesture_Select_And_Do_Command command should be set with the command to be executed on the selected objects, which can be the same command as used in the menu bar. For example:

.Add (Am_Gesture_Select_And_Do_Command.Create ("gesture_cut")
                .Set (Am_LABEL, ":CUT")
                .Set (Am_IMPLEMENTATION_PARENT, cut_command)
                .Set (Am_SELECTION_WIDGET, my_selection))
The other common operation is to create a new object with a gesture. The Am_Gesture_Create_Command can help with this. This command needs a method of the same form as for Am_New_Points_Interactor to create the new object set into the Am_CREATE_NEW_OBJECT_METHOD slot. For example:

Am_Define_Method(Am_Create_New_Object_Method, Am_Object, gesture_creator,
                 (Am_Object cmd, Am_Inter_Location data,
                  Am_Object old_object )) { ... }
The command also needs to know whether the object to be created is like a line or not, set into the Am_AS_LINE slot (default = false). And the programmer can specify an extra command to be executed by setting the Am_Gesture_Create_Command's Am_IMPLEMENTATION_PARENT slot, but this is optional. For example:

   .Add (Am_Gesture_Create_Command.Create(``rect_gesture'')
                .Set (Am_LABEL, ``:RECTANGLE'')
                .Set (Am_AS_LINE, false)
                .Set (Am_CREATE_NEW_OBJECT_METHOD, gesture_create_rect))
   .Add (Am_Gesture_Create_Command.Create()
                .Set (Am_LABEL, ``line'')
                .Set (Am_AS_LINE, true)
                .Set (Am_CREATE_NEW_OBJECT_METHOD, gesture_create_line)
                .Set (Am_IMPLEMENTATION_PARENT,
undo_create_line_cmd.Create()))
Finally, a standard Am_Gesture_Unrecognized_Command is supplied, which prints out a message and beeps.

7.4.5 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.

7.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: 01:46pm EDT, August 13, 1997